# Appendix A — Error Codes

The `error_code` field is stable across releases. Treat it as the
machine identifier and surface `message` only to humans.



_242 codes, generated from the agent source by `agent/scripts/gen_error_codes.py`; do not edit by hand._

### Authentication & session

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `AUTH_2FA_INVALID` | 400 / 401 | `auth`, `exceptions`, `profile` | Invalid 2FA code |
| `AUTH_2FA_LOCKED` | 429 | `auth`, `exceptions` | Too many failed 2FA attempts, temporary lock |
| `AUTH_CAPTCHA_FAILED` | 400 | `auth` | reCAPTCHA verification failed (low score or invalid token) |
| `AUTH_EXPIRED` | 401 | `auth`, `exceptions` | Timestamp drift > 300 s, or session cookie expired. |
| `AUTH_FAILED` | 401 / 403 / 404 | `api_keys`, `auth`, `backups` +20 | Invalid API key, signature, or session cookie. |
| `AUTH_LOCKED` | 429 | `auth` | Too many failed sign-in attempts for this identity; retry after the `Retry-After` seconds. |
| `AUTH_RATE_LIMITED` | 429 | `exceptions` | Auth rate limited |
| `AUTH_REQUIRED` | 401 | `license` | Endpoint requires authentication and none was provided. |
| `CROSS_ORIGIN_DENIED` | 403 | `main` | Request origin is not allowed for this action |

### API keys

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `API_KEY_LIMIT` | 403 | `exceptions` | Api key limit |
| `API_KEY_NOT_FOUND` | 404 | `exceptions` | Api key not found |
| `API_KEY_REVOKED` | 403 | `exceptions` | Api key revoked |
| `HMAC_FORBIDDEN_FOR_CREDENTIAL_MUTATION` | 403 | `accounts`, `client_accounts`, `exceptions` +1 | Credential change needs a session cookie: account, sub-account and root password (403) |
| `SCOPE_DENIED` | 403 | `audit_logs`, `exceptions` | API key not found |
| `SCOPE_LOOKUP_FAILED` | 500 | `audit_logs` | Could not resolve API key scopes |

### Request validation & framework

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `BUNDLE_MISMATCH` | 403 | `main` | Browser-only: the bundle fingerprint does not match. SDKs are exempt. |
| `HTTP_405` | 405 | `node_apps`, `python_apps` | Method Not Allowed |
| `HTTP_<status>` | 404 / 405 | `main` | Plain HTTP errors from the framework (unknown route, method not allowed) wrapped in the standard envelope; the code carries the status. |
| `IDEMPOTENCY_KEY_INVALID` | 400 | `main` | Header value doesn't match `^[A-Za-z0-9_-]{8,64}$`. |
| `IDEMPOTENCY_KEY_REUSED` | 409 | `main` | Same key replayed by the same credential with a **different method, path or body**. |
| `NOT_FOUND` | 404 | `exceptions`, `node_apps`, `profile` | A named resource does not exist. |
| `PAYLOAD_TOO_LARGE` | 413 | `main` | Request body too large ( MB >  MB limit) |
| `VALIDATION_ERROR` | 400 / 422 | `auth`, `cron`, `emails` +7 | Pydantic schema rejection. `details` carries field-level reasons. |

### Firewall & security services

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `ATTACK_MODE_ACTIVE` | 409 | `exceptions` | Under-attack mode is already active (409) |
| `ATTACK_MODE_INACTIVE` | 409 | `exceptions` | Under-attack mode is not currently active (409) |
| `BANNED_WORD` | 403 | `exceptions` | Banned word |
| `FAIL2BAN_ERROR` | 500 | `exceptions` | Fail2Ban service error |
| `FAIL2BAN_JAIL_NOT_FOUND` | 404 | `exceptions` | Fail2Ban jail not found |
| `IP_ADD_FAILED` | 500 | `system_ip_service` | Failed to add IP  to : |
| `IP_DELETE_FAILED` | 500 | `system_ip_service` | Failed to remove IP  from : |
| `IP_NOT_ALLOWED` | 403 | `exceptions`, `main` | Source IP is not in the API key's allowlist. |
| `IP_WHITELISTED` | 409 | `exceptions` | The address is whitelisted; the block would sit below the allow rule and never match. Remove it from the whitelist first (409). |
| `IP_WHITELIST_SELF_LOCKOUT` | 400 | `system` | Admin IP list does not cover the caller (400; force=true overrides) |
| `RATE_LIMIT` | 429 | `main`, `node_apps` | Tier limit exceeded; honour `Retry-After`. |
| `RATE_LIMIT_APPLY_FAILED` | 500 | `exceptions` | The kernel refused the rule's ipset / iptables objects; nothing was recorded (500). |
| `SELF_LOCKOUT` | 422 | `validators` | Deny/block covers the caller's own address (422) |
| `WAF_CONFIG_ERROR` | 500 | `exceptions`, `modsecurity_service` | ModSecurity configuration syntax error |
| `WAF_MODULE_ERROR` | 500 | `exceptions` | Webserver ModSecurity module load error |
| `WAF_NOT_INSTALLED` | 503 | `exceptions`, `modsecurity_service` | ModSecurity is not installed |
| `WAF_RULE_NOT_FOUND` | 404 | `exceptions`, `modsecurity_service` | WAF rule ID not found |
| `WAF_TEST_UNAVAILABLE` | 409 | `exceptions` | No ModSecurity config test for the running webserver (409) |
| `WORDLIST_NOT_FOUND` | 404 | `exceptions` | Wordlist not found |

### Accounts, plans & resellers

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `ACCOUNT_EXISTS` | 409 | `exceptions` | Username already provisioned. |
| `ACCOUNT_NOT_FOUND` | 404 | `auth`, `exceptions`, `profile` | Username unknown. |
| `ACCOUNT_SUSPENDED` | 403 | `exceptions` | The account is suspended; until it is resumed its cron jobs cannot be changed or run and no domain (addon, subdomain, parked) can be added to it. |
| `ACCOUNT_TERMINATED` | 400 | `auth` | The target account is terminated and cannot be impersonated. |
| `DISK_LIMIT` | 403 | `exceptions` | Disk quota exceeded |
| `DISK_QUOTA_EXCEEDED` | 403 | `exceptions` | Disk quota exceeded |
| `PLAN_EXISTS` | 409 | `exceptions` | Plan exists |
| `PLAN_IN_USE` | 409 | `exceptions` | The hosting plan or reseller ACL plan is still assigned to accounts and cannot be deleted; `details` carries the count. |
| `PLAN_LIMIT` | 403 | `exceptions` | Plan limit exceeded |
| `PLAN_NOT_FOUND` | 404 | `exceptions` | Hosting plan not found |
| `RESELLER_LIMIT` | 403 | `exceptions` | Reseller resource limit exceeded |
| `RESELLER_NOT_FOUND` | 404 | `exceptions` | Reseller not found |

### Domains, DNS, SSL & PHP

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `ADDON_DOMAIN_LIMIT` | 403 | `exceptions` | Addon domain limit exceeded |
| `DNS_ERROR` | 500 | `exceptions` | PowerDNS API failure. |
| `DNS_RECORD_NOT_FOUND` | 404 | `exceptions` | Dns record not found |
| `DNS_RECORD_PROTECTED` | 409 | `exceptions` | The zone's SOA record and its last NS record are managed by the server: the SOA cannot be edited or deleted, the last NS cannot be deleted. |
| `DNS_ZONE_EXISTS` | 409 | `exceptions` | Dns zone exists |
| `DNS_ZONE_NOT_FOUND` | 404 | `exceptions` | Dns zone not found |
| `DOMAIN_EXISTS` | 409 | `exceptions` | Domain already mounted on this server. |
| `DOMAIN_NOT_FOUND` | 404 | `exceptions` | Domain unknown. |
| `DOMAIN_NOT_OWNED` | 403 | `emails`, `exceptions`, `logs` | Domain '' is not registered to account ''. |
| `PARKED_DOMAIN_EXISTS` | 409 | `exceptions` | Parked domain already exists |
| `PARKED_DOMAIN_LIMIT` | 403 | `exceptions` | Parked domain limit |
| `REDIRECT_NOT_FOUND` | 404 | `exceptions` | Redirect not found |
| `SSL_EMAIL_REQUIRED` | 400 | `exceptions` | Ssl email required |
| `SSL_ERROR` | 500 | `exceptions` | Let's Encrypt or custom certificate operation failed. |
| `SSL_EXISTS` | 409 | `exceptions` | Ssl exists |
| `SSL_NOT_FOUND` | 404 | `exceptions` | Ssl not found |
| `SUBDOMAIN_LIMIT` | 403 | `exceptions` | Subdomain limit exceeded |
| `VHOST_CONFIG_ERROR` | 500 | `exceptions` | Custom VHost configuration error (syntax invalid or apply failed) |

### Databases

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `DATABASE_EXISTS` | 409 | `exceptions` | Database name collision. |
| `DATABASE_LIMIT` | 403 | `exceptions` | Plan database limit reached. |
| `DATABASE_NOT_FOUND` | 404 | `databases`, `exceptions` | Database '' not found. |
| `DATABASE_SSO_DISABLED` | 503 | `exceptions` | Database sso disabled |
| `DATABASE_USER_EXISTS` | 409 | `exceptions` | Database user exists |
| `DATABASE_USER_NOT_FOUND` | 404 | `exceptions` | Database user not found |
| `INTERNAL_DB_PROTECTED` | 403 | `exceptions` | Internal db protected |
| `PMA_INSTALL_FAILED` | 500 | `phpmyadmin_service` | phpMyAdmin installation failed |
| `PMA_NOT_INSTALLED` | 500 | `databases`, `phpmyadmin_service` | phpMyAdmin is not installed |
| `PMA_SSO_FAILED` | 500 | `databases` | Could not provision a phpMyAdmin session. |
| `REMOTE_DB_DENIED` | 403 | `exceptions` | Remote database access denied |
| `REMOTE_DB_HOST_EXISTS` | 409 | `exceptions` | Remote db host exists |

### Email

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `EMAIL_EXISTS` | 409 | `exceptions` | Email account already exists. |
| `EMAIL_FORWARDER_EXISTS` | 409 | `exceptions` | A forwarder to that destination already exists. |
| `EMAIL_FORWARDER_NOT_FOUND` | 404 | `exceptions` | Forwarder not found for this mailbox. |
| `EMAIL_INACTIVE` | 409 | `emails`, `exceptions` | The email account is inactive; activate it first. |
| `EMAIL_LIMIT` | 403 | `exceptions` | The plan's email account limit is reached. |
| `EMAIL_NOT_CONFIGURED` | 400 / 500 | `auth`, `profile` | Admin email address is not configured for 2FA |
| `EMAIL_NOT_FOUND` | 404 | `emails`, `exceptions`, `spam` | Email account not found for this hosting account. |
| `EMAIL_SEND_FAILED` | 500 | `notifications` | Failed to send test email. Check SMTP settings. |
| `MAIL_QUEUE_MESSAGE_NOT_FOUND` | 404 | `exceptions` | No message with that id is in the mail queue. |
| `SMTP_NOT_CONFIGURED` | 400 | `profile` | SMTP settings must be configured before using email 2FA |
| `SMTP_PASSWORD_REQUIRED` | 400 | `notifications` | The SMTP server, port, user or encryption changed while the password field still held the masked stored password: type the password again. |
| `SMTP_TARGET_FORBIDDEN` | 400 | `notifications` | SMTP relay host/port refused (non-submission port or metadata address) |

### FTP

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `FTP_ACCOUNT_EXISTS` | 409 | `exceptions` | FTP account already exists. |
| `FTP_ACCOUNT_NOT_FOUND` | 404 | `exceptions` | Ftp account not found |
| `FTP_LIMIT` | 403 | `exceptions` | Plan FTP account limit reached. |
| `FTP_NOT_ORPHAN` | 409 | `exceptions` | Ftp not orphan |

### Files

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `FILE_EXISTS` | 409 | `exceptions` | File exists |
| `FILE_NOT_FOUND` | 404 | `exceptions`, `node_apps` | File or directory does not exist. |
| `FILE_TOO_LARGE` | 400 / 413 | `branding_assets`, `exceptions`, `node_apps` +2 | Uploaded file exceeds size limit |
| `PATH_TRAVERSAL` | 403 | `exceptions` | `..`, null byte, or symlink escape detected. |
| `PROTECTED_PATH` | 403 | `exceptions` | Protected path |
| `UNSUPPORTED_ARCHIVE` | 422 | `exceptions` | Unsupported archive |

### Cron

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `CRON_JOB_EXISTS` | 409 | `exceptions` | Cron job exists |
| `CRON_JOB_NOT_FOUND` | 404 | `exceptions` | Cron job not found |
| `CRON_RUN_TIMEOUT` | 408 | `exceptions` | The manual run did not finish within 85 seconds and was stopped; the scheduled runs are not affected. |

### Backups

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `BACKUP_DISABLED` | 403 | `exceptions` | The backup system is switched off in the server settings; backups, restores and schedules are refused until it is switched on. |
| `BACKUP_ERROR` | 500 | `exceptions` | Backup creation or restore failed. |
| `BACKUP_FILE_MISSING` | 410 | `exceptions` | The backup is listed but its archive file is gone from disk; it can only be deleted. |
| `BACKUP_IN_PROGRESS` | 409 | `exceptions` | A backup, a restore or a scheduled backup is running; an update would cut it with its restart. The message names the work and when it started — install after it finishes. |
| `BACKUP_NOT_FOUND` | 404 | `exceptions` | Backup not found |
| `BACKUP_SCHEDULE_LIMIT` | 403 | `exceptions` | Backup schedule limit |
| `BACKUP_SCHEDULE_NOT_FOUND` | 404 | `exceptions` | Backup schedule not found |
| `BUNNY_AUTH_FAILED` | 400 | `system` | Bunny.net refused the account API key (400). |
| `BUNNY_FETCH_FAILED` | 502 | `system` | The storage zone list could not be fetched from Bunny.net (502). |
| `GOOGLE_DRIVE_AUTH_FAILED` | 400 | `system` | Google refused the authorization code exchange, or the state of the sign-in did not match (400). |
| `GOOGLE_DRIVE_NO_CREDENTIALS` | 500 | `remote_backup_service` | The Google Drive destination has neither an OAuth refresh token nor a service-account file. |
| `ONEDRIVE_AUTH_FAILED` | 400 | `system` | Microsoft refused the authorization code exchange, or the state of the sign-in did not match (400). |
| `REMOTE_BACKUP_AUTH_FAILED` | 500 | `remote_backup_service` | No access token returned from Microsoft |
| `REMOTE_BACKUP_DOWNLOAD_FAILED` | 500 | `remote_backup_service` | The provider refused or dropped the download. |
| `REMOTE_BACKUP_HOST_FORBIDDEN` | 422 | `exceptions` | The destination host resolves to a private, loopback or metadata address and is refused (422). |
| `REMOTE_BACKUP_NOT_FOUND` | 404 | `remote_backup_service` | The file is not on the remote destination. |
| `REMOTE_BACKUP_UPLOAD_FAILED` | 500 | `remote_backup_service` | The provider refused or dropped the upload; the local archive is complete. |
| `REMOTE_DESTINATION_DISABLED` | 409 | `exceptions` | The remote destination is switched off; enable it or pick another one. |
| `REMOTE_DESTINATION_EXISTS` | 500 | `remote_backup_service` | A destination with the same id already exists. |
| `REMOTE_DESTINATION_IN_USE` | 409 | `exceptions` | A backup schedule still uses the destination; edit or remove the schedule first (details.schedules). |
| `REMOTE_DESTINATION_NOT_FOUND` | 404 | `remote_backup_service` | No remote destination with that id in the caller's store. |
| `REMOTE_PROVIDER_UNKNOWN` | 500 | `remote_backup_service` | The stored destination names a provider type the agent does not know. |
| `YANDEX_DISK_AUTH_FAILED` | 400 | `system` | Yandex refused the authorization code exchange, or the state of the sign-in did not match (400). |

### Plugins & LiteSpeed

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `PLUGIN_ACTIVATION_ERROR` | 500 | `plugins` | Activation pipeline failed (vhost migrate, package install, …). |
| `PLUGIN_DEACTIVATION_ERROR` | 500 | `plugins` | Deactivation rollback failed. |
| `PLUGIN_DISABLED` | 503 | `node_apps`, `python_apps` | Node.js Hosting plugin is not active. Enable it at Admin → Plugins to use this feature. |
| `PLUGIN_LIST_ERROR` | 500 | `plugins` | Plugin list error |
| `PLUGIN_NOT_FOUND` | 404 | `plugins` | Unknown plugin id. |
| `PLUGIN_TASK_IN_PROGRESS` | 409 | `plugins` | Concurrent activation attempt rejected. |
| `PLUGIN_TASK_NOT_FOUND` | 404 | `plugins` | Async task id unknown or expired. |
| `PLUGIN_UPDATE_ERROR` | 500 | `plugins` | Plugin update error |
| `PLUGIN_UPDATE_NOT_SUPPORTED` | 400 | `plugins` | Plugin does not implement `update()`. |
| `PLUGIN_VALIDATION_ERROR` | 400 | `plugins` | Bad mode / serial / state for the requested operation. |

### Python & Node.js apps

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `NODE_APP_EXISTS` | 409 | `exceptions` | Node app exists |
| `NODE_APP_LIMIT` | 403 | `exceptions` | Node app limit |
| `NODE_APP_NOT_FOUND` | 404 | `exceptions` | Node app not found |
| `NODE_APP_START_ERROR` | 500 | `exceptions` | Node app start error |
| `NODE_NPM_ERROR` | 500 | `exceptions` | Node npm error |
| `NODE_SCRIPT_DENIED` | 403 | `exceptions` | Node script denied |
| `NODE_VERSION_NOT_FOUND` | 400 | `exceptions` | Node version not found |
| `NODE_WORKER_LIMIT` | 403 | `exceptions` | Node worker limit |
| `PYTHON_APP_EXISTS` | 409 | `exceptions` | Python app with this name already exists for account |
| `PYTHON_APP_LIMIT` | 403 | `exceptions` | Maximum Python apps reached for this account |
| `PYTHON_APP_NOT_FOUND` | 404 | `exceptions` | Python app not found |
| `PYTHON_APP_START_ERROR` | 500 | `exceptions` | Application server failed to start |
| `PYTHON_MANAGE_CMD_DENIED` | 403 | `exceptions` | manage.py command not in allowed list |
| `PYTHON_PIP_ERROR` | 500 | `exceptions` | pip install/uninstall failed |
| `PYTHON_VENV_ERROR` | 500 | `exceptions` | Virtualenv creation/management failed |
| `PYTHON_VERSION_NOT_FOUND` | 400 | `exceptions` | Requested Python version not installed on server |
| `PYTHON_WORKER_LIMIT` | 403 | `exceptions` | Worker count exceeds plan limit |

### Updates & migration

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `MIGRATION_API_ERROR` | 502 | `exceptions` | Migration api error |
| `MIGRATION_BACKUP_INVALID` | 400 | `exceptions` | The backup archive fetched from the source WHost server during a migration could not be read. |
| `MIGRATION_CONNECTION_FAILED` | 400 | `exceptions` | Migration connection failed |
| `MIGRATION_CREDENTIALS_EXPIRED` | 400 | `migration` | SSH credentials have expired or were not found. Please re-scan the server. |
| `MIGRATION_INVALID_STATE` | 400 | `exceptions` | Migration invalid state |
| `MIGRATION_JOB_NOT_FOUND` | 404 | `exceptions` | Migration job not found |
| `MIGRATION_PANEL_UNKNOWN` | 400 | `exceptions` | Cannot detect cPanel/DA on source server |
| `MIGRATION_SCAN_FAILED` | 500 | `exceptions` | Migration scan failed |
| `MIGRATION_SECURITY_ERROR` | 400 | `exceptions` | Migration security error |
| `MIGRATION_SSH_FAILED` | 400 | `exceptions` | SSH connection to source server failed |
| `MIGRATION_UPLOAD_RESTORE_UNAVAILABLE` | 409 | `exceptions` | Restoring from an uploaded backup file is not offered; migrate the accounts over SSH (`POST /migration/scan`, then `POST /migration/start`). |
| `OS_UPGRADE_IN_PROGRESS` | 409 | `exceptions` | OS package upgrade already running (lock file held). |
| `UPDATE_ALREADY_LATEST` | 400 | `exceptions` | Returned in response body when no upgrade is available. |
| `UPDATE_CHECK_REQUIRED` | 400 | `exceptions` | Returned when an install is requested before any update check has run in this agent process; call /system/update/check first. |
| `UPDATE_DOWNGRADE_REJECTED` | 400 | `exceptions` | Update downgrade rejected |
| `UPDATE_INSTALL_FAILED` | 500 | `exceptions` | WHost update pipeline aborted; rollback completed. |
| `UPDATE_INVALID_VERSION` | 400 | `exceptions` | Update invalid version |
| `UPDATE_IN_PROGRESS` | 409 | `exceptions` | An install is already running: a second install is refused, and so are a backup and a restore until the install's restart. |

### License

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `LICENSE_ACTIVATION_FAILED` | 400 | `exceptions` | License key rejected by the license service. |
| `LICENSE_ADDON_REQUIRED` | 403 | `whitelabel` | The write needs a licence addon the current licence does not carry (whitelabel); the stored values are left as they are (403) |
| `LICENSE_NOT_ACTIVATED` | 403 | `exceptions`, `license_gate` | License key has never been activated. |
| `LICENSE_SERVER_UNREACHABLE` | 503 | `exceptions` | License server unreachable (grace period may apply) |
| `LICENSE_SUSPENDED` | 403 | `exceptions`, `license_check`, `license_gate` | License is suspended, expired or invalid. |
| `LICENSE_VERIFY_FAILED` | 400 | `exceptions` | License verification failed (explicit FAILED from server) |
| `SERVER_UNREACHABLE` | — | `wlicense_client` | Server unreachable |

### Webhooks

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `DELIVERY_NOT_FOUND` | 404 | `webhooks` | Unknown webhook delivery id (404) |
| `DELIVERY_PAYLOAD_MISSING` | 400 | `webhooks` | Delivery cannot be retried: original payload no longer stored (400) |
| `WEBHOOK_DISABLED` | 409 | `webhooks` | The webhook endpoint is disabled or muted; enable it before a test event or a retry can be addressed at it (409) |
| `WEBHOOK_NOT_FOUND` | 404 | `webhooks` | Unknown webhook endpoint id (404) |
| `WEBHOOK_VALIDATION` | 400 | `webhooks` | Webhook endpoint refused: private/loopback URL, unknown event, weak secret (16-128 printable ASCII), malformed muted_until (400) |

### Notifications & profile

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `NOTIFICATION_NOT_FOUND` | 404 | `firewall`, `notifications` | Notification not found |
| `NOTIFICATION_SEND_FAILED` | 500 | `profile` | Notification could not be sent (SMTP error etc.) |
| `PROFILE_PASSWORD_MISMATCH` | 400 | `profile` | Current password is wrong |

### Background tasks

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `TASK_NOT_FOUND` | 404 | `node_apps`, `python_apps` | Deploy task id unknown to this account (python-apps / node-apps) |
| `TOO_MANY_TASKS` | 429 | `node_apps`, `python_apps` | Concurrent deploy limit reached for the account (429) |

### System & services

| Code | HTTP | Raised by | Description |
|------|------|-----------|-------------|
| `APACHE_CONFIG_TEST_FAILED` | 500 | `exceptions` | Apache config test failed |
| `APACHE_NOT_RUNNING` | 503 | `exceptions` | Apache not running |
| `APACHE_STATUS_ENABLE_FAILED` | 500 | `system` | Apache status enable failed |
| `APACHE_STATUS_ERROR` | 500 | `system` | Apache status error |
| `APACHE_STATUS_NOT_AVAILABLE` | 503 | `exceptions` | Apache status not available |
| `CODE_EXPIRED` | 400 | `profile` | Verification code has expired. Please resend. |
| `CONFIRM_REQUIRED` | 400 | `emails` | Server-wide queue wipe requires the confirm=ALL query parameter. |
| `DEPENDENCY_MISSING` | 500 | `profile` | pyotp is not installed |
| `FEATURE_DISABLED` | 409 | `exceptions` | The feature is switched off in the agent's configuration; the request is refused until it is enabled (409) |
| `FEATURE_UNAVAILABLE` | 403 | `firewall` | Firewall management is handled at the server level and is not available per account. |
| `FORBIDDEN` | 403 | `dns`, `php`, `spam` +1 | Domain does not belong to your account |
| `HOSTNAME_UPDATE_FAILED` | 500 | `system` | Hostname update failed |
| `IMAGE_DIMENSIONS_TOO_LARGE` | 400 | `branding_assets` | The uploaded image is larger than 25 megapixels; it is refused from its header, before any pixel is decoded. |
| `INTERNAL_ERROR` | 500 | `system` | Failed to change root password |
| `INVALID_FILE_TYPE` | 400 | `branding_assets`, `profile`, `python_apps` | Uploaded file has invalid MIME type or malformed SVG |
| `INVALID_HOSTNAME` | 422 | `system` | Hostname is not a valid FQDN (422) |
| `INVALID_IMAGE` | 400 | `branding_assets` | The uploaded file does not decode as the image type its content declares (truncated or damaged file). |
| `INVALID_IP` | 422 | `system` | Server address is not a routable unicast IPv4 (422) |
| `INVALID_JSON` | 400 | `main` | Request body is not valid JSON |
| `INVALID_LOGO_TYPE` | 400 | `whitelabel` | Invalid logo type parameter (valid: light, dark, favicon, favicon_dark) |
| `INVALID_OPERATION` | 400 | `profile` | Email 2FA setup is not in progress |
| `INVALID_QUEUE_ID` | 400 | `emails` | Invalid queue id |
| `INVALID_REQUEST` | 400 | `node_apps`, `python_apps` | Either 'archive' (file upload) or 'server_path' is required |
| `INVALID_TOKEN` | 400 | `auth` | Password-reset token is invalid, expired or already used. |
| `INVALID_VIDEO` | 400 | `branding_assets` | The uploaded sign-in page video is not a readable MP4: its box structure is broken or truncated, it has no video track, or its frame is outside 1-4096 px (400). |
| `IONCUBE_NOT_INSTALLED` | 503 | `exceptions` | IonCube Loader not installed for PHP version |
| `LIMIT_REACHED` | 403 | `domains` | Addon domain limit reached |
| `LOGIN_VIDEO_NOT_FOUND` | 404 | `whitelabel` | The sign-in page has no uploaded video or cover, so there is nothing to remove (404). |
| `LOGIN_VIDEO_REQUIRED` | 400 | `whitelabel` | A cover image was sent for a sign-in page that has no uploaded video; send the video first or together with the cover (400). |
| `LOGO_NOT_FOUND` | 404 | `whitelabel` | No whitelabel logo of that type is set, so there is nothing to remove (404) |
| `MISSING_RECIPIENT` | 400 | `notifications` | No recipient email address configured |
| `NGINX_CONFIG_FAILED` | 500 | `system` | Hostname change rolled back — Nginx config failed: |
| `NO_FILE` | 400 | `whitelabel` | No file provided in upload request |
| `PERMISSION_DENIED` | 403 | `auth`, `exceptions` | The caller is authenticated but lacks the required permission. |
| `PORT_IN_USE` | 422 | `system` | SSH port is held by the panel or another listener (422) |
| `REBOOT_SCHEDULE_FAILED` | 500 | `system` | Reboot schedule failed |
| `REMOTE_STORE_ENCRYPTION_FAILED` | 500 | `remote_backup_service` | The destination's credentials could not be encrypted, so nothing was saved. |
| `REMOTE_STORE_UNREADABLE` | 500 | `exceptions` | The remote destination store on disk is not valid JSON; nothing was written over it. Repair or restore the file. |
| `SERVICE_ERROR` | 500 | `exceptions` | systemd service management failure. |
| `SETUP_NOT_INITIATED` | 400 | `profile` | Call /2fa/setup first |
| `SYSTEM_ERROR` | 500 | `exceptions`, `main`, `profile` | Internal server error (catch-all; check agent log). |
| `TWO_FACTOR_ALREADY_ENABLED` | 409 | `profile` | 2FA is already enabled |
| `TWO_FACTOR_NOT_ENABLED` | 400 | `profile` | 2FA is not currently enabled |
| `UNSUPPORTED_MEDIA_TYPE` | 415 | `branding_assets`, `profile`, `whitelabel` | The uploaded file is not one of the types this upload accepts, judged from its content rather than the declared type: an avatar or a whitelabel image that is not PNG, JPEG or WebP (SVG too for logos), or a sign-in page video that is not an MP4 or whose video track is not H.264 (415). |
| `UPLOAD_TOO_LARGE` | 413 | `python_apps` | Upload is  MB — exceeds  MB limit. Upload via SSH/SFTP to /home// and use server_path mode. |
| `WEBMAIL_NOT_INSTALLED` | 503 | `emails`, `exceptions` | Roundcube webmail is not installed on this server. |
| `WEBMAIL_SSO_NOT_CONFIGURED` | 503 | `exceptions` | The webmail single sign-on bridge (Dovecot master user) is not configured. |
| `WEBSERVER_NOT_APACHE` | 422 | `exceptions` | Webserver not apache |



---
