Errors
Every error code the VPS API returns, with its HTTP status and what to do about it.
Every error uses the same envelope. Branch on code; error is a human-readable message you can show or log.
{
"success": false,
"error": "The VPS is restarting. Try again when it has finished.",
"code": "VPS_BUSY"
}Access
| Status | Code | Meaning | What to do |
|---|---|---|---|
401 | NO_API_KEY | No Authorization: Bearer header, or an empty key. | Send the key. |
401 | INVALID_API_KEY | The key is unknown or revoked. | Check the key, or create a new one. |
401 | API_KEY_EXPIRED | The key is past its expiry date. | Create a new key. |
403 | SCOPE_NOT_ALLOWED | The key does not include the VPS API product. | Create a key with VPS API enabled. |
429 | RATE_LIMITED | More than 10 requests per second with this key. | Wait for the Retry-After header (seconds), then retry. |
Request shape
| Status | Code | Meaning | What to do |
|---|---|---|---|
422 | VALIDATION | A body or query field is missing, has the wrong type, or is out of range (for example a name shorter than 3 characters). error names the field, such as Invalid request: /planId. | Fix the request. |
400 | PARSE | The body is not valid JSON. | Send JSON with Content-Type: application/json. |
Not found and scope
Anything the key cannot reach answers 404, whether it does not exist, belongs to another account, or lies outside the key's project. The API never answers 403 for a resource, so ids cannot be probed.
| Status | Code | Meaning |
|---|---|---|
404 | INSTANCE_NOT_FOUND | No server with this id within the key's reach. |
404 | PROJECT_NOT_FOUND | The projectId is not a project of the key owner, or not the key's own project. |
400 | PROJECT_REQUIRED | projectId is empty on create. |
404 | SNAPSHOT_NOT_FOUND | No snapshot with this id within the key's reach. |
404 | FIREWALL_RULE_NOT_FOUND | A firewall rule to remove does not exist. Nothing was removed. |
Server state
| Status | Code | Meaning | What to do |
|---|---|---|---|
409 | VPS_BUSY | The server is mid-action (being created, starting, stopping, restarting, reinstalling, upgrading), or a backup, restore, backup setup, snapshot or snapshot rollback is in progress on it. | Poll GET /vps/:id and retry when it is settled. |
VPS_BUSY applies to start, stop, restart, terminate, reinstall, reset password, upgrade, snapshots and firewall changes. Reads, renewals and auto-renew are never blocked.
Business rules
| Status | Code | Meaning |
|---|---|---|
400 | INVALID_BILLING_PERIOD | billingPeriod on create or renew is not monthly, 3month, 6month or yearly. |
402 | INSUFFICIENT_BALANCE | The wallet cannot cover a create, renew or upgrade. error gives the required and available amounts. |
400 | INVALID_FIREWALL_RULE | A firewall rule to add is invalid (protocol, action, a port outside 1 to 65535 or a reversed range, or a malformed IP or CIDR), or a remove request named neither ids nor rules. |
400 | INVALID_SNAPSHOT_NAME | A snapshot name is not 1 to 60 characters of letters, numbers, spaces, dots, dashes and underscores, starting with a letter or number. |
400 | REQUEST_ERROR | The request is well formed but another rule refused it. error says which. |
Common REQUEST_ERROR reasons:
- The account reached its server limit (terminated servers do not count).
- Unknown plan, or no image for the requested
osTypeandosVersion. - Invalid server name or root password (the message gives the rule: 12 to 30 characters, letters, numbers and
! @ # $ % ^ *). - The server is in a status the action does not accept, for example
Cannot start instance in 'running' status. - Upgrade to a plan in another region or family, a plan that is not larger, or an upgrade with nothing left to pay.
- Snapshot limit reached, snapshots not available on the plan, or a snapshot that is not ready or not on this server.
Nothing is charged when a request fails with one of these.
Failed jobs
A paid job that fails after the call returned is settled automatically, once:
| Job | On failure |
|---|---|
| Create | Charge refunded. Status error. |
| Renew | Charge refunded, previous expiresAt restored. The status does not change. |
| Upgrade | Charge refunded, old plan restored. Back to the status before the call. |
| Start, stop, restart, reinstall, snapshot rollback | Back to the status before the call. A reinstall keeps the old password. |
| Reset password | Status unchanged. The old password stays valid. |
In every case lastJobStatus is error and lastJobError says why.
Ports, SSH, metrics and upgrade options
| Status | Code | Meaning |
|---|---|---|
409 | FEATURE_NOT_AVAILABLE | The server does not offer this feature: port forwarding and SSH keys and access only exist on servers that share a public address, and upgrade options only on servers the API can upgrade. |
409 | INSTANCE_NOT_RUNNING | The change is applied on the running server. Start it first. |
400 | INVALID_PORT | A port outside the server's range, a wrong port for http (80) or https (443), or a missing or invalid domain. |
409 | PORT_TAKEN | The public port is already forwarded, or the domain already has a route. |
404 | PORT_NOT_FOUND | No port forward with this id on the server. |
400 | INVALID_SSH_KEY | Not a single-line OpenSSH public key of a supported type. |
400 | PRIVATE_KEY_REJECTED | A private key was sent. It is not stored. |
400 | SSH_KEY_LIMIT | The server already has 10 SSH keys. |
409 | SSH_KEY_EXISTS | The key is already on the server. |
404 | SSH_KEY_NOT_FOUND | No key with this fingerprint on the server. |
Backups
The backup routes add their own codes:
| Status | Code | Meaning |
|---|---|---|
404 | INSTANCE_NOT_FOUND | Unknown server, or one outside the key's reach. |
404 | NOT_FOUND | Unknown restore point or download. |
400 | NOT_ELIGIBLE | The server cannot use backups (not Linux, not provisioned, or terminated). |
400 / 409 | VPS_NOT_RUNNING | The server must be running. |
409 | AGENT_OFFLINE | The server's management agent does not answer. Restart the server and try again. |
409 | NODE_NOT_READY | Backups are not available on this server yet. |
404 | PLAN_NOT_FOUND | Unknown or retired backup planCode. |
409 | ALREADY_SUBSCRIBED | The server already has the backup add-on. |
409 | BONUS_IN_USE | A bundled backup bonus for this server is already being claimed. |
402 | INSUFFICIENT_BALANCE | The wallet cannot cover the backup charge. |
404 | NOT_SUBSCRIBED | No backup add-on, or nothing to read yet. |
409 | OVER_NEW_QUOTA | The smaller plan cannot hold the current backups. |
409 | NOT_RUNNABLE | The add-on is not in a status that runs backups. |
409 | BUSY | Another backup or restore is in progress. |
429 | LIMIT | Daily limit of manual backups or downloads reached. |
409 | NO_BLUEPRINT | The operating system cannot be determined for a full restore. |
503 | BACKUP_NOT_CONFIGURED | Backups are temporarily unavailable. |
400 / 404 / 409 | BACKUP_ERROR | Any other backup rule, for example an invalid path. Read error. |
A path longer than 1024 characters on the file list answers 422 VALIDATION.
Server errors
| Status | Code | Meaning | What to do |
|---|---|---|---|
500 | INTERNAL_ERROR | Something failed on our side. The message is generic on purpose. | Retry with backoff. For a create, list your servers before retrying so you do not buy twice. |
Handling errors
async function vpsApi(path, { method = 'GET', body, key } = {}) {
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch(`https://api.helipod.io/api${path}`, {
method,
headers: {
Authorization: `Bearer ${key}`,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (json.success) return json.data;
if (json.code === 'RATE_LIMITED') {
const wait = Number(res.headers.get('retry-after') ?? 1);
await new Promise((r) => setTimeout(r, wait * 1000));
continue;
}
if (json.code === 'VPS_BUSY') {
await new Promise((r) => setTimeout(r, 10_000));
continue;
}
throw Object.assign(new Error(json.error), { code: json.code, status: res.status });
}
throw new Error('Gave up after 5 attempts');
}The helper retries RATE_LIMITED and VPS_BUSY only. INTERNAL_ERROR is safe to retry for reads and power actions. Do not retry paid calls (POST /vps, renew, upgrade, backup activation) blindly: check the current state first.