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...",
"user_id": 1,
"expires_in": 3600
}
If the account has two-factor authentication enabled and no twofa_code was sent, the response is instead:
{"twofa_required": true, "user_id": 1}
Retry the request with twofa_code included in the body. Login attempts are rate-limited per IP; exceeding the limit returns 429 Too Many Requests.
Step 2 — Use it on every request:
curl https://panel.example.com/api/account \
-H "Authorization: Bearer eyJhbGciOi..."
Tokens expire after 1 hour (expires_in is in seconds). 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/PIDs limits​
PATCH /api/containers/myapp/resources
Content-Type: application/json
{"cpu": "1", "ram": "512m", "pids": "512"}
All three fields are optional — send only the ones you want to change.
{"message": "Resources updated", "results": {"cpu": "...", "ram": "...", "pids": "Max PIDs for container myapp set to 512"}}
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"}
PHP App Manager​
Installs and manages a Composer-based PHP project in an existing domain's docroot, run inside whichever shared php-fpm-<version> container that domain's PHP version already points to. Unlike the Node.js/Python app installer below, this never creates a dedicated container — the domain's existing vhost already routes to the right php-fpm container.
Install a PHP app​
Form-encoded body; response streams newline-delimited JSON progress events.
POST /api/php/install
Re-run composer install / update​
POST /api/php/apps/composer-install/example.com
POST /api/php/apps/composer-update/example.com
Content-Type: application/json
{"optimize_autoloader": "true"}
Get Composer run log​
GET /api/php/apps/logs/example.com
Response is plain text ("No Composer runs recorded yet." if none).
Remove a PHP app​
Removes the app's tracking entry and .env settings — docroot files and the database, if any, are left untouched.
DELETE /api/php/apps/example.com
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"}
]
}
Get activity log​
Paginated, newest first. search filters by substring (forces show_all); show_all=true returns every matching line on one page.
GET /api/account/activity?page=1&search=&show_all=false
{
"rows": [
{"Timestamp": "2026-01-15 12:00:00", "IP": "203.0.113.10", "User": "myuser", "Action": "logged in via user API"}
],
"page": 1, "per_page": 100, "total_pages": 3, "total_lines": 214,
"show_all": false, "search": ""
}
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"}
Combined server info​
The same three payloads above (info/plan/ports), in one call — what the /server/info panel page itself fetches, bundled.
GET /api/server/info
{
"info": {"system": "Linux", "node": "openpanel", "release": "5.14.0-...", "ip": "203.0.113.10", "uptime": "11 days", "load_avg": "2.37, 2.72, 2.75"},
"plan": {"context": "myuser", "plan_webserver": "nginx", "plan_mysql": "mariadb", "plan_cpu_limit": "4", "plan_ram_limit": "6g"},
"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
Backups​
The configurable backup destination flow (S3, WebDAV, SSH, Azure, Dropbox) — separate from the one-click Backup Wizard above. values holds the active destination's own credential keys (varies per destination); settings holds the shared schedule/retention/notification keys.
Get status summary​
GET /api/backups
{"target": "s3", "service_active": true, "values": {"AWS_ACCESS_KEY_ID": "..."}, "settings": {"BACKUP_RETENTION_DAYS": "7"}}
Get / update settings​
GET /api/backups/settings
PUT /api/backups/settings
Content-Type: application/json
{"values": {"AWS_ACCESS_KEY_ID": "AKIA..."}, "settings": {"BACKUP_RETENTION_DAYS": "7"}}
Get / switch the active destination​
GET /api/backups/destination
{"active": "s3", "targets": ["s3", "webdav", "ssh", "azure", "dropbox"]}
PUT /api/backups/destination
Content-Type: application/json
{"target": "s3"}
List backups at the destination​
Served from a cached index; the panel UI reindexes it periodically against the remote destination.
GET /api/backups/list
[{"name": "myuser-2026-01-15T12-00-00.tar.gz", "size": "45.2 MB", "mtime": "2026-01-15 12:00:00"}]
Restore from a backup​
restore_target is all (files + databases), database (a single database — set database to its name), or files (files only).
POST /api/backups/restore
Content-Type: application/json
{"backup_file": "myuser-2026-01-15T12-00-00.tar.gz", "restore_target": "all", "database": ""}
{"success": true, "message": "Full restore completed."}
Download a backup​
Streams the archive from the configured destination.
POST /api/backups/download
Content-Type: application/json
{"backup_file": "myuser-2026-01-15T12-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​
Omit the path segment (GET /api/files) to list the docroot root.
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 favicon​
Redirects (302) to the site's favicon — the Google favicon service, or a configured favicon proxy.
GET /api/sites/example.com/favicon
Get / refresh screenshot​
GET returns a cached screenshot (fetching and caching one first if none exists yet); POST forces a re-fetch.
GET /api/sites/example.com/screenshot
POST /api/sites/example.com/screenshot
Response is the image itself (image/png).
Get database size​
Either a WordPress install's on-disk size (wp db size, needs domain + docroot) or a raw database's size (needs database).
GET /api/sites/database-size?domain=example.com&docroot=/var/www/html/example.com
{"size": "45.2 MB"}
Re-run package install​
Re-runs pip install/npm install/pnpm install for an already-created Node.js/Python app (distinct from POST /api/nodejs/install//api/python/install, which create the app).
POST /api/sites/example.com/packages/npm
{"message": "NPM packages installed successfully.", "output": "..."}
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_"}
}
Site-manager WP-CLI passthrough​
A separate, smaller action set from POST /api/wp-cli/<action> below — scoped to the site-manager single-page UI's general/debug/update-preferences panels.
GET /api/sites/wp-cli/site_info?website=example.com&docroot=/var/www/html/example.com
{"success": true, "site_url": "https://example.com", "home_url": "https://example.com", "site_name": "My Blog", "tagline": "Just another WordPress site", "admin_email": "[email protected]"}
action: site_info, update_debug, update_site_information, update_now, update_update_preferences, debug_info, update_info. website (or domain) and docroot are always required; update_debug/update_site_information/update_update_preferences take their values as additional query parameters (e.g. &WP_DEBUG=true, &siteurl=https://example.com).
Drupal​
Deliberately simpler than the WordPress API below: install, clone, update, cache rebuild, and uninstall — no listing endpoint, no filesystem scan, no hardening rules, no general drush passthrough. MySQL/MariaDB only. Installed sites still show up in the general Site Manager (GET /api/sites).
Install Drupal​
Streams newline-delimited JSON progress events as the install runs (composer create-project drupal/recommended-project, composer require drush/drush, database setup, drush site:install).
POST /api/drupal/install
Content-Type: application/json
{
"domain_id": "42",
"site_name": "My Drupal Site",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"drupal_version": ""
}
{"status": "Creating Composer project drupal/recommended-project"}
{"status": "Requiring drush/drush"}
{"status": "Creating database drupal_a1b2c3 and user d4e5f6g7h8"}
{"status": "Running drush site:install"}
{"status": "Drupal installation completed!"}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, admin username, latest version).
Clone Drupal​
Copies the site's files and database to a new domain (or subdirectory). target_domain and source_db are required; everything else falls back to a generated value.
POST /api/drupal/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"source_db": "drupal_a1b2c3",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"drupal_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "drupal_d4e5f6"}
Update Drupal​
Updates Drupal to the latest version via drush. Streams newline-delimited JSON progress events.
POST /api/drupal/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: openpanel_php83"}
{"status": "Running composer update"}
{"status": "Running database updates (drush updatedb)"}
{"status": "Update completed!", "version": "11.2.5"}
Clear Drupal cache​
Rebuilds the Drupal cache via drush cache:rebuild.
POST /api/drupal/sites/42/cache
{"message": "Cache rebuilt successfully."}
Uninstall Drupal​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/drupal/sites/42
Flarum​
Install, clone, update, cache clear, and uninstall. Installed sites still show up in the general Site Manager (GET /api/sites).
Install Flarum​
Streams newline-delimited JSON progress events as the install runs (composer create-project flarum/flarum, database setup, flarum install).
POST /api/flarum/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"site_name": "My Flarum Forum",
"flarum_version": "",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"db_name": "",
"db_user": "",
"db_password": ""
}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, admin username, latest version).
Clone Flarum​
Copies the site's files and database to a new domain (or subdirectory). target_domain and source_db are required; everything else falls back to a generated value.
POST /api/flarum/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"source_db": "flarum_a1b2c3",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"flarum_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "flarum_d4e5f6"}
Update Flarum​
Updates Flarum to the latest version via composer require + flarum migrate. Streams newline-delimited JSON progress events.
POST /api/flarum/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: openpanel_php83"}
{"status": "Running composer require (flarum/core:^2.0)"}
{"status": "Running database migrations (flarum migrate)"}
{"status": "Clearing cache"}
{"status": "Update completed!", "version": "2.1.0"}
Clear Flarum cache​
POST /api/flarum/sites/42/cache
{"message": "Cache cleared successfully."}
Uninstall Flarum​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/flarum/sites/42
Matomo​
Install, clone, update, cache clear, and uninstall — no listing or hardening endpoints. Installed sites still show up in the general Site Manager (GET /api/sites).
Install Matomo​
Streams newline-delimited JSON progress events as the install runs.
POST /api/matomo/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"matomo_version": "",
"admin_login": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"db_name": "",
"db_user": "",
"db_password": ""
}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, latest version).
Clone Matomo​
Copies the site's files and database to a new domain (or subdirectory). target_domain and source_db are required; everything else falls back to a generated value.
POST /api/matomo/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"source_db": "matomo_a1b2c3",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"matomo_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "matomo_d4e5f6"}
Update Matomo​
Updates Matomo to the latest version by downloading the release archive and running console core:update. Streams newline-delimited JSON progress events.
POST /api/matomo/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: openpanel_php83"}
{"status": "Downloading https://builds.matomo.org/matomo-5.3.1.zip"}
{"status": "Replacing core files (preserving config)"}
{"status": "Running database updates (console core:update)"}
{"status": "Update completed!", "version": "5.3.1"}
Clear Matomo cache​
POST /api/matomo/sites/42/cache
{"message": "Cache cleared successfully."}
Uninstall Matomo​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/matomo/sites/42
MediaWiki​
Install, clone, update, and uninstall — no cache endpoint. Installed sites still show up in the general Site Manager (GET /api/sites).
Install MediaWiki​
Streams newline-delimited JSON progress events as the install runs.
POST /api/mediawiki/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"mediawiki_version": "",
"site_name": "My Wiki",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"db_name": "",
"db_user": "",
"db_password": ""
}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, latest version).
Clone MediaWiki​
Copies the site's files and database to a new domain (or subdirectory). source_db is derived automatically from the source site's LocalSettings.php, so only target_domain is required.
POST /api/mediawiki/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"mediawiki_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "mediawiki_d4e5f6"}
Update MediaWiki​
Updates MediaWiki to the latest version and runs maintenance/update.php. Streams newline-delimited JSON progress events.
POST /api/mediawiki/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: openpanel_php83"}
{"status": "Downloading https://releases.wikimedia.org/mediawiki/1.42/mediawiki-1.42.3.tar.gz"}
{"status": "Replacing core files (preserving LocalSettings.php and images/)"}
{"status": "Running maintenance/update.php"}
{"status": "Update completed!", "version": "1.42.3"}
Uninstall MediaWiki​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/mediawiki/sites/42
DokuWiki​
Install, clone, update, and uninstall — flat-file CMS, so no database fields anywhere and no cache endpoint. Installed sites still show up in the general Site Manager (GET /api/sites).
Install DokuWiki​
Streams newline-delimited JSON progress events as the install runs (download, extraction, admin account setup).
POST /api/dokuwiki/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"admin_email": "[email protected]",
"admin_user": "admin",
"admin_password": "changeme",
"admin_full_name": "Admin User",
"site_title": "My DokuWiki"
}
domain_id is required; everything else falls back to a generated value.
Clone DokuWiki​
Copies the site's files to a new domain (or subdirectory). No database fields — only target_domain is required.
POST /api/dokuwiki/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"subdirectory": "",
"admin_email": "[email protected]"
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": ""}
Update DokuWiki​
Downloads and applies the latest DokuWiki release. Streams newline-delimited JSON progress events.
POST /api/dokuwiki/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: openpanel_php83"}
{"status": "Downloading latest DokuWiki release"}
{"status": "Applying update: 2023-04-04a -> 2024-02-16b"}
{"status": "Update completed!", "version": "2024-02-16b"}
If the site is already on the latest release, the stream instead emits {"status": "Already running the latest version (2024-02-16b)", "version": "2024-02-16b"} and stops.
Uninstall DokuWiki​
Deletes every file in the docroot and removes it from Site Manager.
DELETE /api/dokuwiki/sites/42
Moodle​
Install, clone, update, cache purge, and uninstall. Installed sites still show up in the general Site Manager (GET /api/sites).
Install Moodle​
Streams newline-delimited JSON progress events as the install runs.
POST /api/moodle/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"moodle_version": "",
"site_name": "My Moodle Site",
"site_shortname": "",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"db_name": "",
"db_user": "",
"db_password": ""
}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, latest version).
Clone Moodle​
Copies the site's files and database to a new domain (or subdirectory). target_domain and source_db are required; everything else falls back to a generated value. Unlike most other CMS clone endpoints, there's no source_folder field — Moodle's docroot is a symlink derived from the site slug.
POST /api/moodle/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"source_db": "moodle_a1b2c3",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"moodle_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "moodle_d4e5f6"}
Update Moodle​
Updates Moodle to the latest version and runs admin/cli/upgrade.php. Streams newline-delimited JSON progress events.
POST /api/moodle/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: openpanel_php83"}
{"status": "Downloading https://download.moodle.org/download.php/direct/stable405/moodle-4.5.3.tgz"}
{"status": "Enabling maintenance mode"}
{"status": "Replacing core files (preserving config.php)"}
{"status": "Running admin/cli/upgrade.php"}
{"status": "Disabling maintenance mode"}
{"status": "Update completed!", "version": "4.5.3"}
Clear Moodle cache​
POST /api/moodle/sites/42/cache
{"message": "Caches purged successfully."}
Uninstall Moodle​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/moodle/sites/42
Joomla​
Deliberately simpler than the WordPress API below: install, clone, cache clear, and uninstall — no listing endpoint, no filesystem scan, no hardening rules. MySQL/MariaDB only. Installed sites still show up in the general Site Manager (GET /api/sites).
Install Joomla​
Streams newline-delimited JSON progress events as the install runs (archive download from GitHub, extraction, database setup, Joomla's own installation/joomla.php install CLI installer).
POST /api/joomla/install
Content-Type: application/json
{
"domain_id": "42",
"site_name": "My Joomla Site",
"admin_name": "Administrator",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"joomla_version": ""
}
{"status": "Downloading https://github.com/joomla/joomla-cms/releases/download/6.1.2/Joomla_6.1.2-Stable-Full_Package.tar.gz"}
{"status": "Extracting files to /var/www/html/example.com"}
{"status": "Creating database joomla_a1b2c3 and user d4e5f6g7h8"}
{"status": "Running Joomla CLI installer"}
{"status": "Joomla installation completed!"}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, admin username, latest version).
Clone a Joomla site​
POST /api/joomla/clone
Content-Type: application/json
{
"source_domain": "example.com",
"target_domain": "clone.example.com",
"source_db": "joomla_a1b2c3",
"source_folder": "",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": ""
}
Fields left blank fall back to generated values, same as install.
Clear Joomla cache​
POST /api/joomla/sites/42/cache
{"message": "Cache cleared successfully."}
Uninstall Joomla​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/joomla/sites/42
OpenCart​
Deliberately simpler than the WordPress API below: install, clone, cache clear, and uninstall — no update endpoint, no listing endpoint, no filesystem scan, no hardening rules. MySQL/MariaDB only. Installed sites still show up in the general Site Manager (GET /api/sites).
Install OpenCart​
Streams newline-delimited JSON progress events as the install runs (archive download from GitHub, extraction, database setup, OpenCart's own install/cli_install.php install CLI installer).
POST /api/opencart/install
Content-Type: application/json
{
"domain_id": "42",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"opencart_version": ""
}
{"status": "Downloading https://github.com/opencart/opencart/releases/download/4.1.0.4/opencart-4.1.0.4.zip"}
{"status": "Extracting files to /var/www/html/example.com"}
{"status": "Creating database opencart_a1b2c3 and user d4e5f6g7h8"}
{"status": "Running OpenCart CLI installer"}
{"status": "OpenCart installation completed!"}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, admin username, latest version).
Clone OpenCart​
Copies the site's files and database to a new domain (or subdirectory). source_db is derived automatically from the source site's config.php, so only target_domain is required.
POST /api/opencart/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"opencart_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "opencart_d4e5f6"}
Clear OpenCart cache​
POST /api/opencart/sites/42/cache
{"message": "Cache cleared successfully."}
Uninstall OpenCart​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/opencart/sites/42
Nextcloud​
Deliberately simpler than the WordPress API below: install, clone, update, cache clear, and uninstall — no listing endpoint, no filesystem scan, no hardening rules. MySQL/MariaDB only. Installed sites still show up in the general Site Manager (GET /api/sites).
Install Nextcloud​
Streams newline-delimited JSON progress events as the install runs (archive download from download.nextcloud.com, extraction, database setup, Nextcloud's own occ maintenance:install CLI installer, trusted domain configuration).
POST /api/nextcloud/install
Content-Type: application/json
{
"domain_id": "42",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"nextcloud_version": ""
}
{"status": "Downloading https://download.nextcloud.com/server/releases/nextcloud-34.0.3.zip"}
{"status": "Extracting files to /var/www/html/example.com"}
{"status": "Creating database nextcloud_a1b2c3 and user d4e5f6g7h8"}
{"status": "Running Nextcloud CLI installer (occ maintenance:install)"}
{"status": "Configuring trusted domain and site URL"}
{"status": "Nextcloud installation completed!"}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, admin username, latest version).
Clone Nextcloud​
Copies the site's files and database to a new domain (or subdirectory). target_domain and source_db are required; everything else falls back to a generated value.
POST /api/nextcloud/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"source_db": "nextcloud_a1b2c3",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"nextcloud_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "nextcloud_d4e5f6"}
Update Nextcloud​
Updates Nextcloud to the latest version via occ upgrade. Streams newline-delimited JSON progress events.
POST /api/nextcloud/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: openpanel_php83"}
{"status": "Downloading https://download.nextcloud.com/server/releases/nextcloud-34.0.3.zip"}
{"status": "Enabling maintenance mode"}
{"status": "Replacing core files (preserving config and data)"}
{"status": "Running occ upgrade"}
{"status": "Disabling maintenance mode"}
{"status": "Update completed!", "version": "34.0.3"}
Clear Nextcloud cache​
POST /api/nextcloud/sites/42/cache
{"message": "Cache cleared successfully."}
Uninstall Nextcloud​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/nextcloud/sites/42
PrestaShop​
Deliberately simpler than the WordPress API below: install, clone, cache clear, and uninstall — no update endpoint, no listing endpoint, no filesystem scan, no hardening rules. MySQL/MariaDB only. Installed sites still show up in the general Site Manager (GET /api/sites).
Install PrestaShop​
Streams newline-delimited JSON progress events as the install runs (archive download from GitHub, extraction, database setup, PrestaShop's own install/index_cli.php CLI installer, admin directory rename).
POST /api/prestashop/install
Content-Type: application/json
{
"domain_id": "42",
"admin_firstname": "Admin",
"admin_lastname": "User",
"admin_password": "changeme",
"admin_email": "[email protected]",
"prestashop_version": ""
}
{"status": "Downloading https://github.com/PrestaShop/PrestaShop/releases/download/8.2.7/prestashop_8.2.7.zip"}
{"status": "Extracting files to /var/www/html/example.com"}
{"status": "Creating database prestashop_a1b2c3 and user d4e5f6g7h8"}
{"status": "Running PrestaShop CLI installer"}
{"status": "Securing admin directory"}
{"status": "PrestaShop installation completed!"}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, "Admin"/"User" names, latest version with a downloadable release asset).
Clone PrestaShop​
Copies the site's files and database to a new domain (or subdirectory). source_db is derived automatically from the source site's app/config/parameters.php, so only target_domain is required.
POST /api/prestashop/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"prestashop_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "prestashop_d4e5f6"}
Clear PrestaShop cache​
POST /api/prestashop/sites/42/cache
{"message": "Cache cleared successfully."}
Uninstall PrestaShop​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/prestashop/sites/42
SofaWiki​
Install, clone, and uninstall only — flat-file CMS, so no database fields anywhere and no update or cache endpoints. Installed sites still show up in the general Site Manager (GET /api/sites).
Install SofaWiki​
Streams newline-delimited JSON progress events as the install runs.
POST /api/sofawiki/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"admin_email": "[email protected]"
}
domain_id is required; everything else falls back to a generated value.
Clone SofaWiki​
Copies the site's files to a new domain (or subdirectory). No database fields — only target_domain is required.
POST /api/sofawiki/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"subdirectory": "",
"admin_email": "[email protected]"
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": ""}
Uninstall SofaWiki​
Deletes every file in the docroot and removes it from Site Manager.
DELETE /api/sofawiki/sites/42
phpBB​
Install, clone, and uninstall only — no update or cache endpoints. MySQL/MariaDB only. Installed sites still show up in the general Site Manager (GET /api/sites).
Install phpBB​
Streams newline-delimited JSON progress events as the install runs (archive download, extraction, database setup, CLI install).
POST /api/phpbb/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"board_name": "My Board",
"board_description": "A phpBB forum",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]"
}
domain_id is required; everything else falls back to a generated value (random DB name/user/password, admin username, latest version).
Clone phpBB​
Copies the site's files and database to a new domain (or subdirectory). source_db is derived automatically from the source site's config.php, so only target_domain is required. Note the version field is named version, not phpbb_version.
POST /api/phpbb/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"subdirectory": "",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "phpbb_d4e5f6"}
Uninstall phpBB​
Drops the database and user, deletes every file in the docroot, and removes it from Site Manager.
DELETE /api/phpbb/sites/42
TinyPhotoGallery​
Install and uninstall only — no clone, update, or cache endpoints. No database, no admin account, no versioning. Installed sites still show up in the general Site Manager (GET /api/sites).
Install TinyPhotoGallery​
Downloads a single index.php file and creates an empty photos/ directory next to it. Streams newline-delimited JSON progress events as the install runs.
POST /api/tinyphotogallery/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": ""
}
domain_id is required; subdirectory is optional (leave empty to install at the domain root).
{"status": "TinyPhotoGallery installation completed!"}
Uninstall TinyPhotoGallery​
Deletes every file in the docroot and removes it from Site Manager.
DELETE /api/tinyphotogallery/sites/42
TinyFileManager​
Install and uninstall only — no clone, update, or cache endpoints. No database, no versioning. Installed sites still show up in the general Site Manager (GET /api/sites).
Install TinyFileManager​
Downloads a single tinyfilemanager.php file and writes the given admin username/password (bcrypt-hashed, inside the container so it matches that container's own PHP build) directly into its $auth_users array. Streams newline-delimited JSON progress events as the install runs.
POST /api/tinyfilemanager/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"admin_username": "admin",
"admin_password": "changeme"
}
domain_id, admin_username, and admin_password are required.
{"status": "TinyFileManager installation completed!", "admin_user": "admin"}
Uninstall TinyFileManager​
Deletes every file in the docroot and removes it from Site Manager.
DELETE /api/tinyfilemanager/sites/42
OJS​
Install, clone, update, cache, and uninstall. MySQL/MariaDB only, requires PHP 8.2+. Installed sites still show up in the general Site Manager (GET /api/sites).
Install OJS​
Downloads the official Open Journal Systems release package directly from PKP's own release server (not a GitHub archive — the repo uses submodules a plain archive download would omit) and runs its CLI installer non-interactively. The required PHP ftp extension is installed automatically first if the PHP container doesn't already have it. Streams newline-delimited JSON progress events as the install runs.
POST /api/ojs/install
Content-Type: application/json
{
"domain_id": "42",
"subdirectory": "",
"ojs_version": "",
"admin_username": "admin",
"admin_password": "changeme",
"admin_email": "[email protected]",
"db_name": "",
"db_user": "",
"db_password": ""
}
domain_id is required; everything else falls back to a generated value (ojs_version empty resolves to the latest release).
{"status": "Checking if existing installation processes are running.."}
{"status": "Starting PHP container: php-fpm-8.5"}
{"status": "Downloading https://pkp.sfu.ca/ojs/download/ojs-3.5.0-5.tar.gz"}
{"status": "Creating database ojs_x52piq and user avalnaow32"}
{"status": "Ensuring PHP 'ftp' extension is installed"}
{"status": "Running OJS CLI installer (tools/install.php)"}
{"status": "OJS installation completed!"}
Clone OJS​
Copies the site's files and database to a new domain (or subdirectory). target_domain and source_db are required; everything else falls back to a generated value. Like Moodle, there's no source_folder field — OJS's docroot is a symlink derived from the site slug.
POST /api/ojs/sites/42/clone
Content-Type: application/json
{
"target_domain": "clone.example.com",
"subdirectory": "",
"source_db": "ojs_source",
"target_db": "",
"target_db_user": "",
"target_db_user_password": "",
"admin_email": "[email protected]",
"ojs_version": ""
}
{"status": "success", "source": "example.com", "target": "clone.example.com", "source_path": "/var/www/html/example.com", "target_path": "/var/www/html/clone.example.com", "target_db": "ojs_d4e5f6"}
Update OJS​
Updates OJS to the latest version and runs its own tools/upgrade.php upgrade. Streams newline-delimited JSON progress events.
POST /api/ojs/sites/42/update
{"status": "Checking if existing installation processes are running.."}
{"status": "Downloading https://pkp.sfu.ca/ojs/download/ojs-3.5.0-5.tar.gz"}
{"status": "Running OJS upgrade (tools/upgrade.php upgrade)"}
{"status": "Update completed!", "version": "3.5.0-5"}
Clear OJS cache​
Clears OJS's cache/ directory.
POST /api/ojs/sites/42/cache
{"message": "Caches purged successfully!"}
Uninstall OJS​
Drops the database and user, deletes every file, removes the scheduled-tasks cron job, and removes it from Site Manager.
DELETE /api/ojs/sites/42
WordPress​
List all WordPress installations​
GET /api/wordpress
Install WordPress​
Streams newline-delimited JSON progress events as the install runs (download, extract, DB setup, wp core install, plugin/theme sets) — same install flow as the panel's own install page, just fed from this JSON body instead of a form post.
POST /api/wordpress/install
Content-Type: application/json
{
"domain_id": "42",
"admin_email": "[email protected]",
"website_name": "My Blog",
"admin_username": "admin",
"admin_password": "changeme",
"wordpress_version": "latest",
"db_prefix": "wp_"
}
{"status": "Downloading https://wordpress.org/wordpress-latest.tar.gz"}
{"status": "Extracting files to /var/www/html/example.com"}
{"status": "Creating database wp_a1b2c3 and user wp_d4e5f6"}
{"status": "Importing WordPress tables in the database"}
{"status": "WordPress installation completed!"}
domain_id, admin_username and admin_password are required; everything else falls back to a generated value (random DB name/user/password, wp_ prefix, latest version).
Clone a WordPress site​
POST /api/wordpress/clone
Content-Type: application/json
{
"source_domain": "example.com",
"target_domain": "clone.example.com",
"source_db": "wp_source",
"source_folder": ""
}
source_domain, target_domain, source_db and source_folder are required; target_db/target_db_user/target_db_user_password default to generated values if omitted.
Scan for untracked installations​
Finds WordPress installations on disk that aren't yet registered in WP Manager and adds them. Distinct from POST /api/wordpress/reload below, which only refreshes info for installations already tracked.
GET /api/wordpress/scan
{"message": "Scan completed", "installations": [{"config_file": "example.com/wp-config.php", "domain": "example.com", "admin_email": "[email protected]", "version": "6.7"}], "count": 1}
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, "sitebuilder": 0, "node": 0, "python": 0},
"technologies": ["wordpress", "sitebuilder", "node", "python"],
"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"}
Mark onboarding tour as completed​
POST /api/dashboard/tour/complete
{"ok": true}
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", "..."}]}
Search​
Backs the sidebar's search box. what selects the entity type; some types are gated behind their own feature flag or an Enterprise license (403 if not available).
GET /api/search/domains
GET /api/search/mysql_databases
GET /api/search/files?q=index&folder=&ext=.php
[{"name": "example.com", "link": "/domains"}]
Supported what values: files, folders, features, websites, mysql_databases, mysql_users, postgresql_databases, postgresql_users, domains, emails, ftp, containers, services, crons.
API Introspection​
Lists every /api/* route currently registered on the server (i.e. actually available given the running build and enabled modules), each with its HTTP method(s). Useful for discovering what's available without relying on this document.
GET /api/endpoints
{
"endpoints": [
{"path": "/api/account", "methods": ["GET"]},
{"path": "/api/mysql/databases", "methods": ["GET", "POST"]}
],
"total": 317
}
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/PIDs 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
- PHP App Manager
- Install a PHP app
- Re-run composer install / update
- Get Composer run log
- Remove a PHP app
- Account
- Get account info
- Update account
- List active sessions
- Terminate a session
- List / add / remove favorites
- Get / set preferred UI language
- Get login history
- Get activity log
- 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
- Combined server info
- 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
- Backups
- Get status summary
- Get / update settings
- Get / switch the active destination
- List backups at the destination
- Restore from 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 favicon
- Get / refresh screenshot
- Get database size
- Re-run package install
- Get WordPress site info
- Site-manager WP-CLI passthrough
- Drupal
- Install Drupal
- Clone Drupal
- Update Drupal
- Clear Drupal cache
- Uninstall Drupal
- Flarum
- Install Flarum
- Clone Flarum
- Update Flarum
- Clear Flarum cache
- Uninstall Flarum
- Matomo
- Install Matomo
- Clone Matomo
- Update Matomo
- Clear Matomo cache
- Uninstall Matomo
- MediaWiki
- Install MediaWiki
- Clone MediaWiki
- Update MediaWiki
- Uninstall MediaWiki
- DokuWiki
- Install DokuWiki
- Clone DokuWiki
- Update DokuWiki
- Uninstall DokuWiki
- Moodle
- Install Moodle
- Clone Moodle
- Update Moodle
- Clear Moodle cache
- Uninstall Moodle
- Joomla
- Install Joomla
- Clone a Joomla site
- Clear Joomla cache
- Uninstall Joomla
- OpenCart
- Install OpenCart
- Clone OpenCart
- Clear OpenCart cache
- Uninstall OpenCart
- Nextcloud
- Install Nextcloud
- Clone Nextcloud
- Update Nextcloud
- Clear Nextcloud cache
- Uninstall Nextcloud
- PrestaShop
- Install PrestaShop
- Clone PrestaShop
- Clear PrestaShop cache
- Uninstall PrestaShop
- SofaWiki
- Install SofaWiki
- Clone SofaWiki
- Uninstall SofaWiki
- phpBB
- Install phpBB
- Clone phpBB
- Uninstall phpBB
- TinyPhotoGallery
- Install TinyPhotoGallery
- Uninstall TinyPhotoGallery
- TinyFileManager
- Install TinyFileManager
- Uninstall TinyFileManager
- OJS
- Install OJS
- Clone OJS
- Update OJS
- Clear OJS cache
- Uninstall OJS
- WordPress
- List all WordPress installations
- Install WordPress
- Clone a WordPress site
- Scan for untracked 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
- Mark onboarding tour as completed
- Plugins
- List installed plugins
- Search
- API Introspection
- Error Responses