HiTechCloud API
HiTechCloud 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
Every request except the login endpoint takes a Bearer token.
The API is an Enterprise feature. Both the blanket api feature flag and
the specific feature flag for the endpoint you are calling must be granted by
the account's hosting plan.
POST /api/login
The one route that takes a password. Send JSON:
| Field | Required | What it is |
|---|---|---|
username | yes | The panel username. |
password | yes | Its password. |
twofa_code | only when the account has 2FA | A current TOTP code. |
curl -s -X POST https://panel.example.com/api/login \
-H "Content-Type: application/json" \
-d '{"username": "stefan", "password": "yourpassword"}'
On success — 200:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user_id": 1,
"expires_in": 3600
}
The token is an HS256 JWT whose subject is the numeric user id, valid for
one hour (expires_in is in seconds). There is no refresh token; log in
again.
A successful login is a real login: it is written to the account's login
history and activity log as logged in via user API, and it fires the account's
login notification if that preference is on.
If the account has two-factor authentication and no twofa_code was sent —
401:
{"twofa_required": true, "user_id": 1}
Retry with twofa_code in the body. A code that does not validate is 401
with {"error": "invalid_2fa"}.
Failures. A missing username or password is 400. Everything else is
401 with a stable machine-readable error code:
| Code | Meaning |
|---|---|
invalid_password | The password is wrong. |
unknown_user | No such account. |
suspended | The account is suspended. |
db_error | The panel could not reach its database. |
login_failed | Anything else. |
token_generation_failed and internal_error are 500.
Rate limiting. Login attempts are counted per client IP in a fixed
one-minute window. The limit is the panel's login_ratelimit setting, 5 per
minute by default. Over it, the request never reaches the password check and
the answer is 429:
{"error":"rate_limit_exceeded","message":"Too many requests. Please try again later."}
Each throttled attempt is written to /var/log/hitechcloud/user/failed_login.log
and counted against that IP. Once the count passes login_blocklimit (20 by
default), the IP is given a one-hour temporary ban in ConfigServer Firewall, if
CSF is installed. A successful login clears the IP's counter. The counters live
in the panel process, so restarting the panel clears them.
POST /api/auth/ticket and POST /api/auth/ticket/redeem
One-time sign-in tickets let a signed-in app open another HiTechCloud app
already signed in. The first call takes your Bearer token and returns a ticket
that lives 30 seconds (60 at most) and works once; the second takes the ticket
and no token, and answers like POST /api/login. The full contract is in
App sign-in tickets.
Using the token
curl https://panel.example.com/api/account \
-H "Authorization: Bearer eyJhbGciOi..."
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
file=@dump.sql
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
file=@dump.sql
{"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/hitechcloud/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": [
{"address": "info@example.com", "quota_limit": "1024", "quota_used": "12"}
]
}
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/info@example.com
{
"address": "info@example.com",
"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/info@example.com
Content-Type: application/json
{"password": "newpass456"}
Delete mailbox
DELETE /api/emails/info@example.com
Open a mailbox in the Mail app
POST /api/emails/mail-ticket/user@example.com
Issues a one-time sign-in ticket (30 seconds, single use) for one of your mailboxes, for the panel apps' "Open in Mail" action. It takes no body.
{
"ticket": "v1.dXNlckBleGFtcGxlLmNvbQ.1760000030000.nonce.hexsig",
"expires_at": 1760000030,
"expires_in": 30,
"server": "mail.example.com",
"url": "hitechcloud-mail://open?server=mail.example.com&ticket=v1....&user=user%40example.com"
}
The full contract is in Open a mailbox in the Mail app.
Download mail client configuration
config_type is one of thunderbird, outlook, apple.
GET /api/emails/configuration/thunderbird/info@example.com?ssl=true
List aliases
GET /api/emails/aliases
{
"aliases": [
{"source": "alias@example.com", "targets": ["real@example.com"]}
]
}
Create alias
POST /api/emails/aliases
Content-Type: application/json
{"username": "alias", "domain": "example.com", "target": "real@example.com"}
Get targets for one alias
GET /api/emails/aliases/alias@example.com
{"source": "alias@example.com", "targets": ["real@example.com"]}
Add a target to an existing alias
POST /api/emails/aliases/alias@example.com
Content-Type: application/json
{"target": "second@example.com"}
Delete an alias target (or the whole alias)
DELETE /api/emails/aliases/alias@example.com
Content-Type: application/json
{"target": "real@example.com"}
Delete every target for the alias:
DELETE /api/emails/aliases/alias@example.com
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": "catchall@example.com"}
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/user@example.com
{
"email": "user@example.com",
"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/user@example.com
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
file=@mailboxes.csv
{"import_token": "...", "valid_users": [{"email": "info@example.com", "..."}], "invalid_users": []}
POST /api/emails/import/confirm
Content-Type: application/json
{"import_token": "..."}
{"message": "...", "created": 3, "results": [{"email": "info@example.com", "status": "created"}]}
FTP
List FTP accounts
GET /api/ftp
{
"accounts": [{"username": "ftpuser@example.com", "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/ftpuser@example.com
Change FTP password
PATCH /api/ftp/ftpuser@example.com/password
Content-Type: application/json
{"password": "newftppass"}
Change FTP home path
PATCH /api/ftp/ftpuser@example.com/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/ftpuser@example.com
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",
"email": "stefan@example.com",
"plan_id": 1,
"context": "stefan",
"permit_username_change": false
}
Update account
PATCH /api/account
Content-Type: application/json
{"email": "new@example.com"}
# 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=HiTechCloud"}
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. They describe what an account needs in order to use its hosting - the hostname and IP to point clients at, the distribution and shared software versions its code runs against, and the PHP versions a website can be assigned. The machine's own state (load, uptime, kernel, CPU) is the administrator's and is not reported here.
General hosting info
GET /api/hosting/info
{
"node": "hitechcloud", "ip": "203.0.113.10", "os": "Ubuntu 24.04.3 LTS",
"php_versions": ["8.4", "8.3", "8.2"],
"software": [{"name": "Web Server", "version": "1.24.0"}, {"name": "Database", "version": "10.11.8"}]
}
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": {"node": "hitechcloud", "ip": "203.0.113.10", "os": "Ubuntu 24.04.3 LTS", "php_versions": ["8.4", "8.3"], "software": []},
"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}
List past scans
Scans started from the Malware Scanner page and through this API, and the
scheduled scan's last run, newest first. source is panel, api or
schedule; status is complete, stopped (the page was closed during
the scan) or failed. Each entry lists at most 100 infected files; infected
is the full count.
GET /api/malware-scanner/logs
{
"count": 1,
"logs": [
{
"source": "api",
"started_at": "2026-10-03 10:00:00",
"finished_at": "2026-10-03 10:01:12",
"directory": "/home/alice/public_html",
"scanned": 1234,
"infected": 1,
"status": "complete",
"message": "",
"files": [{"path": "wp-content/uploads/x.php", "signature": "Php.Webshell-1"}]
}
]
}
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
files=@photo.jpg
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": "admin@example.com"}
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": "admin@example.com",
"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": "admin@example.com",
"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: hitechcloud_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": "admin@example.com",
"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": "admin@example.com",
"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: hitechcloud_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": "admin@example.com",
"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": "admin@example.com",
"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: hitechcloud_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": "admin@example.com",
"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": "admin@example.com",
"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: hitechcloud_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": "admin@example.com",
"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": "admin@example.com"
}
{"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: hitechcloud_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": "admin@example.com",
"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": "admin@example.com",
"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 newest build of the site's own release line (a 4.5 site stays on 4.5) and runs admin/cli/upgrade.php. Add ?major=1 to move to the newest Moodle release line instead (for example 4.5 to 5.x); a major release can need a newer PHP version. The release is unpacked beside the site and checked before anything changes: if it is not newer than the installed build, the stream ends with Already running the latest version …, and if the domain's PHP version is older than the release requires, it ends with an error. If upgrade.php stops before it changes the database, the previous release is put back. Streams newline-delimited JSON progress events.
POST /api/moodle/sites/42/update
POST /api/moodle/sites/42/update?major=1
{"status": "Checking if existing installation processes are running.."}
{"status": "Preparing PHP 8.3"}
{"status": "Moodle 5.3.0 is a major upgrade: this update stays on Moodle 4.5 (ask for the major upgrade to move to 5.3.0)"}
{"status": "Downloading https://download.moodle.org/download.php/direct/stable405/moodle-latest-405.tgz"}
{"status": "Extracting the new release beside the site"}
{"status": "Copying config.php into the new release"}
{"status": "Enabling maintenance mode"}
{"status": "Replacing core files (preserving config.php)"}
{"status": "Setting files permissions and owner to 'alice'"}
{"status": "Running admin/cli/upgrade.php"}
{"status": "Disabling maintenance mode"}
{"status": "Update completed!", "version": "4.5.15+"}
When the site already runs the newest build of its line:
{"status": "Already running the latest version of Moodle 4.5 (4.5.15+)", "version": "4.5.15+"}
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": "admin@example.com",
"joomla_version": ""
}
{"status": "Downloading https://hitechcloud.org"}
{"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": "admin@example.com",
"opencart_version": ""
}
{"status": "Downloading https://hitechcloud.org"}
{"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": "admin@example.com",
"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": "admin@example.com",
"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": "admin@example.com",
"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: hitechcloud_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": "admin@example.com",
"prestashop_version": ""
}
{"status": "Downloading https://hitechcloud.org"}
{"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": "admin@example.com",
"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": "admin@example.com"
}
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": "admin@example.com"
}
{"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": "admin@example.com"
}
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": "admin@example.com",
"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": "admin@example.com",
"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": "admin@example.com",
"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": "admin@example.com",
"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.
POST /api/wordpress/scan
{"message": "Scan completed", "installations": [{"config_file": "example.com/wp-config.php", "domain": "example.com", "admin_email": "admin@example.com", "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://hitechcloud.org", "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/user@example.com
{
"email": "user@example.com",
"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
POST /api/loginPOST /api/auth/ticketandPOST /api/auth/ticket/redeem- Using the token
- 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
- Open a mailbox in the Mail app
- 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
- List past scans
- 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