HTTP API reference
The /api/v1 API manages hostings, backups, certificates and nodes from scripts. It is served by the panel itself. The hctl remote commands in the CLI reference call the same endpoints.
Authentication
Section titled “Authentication”Create a key under API keys in the sidebar (Administration). Tick the capabilities the key may use, optionally set Expires, Allowed addresses and Requests per minute, and click Create key. The panel shows the key once, starting with hyp_. Keep the key off the command line. Arguments of a running program are readable by other local users through /proc, so a key written as a curl argument leaks. Read the key into a shell variable without echo, and hand it to curl on standard input as a config line:
read -rs API_KEYhyp() { printf 'header = "Authorization: Bearer %s"\n' "$API_KEY" | curl -sS -K - "$@"; }printf is a shell builtin, so the key never appears in any process’s arguments. Every example below uses hyp in place of curl, and the first call is:
hyp https://panel.example.com/api/v1/mehyp takes standard input for the key, so it cannot be combined with -d @-. Use -d '<json>' or -d @file. A body that carries a password goes in a file readable only by you, with -d @file, so the password stays off the command line. That applies to admin_password on wp/install and basic_auth_password on vhost. See the example under WordPress.
- A key can never do more than the person who created it. Only an account with cluster-wide scope can own a key.
- A header without the
hyp_prefix, or withoutBearer, is treated as no key. A missing, expired, revoked or unknown key answers401. - Allowed addresses (empty means any): a request from another address answers
403withsource IP not allowed for this API key. Hyperion reads the client address from the connection, and fromX-Real-IPonly when the connection comes from the panel’s own nginx. - Requests per minute (0 means unlimited): over the limit answers
429with aRetry-After: 60header. - Session cookies are not accepted on
/api/v1; a request without a valid key answers401.
The OpenAPI document is public, no key needed: GET /api/v1/openapi.json. GET /api/v1/docs shows it in a viewer.
Errors
Section titled “Errors”Every error has the same JSON shape:
{ "error": { "code": "forbidden", "message": "API key lacks capability 'hosting_view'" } }| Status | code |
When |
|---|---|---|
| 400 | validation |
The body or a field is invalid. The message says which. |
| 401 | unauthorized |
missing or invalid API key |
| 403 | forbidden |
The key lacks the capability, may not manage this hosting, or the endpoint needs a cluster-wide key. |
| 404 | not_found |
no such hosting, no such job, or another missing resource. |
| 409 | conflict |
The request conflicts with existing state, for example a hosting for the domain already exists. |
| 429 | rate_limited |
rate limit exceeded for this API key |
| 502 | upstream_error |
The agent or a node did not answer, or answered with an error. |
Conventions
Section titled “Conventions”:idin a path is a hosting id or its domain, for example01J0000000000000000000000Aorexample.com.- A slow action answers
202with{ "job_id": "<id>", "status": "accepted" }. PollGET /api/v1/jobs/<job_id>untilstateisdone,failedorcancelled. - Endpoints that change a hosting need the capability in the table below. The key must also be allowed to manage that hosting. Keys are always cluster-wide, so the capability decides.
- A change made with a key is recorded in the audit log under the actor
apikey:<label>. php_versionis writtenv8_1,v8_2,v8_3orv8_4in JSON bodies and responses. The CLI accepts8.3and converts it.
Endpoints
Section titled “Endpoints”| Method and path | Capability | Answer |
|---|---|---|
GET /api/v1/me |
any valid key | 200 |
GET /api/v1/hostings |
hosting_view |
200 |
POST /api/v1/hostings |
hosting_create |
201 |
GET /api/v1/hostings/:id |
hosting_view |
200 |
DELETE /api/v1/hostings/:id |
hosting_delete |
202 job |
POST /api/v1/hostings/:id/suspend |
hosting_suspend |
200 |
POST /api/v1/hostings/:id/resume |
hosting_suspend |
200 |
PATCH /api/v1/hostings/:id/limits |
hosting_edit_config |
200 |
PATCH /api/v1/hostings/:id/php |
hosting_edit_config |
200 |
PATCH /api/v1/hostings/:id/vhost |
hosting_edit_config |
200 |
PATCH /api/v1/hostings/:id/expiry |
hosting_edit_config |
200 |
PATCH /api/v1/hostings/:id/quota |
hosting_edit_config |
200 |
POST /api/v1/hostings/:id/backup |
backup_run |
202 job |
GET /api/v1/hostings/:id/backups |
backup_run |
200 |
POST /api/v1/hostings/:id/restore |
backup_restore |
202 job |
POST /api/v1/hostings/:id/cert |
cert_manage |
202 job |
POST /api/v1/hostings/:id/wp/install |
wp_manage |
202 job |
POST /api/v1/hostings/:id/wp/core-update |
wp_manage |
202 job |
POST /api/v1/certs/renew-all |
cert_manage, cluster-wide key |
202 job |
GET /api/v1/nodes |
nodes_view |
200 |
GET /api/v1/jobs/:id |
cluster-wide key | 200 |
Identity
Section titled “Identity”GET /api/v1/me returns the key’s id, label, caps (the capability names the key holds) and scope_all.
hyp https://panel.example.com/api/v1/meList hostings
Section titled “List hostings”GET /api/v1/hostings returns { "items": [...], "next_cursor": "<id>" | null, "total": N }. Query parameters:
| Parameter | Meaning |
|---|---|
limit |
Page size, 1 to 200. Default 50. |
cursor |
The next_cursor of the previous page. |
state |
Only hostings in this state, for example active or suspended. |
node |
Only hostings on this node id. |
q |
Case-insensitive part of the domain. |
Items are ordered by id, which sorts by creation time. total counts every match, not only the page. Each item has id, domain, state, php_version, created_at, node_id, maintenance_mode, kind, wp_version, cert_not_after, cert_issuer, disk_bytes, last_backup_at and last_backup_ok.
hyp "https://panel.example.com/api/v1/hostings?state=active&limit=100"Get one hosting
Section titled “Get one hosting”GET /api/v1/hostings/:id returns the full detail: the summary fields plus aliases, system_user, root_dir, database, cert, vhost_options and wp_extras.
hyp https://panel.example.com/api/v1/hostings/example.comCreate a hosting
Section titled “Create a hosting”POST /api/v1/hostings creates the hosting before it answers. The body is JSON:
| Field | Meaning |
|---|---|
domain |
Required. A leading www. is removed. |
aliases |
List of extra domains. Default empty. |
php_version |
v8_1 to v8_4. |
database |
mariadb or postgres. Omit for no database. |
system_user |
Put the site under an existing system user. Only a cluster-wide key may set it; otherwise 400 validation. |
kind |
php (default) or reverse_proxy. |
proxy_upstream_url |
Required when kind is reverse_proxy. |
node |
Node id to create the site on. Empty, missing or local means the master. |
A domain that already exists on any node answers 409 conflict with a hosting for example.com already exists. The 201 body has id, system_user, root_dir, db (the database name, user and password, shown only in this answer), cert and wp.
hyp -X POST https://panel.example.com/api/v1/hostings \ -H "Content-Type: application/json" \ -d '{"domain":"new.example.com","php_version":"v8_3","database":"mariadb"}'Delete a hosting
Section titled “Delete a hosting”DELETE /api/v1/hostings/:id runs as a job. With no query parameters it removes the files, the database, the system user and the vhost, unless Trash is on (see Suspend, resume or delete a website). Then the site goes to Trash instead and is removed after the retention time.
hyp -X DELETE "https://panel.example.com/api/v1/hostings/old.example.com?keep_database=true"Suspend and resume
Section titled “Suspend and resume”Both answer at once. Suspend returns { "id": "...", "state": "suspended" } and resume returns "state": "active".
hyp -X POST https://panel.example.com/api/v1/hostings/example.com/suspendChange limits
Section titled “Change limits”PATCH /api/v1/hostings/:id/limits replaces the whole limits object, so send every required field. Read the current values from GET /api/v1/hostings/:id first.
| Field | Type |
|---|---|
php_memory_mb, php_max_exec_secs, php_max_children, php_max_requests, db_max_connections |
Required integers. |
over_bw_policy |
Required. suspend or throttle. Accepted and not enforced. |
disk_soft_bytes, disk_hard_bytes, inode_soft, inode_hard, bw_monthly_bytes, throttle_kbps |
Integer or null for no limit. bw_monthly_bytes and throttle_kbps are accepted and not enforced. |
hyp -X PATCH https://panel.example.com/api/v1/hostings/example.com/limits \ -H "Content-Type: application/json" \ -d '{"php_memory_mb":256,"php_max_exec_secs":60,"php_max_children":5,"php_max_requests":1000,"db_max_connections":25,"over_bw_policy":"suspend"}'Switch the PHP version
Section titled “Switch the PHP version”PATCH /api/v1/hostings/:id/php with { "version": "v8_3" } answers { "id": "...", "php_version": "v8_3" }. A hosting that is not a PHP site answers 400.
hyp -X PATCH https://panel.example.com/api/v1/hostings/example.com/php \ -H "Content-Type: application/json" \ -d '{"version":"v8_3"}'Change vhost options
Section titled “Change vhost options”PATCH /api/v1/hostings/:id/vhost takes the hosting’s vhost_options object, plus an optional basic_auth_password. Fields you leave out take their type default (false, 0 or an empty string), so start from the vhost_options that GET /api/v1/hostings/:id returned and change what you need. The fields include force_https, hsts_max_age, basic_auth_enabled, basic_auth_user, maintenance_mode, fastcgi_cache_enabled, fastcgi_cache_ttl, redirect_url, redirect_code, redirect_preserve_path, canonical_host, noindex, waf_level, waf_overrides, wp_admin_allowlist, blocked_bots and blocked_countries.
To send basic_auth_password, put the body in a file and use -d @file, as described under Authentication. The example below sends no password, so it stays inline.
Changing custom_nginx_snippet also needs the hosting_edit_nginx_raw capability. Without it the call answers 403 when the snippet differs from the stored one, and an unchanged snippet is accepted.
hyp -X PATCH https://panel.example.com/api/v1/hostings/example.com/vhost \ -H "Content-Type: application/json" \ -d '{"force_https":true,"hsts_max_age":31536000,"maintenance_mode":false}'Set the expiry
Section titled “Set the expiry”PATCH /api/v1/hostings/:id/expiry takes grace_days and warning_offsets_days (a comma list such as "30,7,1"), both required, and optionally expires_at (Unix seconds, or null for no expiry) and owner_email. A past expires_at is moved to the next anniversary of that day and month.
hyp -X PATCH https://panel.example.com/api/v1/hostings/example.com/expiry \ -H "Content-Type: application/json" \ -d '{"expires_at":1798675200,"owner_email":"owner@example.com","grace_days":7,"warning_offsets_days":"30,7,1"}'Set the quota
Section titled “Set the quota”PATCH /api/v1/hostings/:id/quota takes the required integers disk_soft_kib, disk_hard_kib, mem_limit_mib, bw_soft_mib and bw_hard_mib, and exceed_action: notify (the default when empty) or suspend. Only the two disk fields and exceed_action take effect. The other three are accepted and not enforced, so send them as 0. It answers with the stored quota.
hyp -X PATCH https://panel.example.com/api/v1/hostings/example.com/quota \ -H "Content-Type: application/json" \ -d '{"disk_soft_kib":1048576,"disk_hard_kib":2097152,"mem_limit_mib":0,"bw_soft_mib":0,"bw_hard_mib":0,"exceed_action":"notify"}'Back up, list backups, restore
Section titled “Back up, list backups, restore”POST /api/v1/hostings/:id/backup starts a backup and answers 202 with a job id. GET /api/v1/hostings/:id/backups returns { "items": [...] }, newest first, with limit from 1 to 500 (default 50). Each item has id, target, started_at, finished_at, state, archive_path, db_dump_path, bytes_total, error_message, sha256_hex, remote_blob_key, remote_state and remote_error.
hyp -X POST https://panel.example.com/api/v1/hostings/example.com/backupPOST /api/v1/hostings/:id/restore takes archive_path (from a backups item, required) and mode: files_and_db (default), db_only or files_only.
hyp -X POST https://panel.example.com/api/v1/hostings/example.com/restore \ -H "Content-Type: application/json" \ -d '{"archive_path":"<archive_path from the backups list>","mode":"files_only"}'Certificates
Section titled “Certificates”POST /api/v1/hostings/:id/cert issues a Let’s Encrypt certificate as a job. The body is optional. { "staging": true } uses the Let’s Encrypt staging environment, which browsers do not trust; the default is a production certificate. The DNS check always runs, so a domain that does not point at the node fails the job.
hyp -X POST https://panel.example.com/api/v1/hostings/example.com/certPOST /api/v1/certs/renew-all renews certificates on the master and on every enrolled node, as a job. It needs a cluster-wide key with cert_manage.
WordPress
Section titled “WordPress”POST /api/v1/hostings/:id/wp/install takes site_url, title, admin_user, admin_email and admin_password, all required, and optionally locale (default en_US), version (default latest) and no_index.
read -rs ADMIN_PW(umask 077; printf '{"site_url":"https://example.com","title":"Example","admin_user":"admin","admin_email":"owner@example.com","admin_password":"%s"}' "$ADMIN_PW" > wp-install.json)hyp -X POST https://panel.example.com/api/v1/hostings/example.com/wp/install \ -H "Content-Type: application/json" \ -d @wp-install.jsonrm wp-install.jsonThe printf line does no JSON escaping, so this form needs a password without " or \. The file holds the password, so delete it with rm wp-install.json even when hyp fails.
POST /api/v1/hostings/:id/wp/core-update takes { "version": "6.7.1" }. The version must be one WordPress offers this site. The job takes a full backup first and stops if the backup fails; a version the site is not offered fails the job before anything is touched.
hyp -X POST https://panel.example.com/api/v1/hostings/example.com/wp/core-update \ -H "Content-Type: application/json" \ -d '{"version":"6.7.1"}'GET /api/v1/nodes returns the enrolled worker nodes: node_id, label, agent_version, public_ip, enrolled_at, last_seen_at, is_drained and drain_reason, among others.
hyp https://panel.example.com/api/v1/nodesGET /api/v1/jobs/:id returns the job: id, kind, target, state (running, done, failed or cancelled), step_label, progress_pct, substeps, log_tail, error, started_at, updated_at and finished_at. A key that is not cluster-wide answers 403. An unknown id answers 404 no such job.
hyp https://panel.example.com/api/v1/jobs/<job_id>Capability names
Section titled “Capability names”The names a key can hold are: hosting_view, hosting_create, hosting_delete, hosting_suspend, hosting_edit_config, hosting_edit_nginx_raw, hosting_files, hosting_databases, hosting_cron, hosting_migrate_clone, wp_manage, wp_vuln_view, backup_run, backup_restore, backup_targets, cert_manage, security_manage, monitoring_view, monitoring_manage, nodes_view, nodes_manage, services_view, services_manage, users_manage, roles_manage, settings_manage, profiles_manage, audit_view, email_log_view, panel_import, trash_manage and api_keys_manage. Only the ones in the endpoint table are checked by /api/v1.