---
title: "Servers — API v1 and MCP | Nodea Documentation"
description: "Complete Servers module reference: REST API v1 endpoints (CRUD, statistics, components, operations, WAF, VM lifecycle start/reboot/stop), infra:read/write/delete scopes, MCP tools, secret masking and curl / JSON-RPC examples."
url: "https://nodea.io/en/api-docs/servers"
lang: "en"
updated: "2026-07-16"
---

# Servers — API v1 and MCP | Nodea Documentation

> Servers module reference — API v1 endpoints (list, show, edit, delete, SSH test, note, statistics, components, operations, WAF, VM lifecycle), infra:read/write/delete scopes, ownership policy, secret masking, 17 MCP tools and examples.

The **Servers** module is the largest part of the infrastructure API: the full lifecycle of a compute instance — edit, delete, SSH connection test, note, performance statistics, components (software installation), server operation history (performed by the nodead agent), WAF status with rules, and the lifecycle of a paired Proxmox virtual machine (start / reboot / stop). This document covers the REST API v1 surface (/api/v1/servers) and the 17 MCP tools in this module.

**Since 12.10.2026 servers outside the nodea cloud can no longer be added.** POST /api/v1/servers and POST /api/v1/servers/{server}/clone return 410 foreign\_servers\_disabled, and the MCP tools server-create and server-clone always fail with foreign\_servers\_disabled. New compute instances (virtual machines in the nodea cloud) are ordered in the panel; dedicated servers have their own module.

## Authentication and scopes

API v1 authenticates with a Passport personal access token via the Authorization: Bearer \<token\> header. Access tiers:

- infra:read — list, show, statistics, components, operations, WAF status.
- infra:write — edit + non-destructive actions (SSH test, note, component install, VM start and reboot, WAF rule toggle).
- infra:delete — delete a server (cascades to destroy the paired VM) and **VM stop** (powering off a production machine is disruptive).

The whole module is additionally gated by the Pennant flag features:servers — disabling the module hides it on both web UI and API/MCP. Rate limit: throttle:api-v1 = 240/min per token user.

## Ownership and visibility (ServerPolicy)

**Servers without a virtual machine** stay a personal resource and work as before. Row-level access is enforced by ServerPolicy: **view** = owner OR accepted FOREIGN share (a collaborator can consume every read endpoint), **manage** = the owner (the OWNER pivot). All mutations require manage — in the web panel too (component install, edit, note, SSH test, component actions); a FOREIGN share is read-only. **Shared admin pool:** a Nodea administrator with confirmed 2FA (when admin 2FA enforcement is on) gets owner-level **manage** and **delete** on the servers without a VM of other such administrators; an administrator without 2FA, a former administrator and a customer with a FOREIGN share do not. The permission is computed live from the role and 2FA, not from the share row alone. A server that exists but isn't visible to the token yields 403 (policy); an unknown id yields 404 (route model binding). Not-yet-accepted invitations never leak into the list while accepted FOREIGN shares are included (parity with web).

### Servers backed by a virtual machine — customer account

**A server paired with a Proxmox virtual machine belongs to that VM's customer account** (see [Customer accounts](https://nodea.io/en/api-docs/accounts)). A request acts in the current account (token pinning → X-Account-Id header → default account):

- **A VM of the current account:** reads require the products account permission; actions (update, VM start/stop/reboot, WAF, components, SSH test, note) require products + manageproducts; deletion requires the account **owner** role (a member, even with manageproducts, gets 403 account\_owner\_required). A missing flag → 403 account\_permission\_denied with a permission field.
- **A VM of another account without a share** (no pivot row) → 404, even when that other account is yours — a token acting in account A never touches account B's VM.
- **A VM of another account with a monitoring share** (OWNER/FOREIGN pivot) → read-only; mutations return 403.
- **The list** (GET /servers) = owned and shared servers + VM-backed servers of the **current account** (when you hold products) labelled ownership: "account"; VM-backed servers of your *other* accounts are hidden.
- meta.can\_manage in the detail response accounts for the account context — always false for a VM outside the current account.

**Check order on deletion** (DELETE /servers/{server}): visibility (403/404) → 423 managed\_by\_whmcs (a VM managed by the WHMCS ModulesGarden module — always, regardless of the global lock) → 423 termination\_locked (a server with a live VM under the global lock — the owner too) → the delete policy: a VM-backed server — the VM account's owner (else 403 account\_owner\_required), a server without a VM — the OWNER pivot or an admin-pool member (else 403).

## Security — what does NOT enter the response

Field selection is explicit and deliberately omits everything sensitive:

- **ServerResource** — omits the sshKey relation (encrypted public/private deploy-key pair, loaded on every query via Server::$with) and the raw info blob from the bootstrap script.
- **VirtualMachineResource** — never serializes the VM root\_password (encrypted, admin reveal only), the PVE cluster coordinates or the per-VM Proxmox user.
- **ServerComponentResource** — masks secret-looking keys in data\_rows (containing pass, secret or token → \*\*\*\*\*\*\*\*); manage actions persist db\_pass and friends into the encrypted data blob, which never round-trips through the API.
- **ServerOperationResource** — does not serialize the raw output blob (full JSON of the agent task result with possible operation parameters); the machine-readable status is the API contract.

## API v1 — endpoints

MethodPathScopeDescriptionGET/api/v1/serversinfra:readThe user's servers (owned + accepted shares + VM-backed servers of the current account), paginated; ownership. GET/api/v1/servers/{server}infra:readServer detail (components + paired VM). GET/api/v1/servers/{server}/statisticsinfra:readCPU/RAM/disk series (latest Prometheus samples). GET/api/v1/servers/{server}/componentsinfra:readServer components and their status. GET/api/v1/servers/{server}/components/{component}infra:readComponent detail (scoped binding) with data\_rows. GET/api/v1/servers/{server}/operationsinfra:readOperation history (install, remove, manage — performed by the nodead agent), newest first; simplePaginate. GET/api/v1/servers/{server}/wafinfra:readWAF status: has\_agent, rules, latest 20 events. POST/api/v1/serversinfra:write**Disabled** — always 410 foreign\_servers\_disabled (servers outside the nodea cloud can no longer be added). PUT/PATCH/api/v1/servers/{server}infra:writeEdit name/SSH port (IP immutable; owner-only). POST/api/v1/servers/{server}/test-sshinfra:writeSSH connection test with the deploy key. {ok, message} or 502. PATCH/api/v1/servers/{server}/noteinfra:writeSet/clear the note. POST/api/v1/servers/{server}/cloneinfra:write**Disabled** — after the visibility check (403/404) always 410 foreign\_servers\_disabled. POST/api/v1/servers/{server}/components/{component}/installinfra:writeInstall a component — performed by the nodead agent installed on the server. Returns 202 + the operation; if the agent disappears before the task is sent, the operation ends with status FAIL and the reason. 422 (field component) with a readable message when: the component is unavailable on the server's OS (e.g. php83 on Ubuntu 26.04); the server has no nodead agent ("the nodead agent is not connected to this server — connect the agent"); the agent has not registered yet (e.g. a freshly created nodea VM); the agent is offline; the agent does not advertise component.install (needs an update). Applies to every component. POST/api/v1/servers/{server}/vm/startinfra:writeStart the VM (async). 422 when no VM paired; 423 (service\_suspended) when the WHMCS-billed service is suspended or ended, or an administrator suspended the server; 409 (vm\_migrating) while an administrator moves the server to another node. POST/api/v1/servers/{server}/vm/rebootinfra:writeReboot the VM (synchronous PVE). 422/409 (no\_pve\_assignment, vm\_migrating)/423 (service\_suspended)/502. POST/api/v1/servers/{server}/waf/rules/{rule}/toggleinfra:writeEnable/disable a WAF rule; enabled (bool). 422 without a fleet agent. DELETE/api/v1/servers/{server}infra:deleteDelete the server + async destroy the VM. Returns 204. A VM-backed server: the VM account's owner only (403 account\_owner\_required). A server with a live VM while the global termination lock is active → 423 (termination\_locked). A VM managed in the WHMCS billing panel (ModulesGarden) → always 423 (managed\_by\_whmcs). A VM being migrated to another node → 409 (vm\_migrating), nothing is deleted. Servers without a VM are deleted normally. POST/api/v1/servers/{server}/vm/stopinfra:deleteStop the VM (destructive — powers off a production machine). 422 without a VM; 409 (vm\_migrating) during a migration to another node.

Total: **18 endpoints** (7 read, 9 write, 2 delete).

### GET /servers — query parameters

- search (string, optional, max 255) — matched against name and IP.
- sort (enum: name|ip|created\_at, default created\_at) — hard whitelist.
- direction (asc|desc; defaults to desc for created\_at).
- per\_page (int 1–100, default 20), page (int ≥1).

### ServerResource — fields

id (UUID), name, ip, ssh\_port, status (lowercase: new|connected|installed|error), note, ownership (owner|shared|account, index only; account = a VM-backed server visible through membership of the current account, without a pivot row), has\_virtual\_machine (bool), virtual\_machine (object or null), components\[\] (when loaded), created\_at, updated\_at. The virtual\_machine object: id, vm\_id (Proxmox VMID), name, ip, status, last\_error, plus optional plan, os\_template, location.

### POST/PATCH /servers — body parameters

- name (string, required, min 3, max 100).
- ip (required for POST, ipv4; immutable — absent from PATCH).
- ssh\_port (required, numeric).

Note: note (string, nullable). WAF toggle: enabled (bool, required). Install: the URL param {component} = a Component enum value (e.g. nginx, php83, mariadb1011, redis, postgres16, nodejs20).

### Error codes

- 401 — missing/invalid token.
- 403 — missing scope or an existing, not-visible server (policy); a mutation of another account's VM-backed server shared for monitoring.
- 403 — account-context error: account\_permission\_denied (the permission field names the missing flag) — products for reads, manageproducts for actions on a VM-backed server, no\_account\_context, account\_forbidden / account\_mismatch (the X-Account-Id header or token pinning) — see [Customer accounts](https://nodea.io/en/api-docs/accounts).
- 403 — account\_owner\_required: only the VM account's owner can delete a VM-backed server.
- 404 — unknown id; another account's VM-backed server without a share; a component from another server (scoped binding).
- 410 — foreign\_servers\_disabled: adding and cloning servers is disabled (servers outside the nodea cloud).
- 409 — no PVE assignment on reboot (no\_pve\_assignment); start / stop / reboot of a VM, and deleting a server whose VM an administrator is moving to another node (vm\_migrating) — the actions return once the migration ends.
- 422 — validation; no paired VM (no\_virtual\_machine); no fleet agent (no\_fleet\_agent).
- 423 — deleting a server whose VM is managed in the WHMCS billing panel (ModulesGarden module) is always refused, even with the global lock off (code managed\_by\_whmcs); the server and VM are left untouched — the service is closed in the billing panel.
- 423 — deleting a server with a live VM is currently locked globally in Nodea (code termination\_locked); the server and VM are left untouched. The lock is checked before account permissions.
- 423 — starting / rebooting a VM whose WHMCS-billed service is suspended (unpaid invoice) or ended (code service\_suspended); stopping the VM stays allowed. The lock lifts when WHMCS restores the service (status Active). The same code answers starting / rebooting a server a Nodea administrator suspended by hand — only an administrator lifts that lock.
- 502 — failed SSH test / failed reboot (reboot\_failed).

## Examples — API

### List servers, sorted

curl -s "https://app.nodea.io/api/v1/servers?sort=name&direction=asc&per\_page=25" \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Accept: application/json"

### Attempt to add a server (disabled)

curl -s -X POST https://app.nodea.io/api/v1/servers \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Accept: application/json" # HTTP 410 { "message": "Servers outside the nodea cloud can no longer be added. Order a new compute instance in the panel.", "code": "foreign\_servers\_disabled" }

### Install a component

curl -s -X POST https://app.nodea.io/api/v1/servers/{id}/components/nginx/install \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Accept: application/json"

### Reboot the virtual machine

curl -s -X POST https://app.nodea.io/api/v1/servers/{id}/vm/reboot \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Accept: application/json"

### Stop the VM (destructive, infra:delete scope)

curl -s -X POST https://app.nodea.io/api/v1/servers/{id}/vm/stop \\ -H "Authorization: Bearer $NODEA\_TOKEN" \\ -H "Accept: application/json"

## MCP — tools (17)

The MCP tools share the authentication and scope model with API v1 (Bearer + scope in ensureScope) and the features:servers flag. Servers are addressed by UUID; a missing or inaccessible id raises the same error (no cross-tenant enumeration — InteractsWithServers::serverFor defers to the view/manage policies). Secrets are masked exactly as in the API (the same resource shapes). Destructive tools (server-delete, server-vm-stop) require confirm: true.

ToolScopeModeDescriptionservers-listinfra:readread-onlyList servers (owned + FOREIGN shares + VM-backed servers of the current account); search, page, per\_page (max 50). Returns the ownership label (owner|shared|account); VM-backed servers of the user's other accounts are hidden. server-getinfra:readread-onlyServer detail (components + VM). Param: id. server-statisticsinfra:readread-onlyCPU/RAM/disk series. Param: id. server-componentsinfra:readread-onlyComponent list; id, page, per\_page. Omits components unavailable on the server's OS and — when the server has no registered nodead agent supporting component.install — every component not yet installed (unless already present). server-operationsinfra:readread-onlyOperation history; id, component filter, pagination. No raw task output. server-waf-statusinfra:readread-onlyWAF status: has\_agent, rules, latest 20 events. Param: id. server-createinfra:write—**Disabled** — always fails with foreign\_servers\_disabled. server-updateinfra:writeidempotentEdit name/ssh\_port (partial; IP immutable). Owner-only. server-set-noteinfra:writeidempotentSet/clear the note; id, note (nullable). server-test-sshinfra:writewrite (dials infra)SSH test with the deploy key; {connected, error}. Owner-only. server-cloneinfra:write—**Disabled** — after the visibility check of id always fails with foreign\_servers\_disabled. server-component-installinfra:writewriteInstall components; id, components\[\] (min 1, enum values). Performed by the server's nodead agent, staggered 10s apart. Error with a readable reason when a component is unavailable on the server's OS or the server has no registered nodead agent able to install (no agent, agent not registered, offline, or without the component.install capability). server-vm-startinfra:writewriteStart the VM; id. no\_virtual\_machine when no VM; service\_suspended when the WHMCS service is suspended or ended, or an administrator suspended the server; vm\_migrating while an administrator moves the server to another node. server-vm-rebootinfra:writewriteReboot the VM (synchronous PVE); id. service\_suspended when the WHMCS service is suspended or ended, or an administrator suspended the server; vm\_migrating during a migration to another node. server-waf-rule-toggleinfra:writeidempotentToggle a WAF rule; id, rule\_id (max 64), enabled. Ships the full override set to the nodead agent. server-deleteinfra:deletedestructiveDelete the server + async destroy the VM; id + required confirm: true. A VM-backed server: the VM account's owner only (account\_owner\_required); a server without a VM: the owner (OWNER pivot) or an admin-pool member. Order: visibility → managed\_by\_whmcs (a VM managed in WHMCS — always, nothing is deleted) → termination\_locked (global lock, nothing is deleted) → the delete policy → vm\_migrating (an administrator is moving the VM to another node, nothing is deleted). server-vm-stopinfra:deletedestructiveStop the VM (offline until restarted); id + required confirm: true. Owner-only. vm\_migrating during a migration to another node.

Breakdown: 6 read-only, 9 write, 2 destructive.

**Customer accounts in MCP.** Servers without a VM work as before (pivot). A VM-backed server belongs to the VM's account: in the current account of the connection, reads require the products permission, actions (server-update, server-vm-start/-stop/-reboot, server-waf-rule-toggle, server-component-install, server-test-ssh, server-set-note) require products + manageproducts, and server-delete requires the account owner role. Another account's VM without a share returns "not found", and with a monitoring share it is read-only. Denials end with the suffix (code: account\_permission\_denied, permission: \<flag\>), (code: account\_owner\_required), (code: no\_account\_context), (code: managed\_by\_whmcs) or (code: termination\_locked); start/reboot of a VM whose WHMCS service is suspended or that an administrator suspended — (code: service\_suspended); start/stop/reboot and deleting a server whose VM an administrator is moving to another node — (code: vm\_migrating).

### Examples — MCP (JSON-RPC)

{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "server-component-install", "arguments": { "id": "9b1f...uuid", "components": \["nginx", "php83"\] } } } { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "server-vm-stop", "arguments": { "id": "9b1f...uuid", "confirm": true } } }

## Related

- [SSH Keys — API v1 and MCP | Nodea Documentation](https://nodea.io/en/api-docs/ssh-keys)
- [DNS](https://nodea.io/en/glossary/dns)
- [Devices (Connection Manager) — API v1 and MCP | Nodea Documentation](https://nodea.io/en/api-docs/vault-devices)
- [Customer accounts — API v1 and MCP | Nodea Documentation](https://nodea.io/en/api-docs/accounts)

---

Languages: [pl](https://nodea.pl/pl/dokumentacja-api/servers.md)  
Site index: [llms.txt](https://nodea.io/en/llms.txt)
