Hosting (DirectAdmin) — API v1 and MCP | Nodea Documentation
The Hosting module covers the user's DirectAdmin hosting accounts: listing and account detail, mailbox management, backups with restore, Let's Encrypt certificate issuance and account termination. This document describes the REST API v1 layer (/api/v1) and the module's MCP tools.
Authentication and scopes
API v1 authenticates with a Passport personal access token via the Authorization: Bearer <token> header. This module's routes are guarded by the account scope matching the operation tier:
account:read— listing and detail (account, mailboxes, backups).account:write— mailbox create/update/delete, backup create, SSL issuance.account:delete— backup restore (overwrites state) and account termination.
The whole module is additionally gated by the Pennant flag features:hosting-suite — disabling it hides the module from the web UI and the API. Rate limit: throttle:api-v1 = 240/min per token user. In MCP, each tool additionally requires the transport scope mcp:use and the hosting-suite flag.
Ownership and visibility
DirectAdmin accounts have no policy class and are never shared — every access is strictly owner-scoped by user_id. Mailboxes and backups are checked twice: the account must belong to the token user, and the mailbox/backup must belong to that account. Any mismatch yields 404 (not 403), so the API never confirms a foreign resource exists. In MCP this maps to a single "not found or not accessible" error.
Sensitive-data safety
Mailbox passwords are write-only: accepted in the create/update payload, forwarded straight to the DirectAdmin API, never stored locally and never returned in any response. The DA panel password (encrypted_password), SSO login keys, and the DA server's hostname/IP/port and API credentials are deliberately never serialized — the account exposes only its location. Internal backup columns (local_path, restore_log) are likewise not exposed.
Side effects — asynchronous and account state
DirectAdmin-side effects (backup create/restore, SSL issuance, termination) run out-of-band through queued jobs, like web — those actions return 202 Accepted. Mailbox create/update/delete calls the DA API synchronously, and the local mirror is refreshed by SyncEmailAccountsJob. Every DA-backend operation requires an active account: a pending account is still provisioning and a terminated one accepts no further actions — both return 409 Conflict.
API v1 — endpoints
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/hosting | account:read | Owner's hosting accounts (id DESC), paginated. |
| GET | /api/v1/hosting/{account} | account:read | Account detail: disk/bandwidth usage, SSL, location, plan, timestamps. |
| GET | /api/v1/hosting/{account}/emails | account:read | Account mailboxes (by address), paginated. |
| POST | /api/v1/hosting/{account}/emails | account:write | Create a mailbox (write-only password). Returns 202. |
| PATCH | /api/v1/hosting/{account}/emails/{email} | account:write | Change password (write-only) and/or mailbox quota. |
| DELETE | /api/v1/hosting/{account}/emails/{email} | account:write | Delete a mailbox (DA + local). Returns 204. |
| GET | /api/v1/hosting/{account}/backups | account:read | Account backups (id DESC), paginated. |
| POST | /api/v1/hosting/{account}/backups | account:write | Queue a new backup (name backup-Y-m-d-H-i). Returns 202. |
| POST | /api/v1/hosting/{account}/backups/{backup}/restore | account:delete | Restore a backup — overwrites account state (irreversible). Returns 202. |
| POST | /api/v1/hosting/{account}/ssl | account:write | Queue a Let's Encrypt certificate. Requires an active account. Returns 202. |
| DELETE | /api/v1/hosting/{account} | account:delete | Terminate the account (destructive, async). Returns 202; already terminated → 422. |
Web-only (out of API and MCP): DirectAdmin control-panel SSO login (login-key mint) and the account password reset — interactive browser hand-offs, not headless operations.
Mailboxes — body parameters
email_local(string, required on create, 1–64 chars, charset[a-zA-Z0-9._-]) — local part of the address.domain(string, required on create, 4–253, FQDN-shaped).password(string, 10–128 chars; required on create, optional on update) — write-only.quota_mb(int 50–10000; required on create, optional on update).
Mailbox (HostingEmailResource): id, email, domain, quota_mb, used_mb, is_forwarder, forward_to, is_active, last_synced_at — never carries a password field. Backup (HostingBackupResource): id, backup_name, size_mb, status, is_remote, created_at, expires_at, restore_url (link to the restore action).
Error codes
401— missing/invalid token.403— missing required scope.404— unknown/foreign account, mailbox or backup.409— account not active (pending/terminated) for a DA-backend action.422— validation or terminating an already-terminated account (codealready_terminated).
Examples — API
List hosting accounts
curl -s "https://app.nodea.io/api/v1/hosting?per_page=20" \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Create a mailbox (write-only password)
curl -s -X POST https://app.nodea.io/api/v1/hosting/{account}/emails \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email_local": "office",
"domain": "example.com",
"password": "S3cretPass!23",
"quota_mb": 2000
}'
Queue a backup
curl -s -X POST https://app.nodea.io/api/v1/hosting/{account}/backups \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
Restore a backup (irreversible)
curl -s -X POST https://app.nodea.io/api/v1/hosting/{account}/backups/{backup}/restore \
-H "Authorization: Bearer $NODEA_TOKEN" \
-H "Accept: application/json"
MCP — tools
The MCP tools share the authentication and scope model with API v1 (Bearer + scope in ensureScope) and the hosting-suite flag (a tool on an inactive flag disappears from tools/list). Ids are integers. A missing and a foreign id return the same error. DirectAdmin-touching actions are asynchronous (returning a queued acknowledgement). Destructive tools — hosting-backup-restore and hosting-terminate — are marked destructive and require an explicit confirm: true. Mailbox passwords remain write-only.
| Name | Scope | Mode | Description |
|---|---|---|---|
| hosting-list | account:read | read-only | DirectAdmin accounts (id DESC); page, per_page (max 50). |
| hosting-get | account:read | read-only | Account detail by id. The DA panel password is never returned. |
| hosting-emails | account:read | read-only | Account mailboxes (by address); account_id, page, per_page (max 50). |
| hosting-email-create | account:write | write | Create a mailbox; account_id, email_local, domain, password (write-only), quota_mb. Account must be active. |
| hosting-email-update | account:write | write | Change password (write-only) and/or quota; account_id, email_id, password?, quota_mb?. |
| hosting-email-delete | account:write | write | Delete a mailbox (DA + local); account_id, email_id. Account must be active. |
| hosting-backups | account:read | read-only | Account backups (id DESC); account_id, page, per_page (max 50). |
| hosting-backup-create | account:write | write | Queue a backup; account_id. Async — poll hosting-backups. Account must be active. |
| hosting-backup-restore | account:delete | destructive (confirm) | Restore a backup — overwrites account data. Requires confirm: true; account_id, backup_id. Async. |
| hosting-issue-ssl | account:write | write | Queue a Let's Encrypt certificate; account_id. Async — poll hosting-get. Account must be active. |
| hosting-terminate | account:delete | destructive (confirm) | Terminate the account — irreversible. Requires confirm: true; account_id. Async. |
Examples — MCP (JSON-RPC)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "hosting-email-create",
"arguments": {
"account_id": 7,
"email_local": "office",
"domain": "example.com",
"password": "S3cretPass!23",
"quota_mb": 2000
}
}
}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "hosting-backup-restore",
"arguments": { "account_id": 7, "backup_id": 128, "confirm": true }
}
}