NexorPanel API
Everything the panel's interface does is done with the same requests: the interface is an ordinary client of this API and has no other way in. So there is no “possible in the panel, impossible over the API”.
Basics
The base address is the machine the panel is opened on:
https://panel.example.com/api/v1
- Request and response bodies are JSON in UTF-8; send
Content-Type: application/jsonwhen there is a body. - Times are UTC in RFC 3339:
2026-09-24T08:12:30Z. - The language of messages follows
Accept-Language: English, Russian, Ukrainian, Belarusian, German or Polish. - A trailing slash does not matter:
/api/v1/sitesand/api/v1/sites/are the same address, answered directly rather than redirected (a redirect afterPOSTloses the body in some clients). - The only version is
v1. Fields may be added inside it, but never change meaning; a breaking change getsv2. GET /healthzneeds no key and answers{"status":"ok","version":"…"}— handy to check that an update landed.
API keys
A program identifies itself with a key, not a login and password. A panel password opens the whole panel and requires a second factor; a key can be issued for one script, made read-only, given an expiry and revoked without touching anything else.
Create a key in the panel: Settings → API keys (or POST /api/v1/tokens from a signed-in session). The key is shown once — the panel keeps only its SHA-256 fingerprint. Send it in a header:
Authorization: Bearer nxp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
- Never in the query string: the panel does not look for it there, because URLs end up in web server logs, shell history and
Referer. - A key inherits its account's rights and rereads them on every request. An administrator demoted to client loses admin rights over the key too; a disabled account's keys stop working.
- A read-only key answers
GET,HEADandOPTIONSand refuses everything else with403 read_only_key— enough for monitoring and exports. - A key cannot create a key —
403 key_by_key; otherwise a leaked key could mint a new one faster than anyone notices. - Changing the password does not revoke keys. If the password is changed because of a leak, revoke the keys separately.
- Expiry is from one day to a year;
0means no expiry. Up to 32 keys per account (409 too_many_keys).
Secret entrance
An installation may have its own path segment (NEXOR_ENTRANCE). Then every address below lives at /<segment>/api/v1/… instead of /api/v1/…, and everything else — including the API — gets an empty 404. Only /healthz stays outside. The machine's administrator tells you the segment; it cannot be guessed, which is the point.
URL rules
| What | How |
|---|---|
| list | GET /api/v1/<section> |
| one object | GET /api/v1/<section>/{id} |
| create | POST /api/v1/<section> |
| update | PUT /api/v1/<section>/{id} |
| delete | DELETE /api/v1/<section>/{id} |
| action | POST /api/v1/<section>/{id}/<action> |
The fields for create and update are the ones the panel's form shows: fill the form, and the browser's developer tools show exactly which JSON was sent. The form and the API are the same thing. Lists of choices for forms (servers, PHP versions, addresses, plans) come from the *-options endpoints.
Lists
All lists work the same way:
GET /api/v1/sites?limit=25&offset=0&search=example
{"items":[…],"total":42,"limit":25,"offset":0}
limit is at most 200; a bigger value falls back to the default instead of failing. search matches the name. Some lists narrow further: ?site=126 by site, ?domain=example.org by mail domain. A client's response contains only their own objects.
Errors
An error is JSON with a code, a message and, when it is about one field, the field's name:
{"code":"invalid_input","message":"The domain must contain at least one dot.","field":"domain"}
Parse code, not message. The message is written for people and changes with the language; the code does not. The most common codes:
| HTTP | Code | Meaning |
|---|---|---|
| 400 | bad_request | the request cannot be parsed |
| 400 | invalid_input | a field failed validation; field names it |
| 401 | unauthenticated | neither a key nor a session |
| 401 | bad_key | the key is revoked, expired or unknown |
| 402 | needs_plan | the feature is not in this client's plan; the message names the plan needed |
| 403 | forbidden | the section is not for this role |
| 403 | read_only_key | the key is read-only |
| 403 | key_by_key | a key cannot create a key |
| 403 | account_disabled | the account is disabled |
| 403 | bad_path | the path is outside the site |
| 403 | bad_origin | a browser request came from another origin |
| 404 | not_found | no such object — or it is not yours |
| 409 | name_taken | the name is taken |
| 409 | cannot_move | a site cannot be moved to another machine by editing a field |
| 409 | action_pending | the previous change is still running |
| 409 | too_many_keys | the account already has the maximum number of keys |
| 413 | file_too_big | the file is larger than the upload limit |
| 422 | bad_archive | the archive cannot be unpacked |
| 429 | rate_limited | too often; wait |
| 500 | internal | a panel error; details are in its log |
| 501 | not_implemented | the section does not work in this mode |
| 501 | agent_not_told | the agent was not given this job; the message names the missing setting |
| 502 | agent_unreachable | the agent on the machine does not answer |
| 502 | agent_refused | the panel and the agent have different secrets |
| 502 | agent_too_old | the agent is older than the panel and does not know the request |
| 502 | agent_failed | the agent refused; details are in the panel's log |
| 503 | no_agent | no agent is configured on the machine |
| 503 | not_configured | this installation was not told what to work with (e.g. NEXOR_IMAP) |
Someone else's object and a missing one answer the same 404 not_found: a difference would let anyone enumerate other people's sites.
Examples
Keep the address and the key in variables:
export NX=https://panel.example.com/api/v1 export KEY=nxp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX curl -sS -H "Authorization: Bearer $KEY" $NX/version
Sites and their disk usage
curl -sS -H "Authorization: Bearer $KEY" "$NX/sites?limit=200" | jq -r '.items[] | "\(.domain)\t\(.usedMb) MB"'
A site's space is three numbers: usedMb (the folder), databasesMb and backupsMb. -1 means “not counted yet”, not zero.
Create a site
curl -sS -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"domain":"example.org","serverId":1,"active":true,"quotaMb":-1,
"phpMode":"php-fpm","ssl":true,"letsEncrypt":true,"forceHttps":true,
"wwwAlias":true,"backupInterval":"daily","backupCopies":7}' \
$NX/sites
serverId is the machine; take it from GET /site-options (servers), together with that machine's PHP versions and addresses. Zero or no field means the panel's own machine; a client's site always goes there.
Pause a site and resume it
curl -sS -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"active":false}' $NX/sites/126/status
Create a client with a limit on sites
curl -sS -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"username":"lira","password":"correct horse battery","company":"Lira Ltd",
"email":"petr@example.org","active":true,
"limits":{"webDomain":5,"database":5,"cron":10,"ftpUser":5}}' \
$NX/clients
Back up a site now
curl -sS -X POST -H "Authorization: Bearer $KEY" $NX/sites/126/backups
Restart a service on the machine — actions: start, stop, restart, reload.
curl -sS -X POST -H "Authorization: Bearer $KEY" $NX/servers/1/services/nginx/restart
See what takes the disk
curl -sS -H "Authorization: Bearer $KEY" $NX/servers/1/disk | jq -r '.parts[] | "\(.key)\t\(.bytes)"'
Check that everything is fine — a read-only key is enough for monitoring:
curl -sS -H "Authorization: Bearer $KEY" $NX/attention | jq -r '.items[] | select(.level != "ok") | .text'
Download a site file
curl -sS -H "Authorization: Bearer $KEY" \ "$NX/sites/126/files/download?path=/web/index.php" -o index.php
Deploy a site from a repository
curl -sS -X POST -H "Authorization: Bearer $KEY" -H 'Content-Type: application/json' \
-d '{"url":"https://github.com/owner/repo","ref":"main","webDir":"public"}' \
$NX/sites/12/deploy
The machine fetches the repository and replaces the site folder with it as a whole — not a git pull, which stops at the first local difference and leaves a half-updated site. The answer is what was deployed: {"commit":"a1b2c3d","ref":"main","webDir":"public"}. webDir is the folder the web server serves, relative to the site folder.
For a private repository send the private deploy key in key and use an ssh URL; the panel does not store it — it lives in a temporary file on the machine for the duration of the clone. Refused URLs: ext::, file:// and local paths, unencrypted git:// and http://, and passwords inside the URL. Submodules are not fetched and .git never reaches the site folder. Files listed in the repository's .gitignore are left in place; anything else to keep can be listed in a .deployignore in the site folder on the server.
All endpoints
219 endpoints. Paths are relative to the panel's address. Unless marked otherwise, an endpoint is open to every signed-in role, and clients see only their own objects.
Sign-in and own account
auth/login and auth/2fa exist for the browser: they set a cookie, require the second factor and are rate-limited. Scripts use a key.
| POST | /api/v1/auth/login | no auth |
| POST | /api/v1/auth/2fa | no auth |
| POST | /api/v1/auth/logout | no auth |
| GET | /api/v1/me | |
| POST | /api/v1/me/password | |
| GET | /api/v1/2fa | |
| POST | /api/v1/2fa/setup | |
| POST | /api/v1/2fa/enable | |
| POST | /api/v1/2fa/disable | |
| POST | /api/v1/2fa/recovery-codes |
API keys
A key cannot create another key (403 key_by_key). Up to 32 keys per account.
| GET | /api/v1/tokens | |
| POST | /api/v1/tokens | |
| DELETE | /api/v1/tokens/{id} |
Dashboard, search and health
/healthz stays reachable even behind a secret entrance and answers with the build version.
| GET | /healthz | no auth |
| GET | /api/v1/version | |
| GET | /api/v1/dashboard | |
| GET | /api/v1/search | |
| GET | /api/v1/attention | |
| GET | /api/v1/notifications | |
| GET | /api/v1/plan/features |
Websites
A site is created on the machine named by serverId (from site-options) and cannot be moved by editing it: a different serverId answers 409 cannot_move. Deleting a site removes its configuration but keeps its files.
| GET | /api/v1/sites | |
| POST | /api/v1/sites | |
| GET | /api/v1/sites/{id} | |
| PUT | /api/v1/sites/{id} | |
| DELETE | /api/v1/sites/{id} | |
| POST | /api/v1/sites/{id}/status | |
| POST | /api/v1/sites/{id}/provision | |
| POST | /api/v1/sites/{id}/copy | |
| POST | /api/v1/sites/{id}/publish | |
| GET | /api/v1/sites/{id}/overview | |
| GET | /api/v1/sites/{id}/stats | |
| PUT | /api/v1/sites/{id}/stats | |
| GET | /api/v1/sites/{id}/shot | |
| GET | /api/v1/sites/{id}/logs | |
| GET | /api/v1/sites/{id}/logs/{name} | |
| GET | /api/v1/sites/children | |
| GET | /api/v1/sites/{id}/children | |
| POST | /api/v1/sites/{id}/children | |
| PUT | /api/v1/sites/{id}/children/{childId} | |
| DELETE | /api/v1/sites/{id}/children/{childId} | |
| GET | /api/v1/site-options | |
| GET | /api/v1/hosting-options |
Deploy from a repository
deploy replaces the site folder with the repository as a whole. A private repository takes a private deploy key in the request; the panel does not store it.
| POST | /api/v1/sites/{id}/deploy | |
| GET | /api/v1/sites/{id}/deploy-key | |
| POST | /api/v1/sites/{id}/deploy-key | |
| DELETE | /api/v1/sites/{id}/deploy-key |
Site files
Paths never leave the site's folder (403 bad_path). Deleting moves a file to the site's trash for 30 days. Large files are uploaded in parts.
| GET | /api/v1/sites/{id}/files | |
| GET | /api/v1/sites/{id}/file | |
| PUT | /api/v1/sites/{id}/file | |
| GET | /api/v1/sites/{id}/files/search | |
| GET | /api/v1/sites/{id}/files/size | |
| POST | /api/v1/sites/{id}/files/action | |
| POST | /api/v1/sites/{id}/files/upload | |
| POST | /api/v1/sites/{id}/files/upload/part | |
| GET | /api/v1/sites/{id}/files/download | |
| GET | /api/v1/sites/{id}/trash | |
| POST | /api/v1/sites/{id}/trash/restore | |
| DELETE | /api/v1/sites/{id}/trash |
SSL certificates
Let's Encrypt is switched on in the site itself (letsEncrypt); these routes are for a certificate of your own.
| GET | /api/v1/sites/{id}/certificate | |
| PUT | /api/v1/sites/{id}/certificate | |
| DELETE | /api/v1/sites/{id}/certificate |
Site backups
The site is part of the route: a backup number alone does not say whose it is.
| GET | /api/v1/sites/{id}/backups | |
| POST | /api/v1/sites/{id}/backups | |
| POST | /api/v1/sites/{siteId}/backups/{id}/action | |
| POST | /api/v1/sites/{siteId}/backups/{id}/offsite | |
| POST | /api/v1/sites/{siteId}/backups/{id}/offsite/fetch | |
| GET | /api/v1/sites/{siteId}/backups/{id}/download |
Databases
temp-login opens phpMyAdmin with a half-hour account created for that one visit; database passwords are never stored by the panel.
| GET | /api/v1/databases | |
| POST | /api/v1/databases | |
| GET | /api/v1/databases/{id} | |
| PUT | /api/v1/databases/{id} | |
| DELETE | /api/v1/databases/{id} | |
| POST | /api/v1/databases/{id}/temp-login | |
| GET | /api/v1/database-users | |
| POST | /api/v1/database-users | |
| GET | /api/v1/database-users/{id} | |
| PUT | /api/v1/database-users/{id} | |
| DELETE | /api/v1/database-users/{id} |
SFTP, FTP and WebDAV access
FTP users are the same records as shell users: FTP is switched on with the ftp field of a shell user, and DELETE ftp-users/{id} only closes FTP.
| GET | /api/v1/shell-users | |
| POST | /api/v1/shell-users | |
| GET | /api/v1/shell-users/{id} | |
| PUT | /api/v1/shell-users/{id} | |
| DELETE | /api/v1/shell-users/{id} | |
| GET | /api/v1/ftp-users | |
| POST | /api/v1/ftp-users | |
| GET | /api/v1/ftp-users/{id} | |
| PUT | /api/v1/ftp-users/{id} | |
| DELETE | /api/v1/ftp-users/{id} | |
| GET | /api/v1/webdav-users | |
| POST | /api/v1/webdav-users | |
| GET | /api/v1/webdav-users/{id} | |
| PUT | /api/v1/webdav-users/{id} | |
| DELETE | /api/v1/webdav-users/{id} |
Protected folders
| GET | /api/v1/web-folders | |
| POST | /api/v1/web-folders | |
| GET | /api/v1/web-folders/{id} | |
| PUT | /api/v1/web-folders/{id} | |
| DELETE | /api/v1/web-folders/{id} | |
| POST | /api/v1/web-folders/{id}/users | |
| DELETE | /api/v1/web-folders/{id}/users/{userId} |
Scheduled tasks
| GET | /api/v1/cron | |
| POST | /api/v1/cron | |
| GET | /api/v1/cron/{id} | |
| PUT | /api/v1/cron/{id} | |
| DELETE | /api/v1/cron/{id} |
Lists narrow with ?domain=. Transports and relay recipients are administrator-only.
| GET | /api/v1/mail/domains | |
| POST | /api/v1/mail/domains | |
| GET | /api/v1/mail/domains/{id} | |
| PUT | /api/v1/mail/domains/{id} | |
| DELETE | /api/v1/mail/domains/{id} | |
| POST | /api/v1/mail/domains/{id}/status | |
| GET | /api/v1/mail/mailboxes | |
| POST | /api/v1/mail/mailboxes | |
| GET | /api/v1/mail/mailboxes/{id} | |
| PUT | /api/v1/mail/mailboxes/{id} | |
| DELETE | /api/v1/mail/mailboxes/{id} | |
| GET | /api/v1/mail/mailboxes/{id}/filters | |
| POST | /api/v1/mail/mailboxes/{id}/filters | |
| PUT | /api/v1/mail/mailboxes/{id}/filters/{filterId} | |
| DELETE | /api/v1/mail/mailboxes/{id}/filters/{filterId} | |
| GET | /api/v1/mail/forwards | |
| POST | /api/v1/mail/forwards | |
| DELETE | /api/v1/mail/forwards/{id} | |
| GET | /api/v1/mail/lists | |
| POST | /api/v1/mail/lists | |
| DELETE | /api/v1/mail/lists/{id} | |
| GET | /api/v1/mail/lists-mailman | |
| POST | /api/v1/mail/lists-mailman | |
| DELETE | /api/v1/mail/lists-mailman/{id} | |
| GET | /api/v1/mail/transports | admin |
| POST | /api/v1/mail/transports | admin |
| DELETE | /api/v1/mail/transports/{id} | admin |
| GET | /api/v1/mail/relay-recipients | admin |
| POST | /api/v1/mail/relay-recipients | admin |
| DELETE | /api/v1/mail/relay-recipients/{id} | admin |
| GET | /api/v1/mail/contacts | |
| POST | /api/v1/mail/contacts | |
| PUT | /api/v1/mail/contacts/{id} | |
| DELETE | /api/v1/mail/contacts/{id} | |
| GET | /api/v1/mail-options |
Webmail
Webmail has its own sign-in: the mailbox password proves the rights and a sealed mail cookie keeps them, so there is no mailbox number in these routes. mail/mailboxes/{id}/webmail opens a mailbox from a panel session.
| POST | /api/v1/mail/mailboxes/{id}/webmail | |
| GET | /api/v1/webmail/session | webmail session |
| POST | /api/v1/webmail/login | webmail session |
| POST | /api/v1/webmail/open | webmail session |
| POST | /api/v1/webmail/logout | webmail session |
| GET | /api/v1/webmail/folders | webmail session |
| GET | /api/v1/webmail/letters | webmail session |
| GET | /api/v1/webmail/search | webmail session |
| GET | /api/v1/webmail/letters/{uid} | webmail session |
| GET | /api/v1/webmail/letters/{uid}/attachments/{index} | webmail session |
| POST | /api/v1/webmail/letters/{uid}/seen | webmail session |
| POST | /api/v1/webmail/letters/{uid}/move | webmail session |
| POST | /api/v1/webmail/send | webmail session |
DNS
| GET | /api/v1/dns/zones | |
| POST | /api/v1/dns/zones | |
| GET | /api/v1/dns/zones/{id} | |
| PUT | /api/v1/dns/zones/{id} | |
| DELETE | /api/v1/dns/zones/{id} | |
| POST | /api/v1/dns/zones/{id}/status | |
| POST | /api/v1/dns/zones/{id}/dnssec | |
| GET | /api/v1/dns/zones/{id}/records | |
| POST | /api/v1/dns/zones/{id}/records | |
| PUT | /api/v1/dns/records/{id} | |
| DELETE | /api/v1/dns/records/{id} | |
| GET | /api/v1/dns/zones/{id}/export | |
| POST | /api/v1/dns/zones/{id}/import | |
| GET | /api/v1/dns/slave-zones | |
| POST | /api/v1/dns/slave-zones | |
| PUT | /api/v1/dns/slave-zones/{id} | |
| DELETE | /api/v1/dns/slave-zones/{id} | |
| GET | /api/v1/dns-options |
Clients and plans
Resellers see the Clients section too, but only their own clients.
| GET | /api/v1/clients | admin, reseller |
| POST | /api/v1/clients | admin, reseller |
| GET | /api/v1/clients/{id} | admin, reseller |
| PUT | /api/v1/clients/{id} | admin, reseller |
| DELETE | /api/v1/clients/{id} | admin, reseller |
| POST | /api/v1/clients/{id}/password | admin, reseller |
| GET | /api/v1/client-options | admin, reseller |
| GET | /api/v1/plans | admin |
| POST | /api/v1/plans | admin |
| GET | /api/v1/plans/{id} | admin |
| PUT | /api/v1/plans/{id} | admin |
| DELETE | /api/v1/plans/{id} | admin |
Servers and services
Service actions: start, stop, restart, reload. The console runs a command on the machine only where the agent has NEXOR_AGENT_CONSOLE=1.
| GET | /api/v1/servers | admin |
| GET | /api/v1/monitor | admin |
| GET | /api/v1/servers/{id}/metrics | admin |
| GET | /api/v1/servers/{id}/agent | admin |
| GET | /api/v1/servers/{id}/disk | admin |
| GET | /api/v1/servers/{id}/services | admin |
| POST | /api/v1/servers/{id}/services/{name}/{action} | admin |
| POST | /api/v1/servers/{id}/console | admin |
| GET | /api/v1/servers/{id}/machine-certificate | admin |
| POST | /api/v1/servers/{id}/machine-certificate | admin |
Firewall and IP addresses
| GET | /api/v1/firewall | admin |
| POST | /api/v1/firewall | admin |
| PUT | /api/v1/firewall/{id} | admin |
| DELETE | /api/v1/firewall/{id} | admin |
| GET | /api/v1/ip-addresses | admin |
| GET | /api/v1/server-ips | admin |
| POST | /api/v1/server-ips | admin |
| PUT | /api/v1/server-ips/{id} | admin |
| DELETE | /api/v1/server-ips/{id} | admin |
Panel backups, logs and updates
update reports whether the panel and every agent run the same version, and whether a newer release exists.
| GET | /api/v1/panel/backups | admin |
| POST | /api/v1/panel/backups | admin |
| POST | /api/v1/panel/backups/upload | admin |
| GET | /api/v1/panel/backups/plan | admin |
| PUT | /api/v1/panel/backups/plan | admin |
| GET | /api/v1/panel/backups/{name}/download | admin |
| POST | /api/v1/panel/backups/{name}/restore | admin |
| POST | /api/v1/panel/backups/{name}/offsite | admin |
| POST | /api/v1/panel/backups/{name}/offsite/fetch | admin |
| DELETE | /api/v1/panel/backups/{name} | admin |
| GET | /api/v1/audit | admin |
| GET | /api/v1/panel-log | admin |
| GET | /api/v1/update | admin |
| GET | /api/v1/notifications/outbound | admin |
| POST | /api/v1/notifications/outbound/test | admin |
| GET | /api/v1/migration/sources | admin |
| POST | /api/v1/migration/inspect | admin |