Instances
Create, list, inspect and terminate VPS instances, and follow their status.
An instance is one virtual server. Creating one charges the wallet and starts provisioning in the background; you then poll the instance until it is running.
The instance object
{
"id": "1f757fd5-98d1-490a-81a0-58b5798a5815",
"projectId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"planId": "4db565ff-782f-46c8-9324-e4670517233f",
"name": "web-1",
"hostname": "web-1",
"status": "running",
"osType": "ubuntu",
"osVersion": "24.04",
"publicIp": "203.0.113.24",
"cpuCores": 2,
"ramMb": 2048,
"diskGb": 40,
"billingPeriod": 1,
"priceSnapshot": "64900.00",
"autoRenew": true,
"expiresAt": "2026-11-15T08:22:12.000Z",
"provisionedAt": "2026-10-16T08:24:40.000Z",
"terminatedAt": null,
"lastJobType": "vps:create",
"lastJobStatus": "success",
"lastJobError": null,
"lastJobAt": "2026-10-16T08:22:13.000Z",
"createdAt": "2026-10-16T08:22:12.000Z",
"updatedAt": "2026-10-16T08:24:40.000Z",
"plan": {
"id": "4db565ff-782f-46c8-9324-e4670517233f",
"name": "VPS S - Jakarta",
"slug": "vps-s-jkt",
"priceMonthly": "64900.00",
"yearlyDiscount": 15,
"providerCode": 2
}
}| Field | Type | Description |
|---|---|---|
id | string | Instance id, used in every /vps/:id route. |
projectId | string | null | Project the server belongs to. |
planId, plan | string, object | Current plan. plan.providerCode is an opaque number. |
name | string | Name you gave it. |
hostname | string | Lowercase hostname derived from name. |
status | string | See Statuses. |
osType, osVersion | string | Installed operating system. |
publicIp | string | Public IPv4 address. It is the text "pending" until provisioning has finished. |
cpuCores, ramMb, diskGb | number | Current size. |
billingPeriod | number | Length of the paid period in months: 1, 3, 6 or 12. |
priceSnapshot | string | Price paid for one period, in Rupiah. A renewal for the same period length charges this amount again. |
autoRenew | boolean | Whether the server renews itself from the wallet before it expires. |
expiresAt | string | End of the paid period (ISO 8601). |
provisionedAt | string | null | When the server first came up. |
terminatedAt | string | null | When it was terminated. |
lastJobType | string | null | Last background job, for example vps:create, vps:start, vps:reinstall, vps:renew. |
lastJobStatus | string | null | pending while it runs, then success or error. |
lastJobError | string | null | Reason the last job failed. |
createdAt, updatedAt | string | Timestamps (ISO 8601). |
Responses can contain more fields than the ones listed here. Undocumented fields are internal: they can change or disappear without notice, so do not build on them.
Statuses
| Status | Meaning | Busy |
|---|---|---|
pending | Being created. | yes |
provisioning | Being created, or busy with a start, reinstall, upgrade or snapshot restore. | yes |
running | Up. | no |
stopping | Shutting down. | yes |
stopped | Powered off. | no |
restarting | Rebooting. | yes |
error | The create or terminate failed. lastJobError says why. | no |
terminated | Deleted. Kept in lists for your records. | no |
While a server is busy (or a backup, restore or snapshot is in progress on it), calls that change it answer 409 VPS_BUSY. Billing calls (renew, auto-renew) and reads are never blocked.
When any other job fails, the server does not go to error: a failed start, stop, restart, reinstall, snapshot rollback or upgrade puts back the status the server had before the call, and a failed renewal keeps the status as it is. lastJobStatus becomes error and lastJobError says why. Jobs that are not power actions (reset password, firewall, snapshots, renewals) never change the power status, so a stopped server stays stopped.
Polling
Poll GET /vps/:id every 5 to 10 seconds after an action until the status is running, stopped, terminated or error. Creating a server usually takes a few minutes; power actions take seconds to a minute. Check lastJobStatus and lastJobError to see how the last job ended.
async function waitForVps(id, key, { timeoutMs = 10 * 60_000 } = {}) {
const settled = ['running', 'stopped', 'terminated', 'error'];
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`https://api.helipod.io/api/vps/${id}`, {
headers: { Authorization: `Bearer ${key}` },
});
const body = await res.json();
if (!body.success) throw new Error(`${body.code}: ${body.error}`);
if (settled.includes(body.data.status)) return body.data;
await new Promise((r) => setTimeout(r, 5000));
}
throw new Error('Timed out waiting for the VPS');
}List instances
/vpsProp
Type
curl "https://api.helipod.io/api/vps?status=running" \
-H "Authorization: Bearer hel_your_key"{
"success": true,
"data": [
{
"id": "1f757fd5-98d1-490a-81a0-58b5798a5815",
"projectId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "web-1",
"status": "running",
"publicIp": "203.0.113.24",
"cpuCores": 2,
"ramMb": 2048,
"diskGb": 40,
"osType": "ubuntu",
"osVersion": "24.04",
"billingPeriod": 1,
"priceSnapshot": "64900.00",
"autoRenew": true,
"expiresAt": "2026-11-15T08:22:12.000Z",
"createdAt": "2026-10-16T08:22:12.000Z",
"plan": {
"id": "4db565ff-782f-46c8-9324-e4670517233f",
"name": "VPS S - Jakarta",
"slug": "vps-s-jkt",
"priceMonthly": "64900.00",
"yearlyDiscount": 15,
"providerCode": 2
}
}
]
}Terminated servers stay in the list with status: "terminated". Filter with ?status= to hide them. A projectId you do not own returns an empty list.
Get an instance
/vps/:idcurl https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815 \
-H "Authorization: Bearer hel_your_key"Returns the instance object. An unknown id, another account's server, or a server outside the key's project answers 404 INSTANCE_NOT_FOUND.
Rename an instance
/vps/:idProp
Type
curl -X PATCH https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815 \
-H "Authorization: Bearer hel_your_key" \
-H "Content-Type: application/json" \
-d '{ "name": "web-2" }'{ "success": true, "data": { "id": "1f757fd5-98d1-490a-81a0-58b5798a5815", "name": "web-2" } }A name that breaks the rule, or a terminated server, answers 400 REQUEST_ERROR.
Metrics
/vps/:id/metricsThe answer has one of two shapes, told apart by source.
Time series for the chosen range: 1h (default), 6h, 24h, 7d or 30d.
curl "https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815/metrics?range=24h" \
-H "Authorization: Bearer hel_your_key"{
"success": true,
"data": {
"source": "monitoring",
"range": "24h",
"periodSeconds": 300,
"series": {
"cpuPercent": [[1760600000000, 12.5], [1760600300000, 9.8]],
"memoryPercent": [[1760600000000, 41.2], [1760600300000, 40.9]],
"networkOutBytesPerSec": [[1760600000000, 5120], [1760600300000, 4210]]
},
"summary": {
"cpuCores": 2, "memoryGb": 4, "diskGb": 60, "bandwidthMbps": 30,
"traffic": { "usedBytes": 1073741824, "totalBytes": 536870912000, "remainingBytes": 535797170176, "overflowBytes": 0, "periodStart": "2026-10-01T00:00:00Z", "periodEnd": "2026-11-01T00:00:00Z" }
}
}
}Each point is [timestamp in milliseconds, value]. The series that can appear are cpuPercent, memoryPercent, memoryUsedBytes, diskPercent, load1, load5, load15, networkInBytesPerSec, networkOutBytesPerSec, packetsInPerSec, packetsOutPerSec, bandwidthOutPercent and tcpConnections. summary.traffic is the monthly traffic allowance, when the plan has one.
A server that offers neither answers 409 FEATURE_NOT_AVAILABLE.
Create an instance
/vpscharges your walletProp
Type
Password rules. 12 to 30 characters from letters, numbers and the symbols ! @ # $ % ^ *, with at least one uppercase letter, one lowercase letter, one number and one of those symbols. No spaces. The same rule applies to reinstall and password reset.
curl -X POST https://api.helipod.io/api/vps \
-H "Authorization: Bearer hel_your_key" \
-H "Content-Type: application/json" \
-d '{
"planId": "4db565ff-782f-46c8-9324-e4670517233f",
"projectId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"name": "web-1",
"osType": "ubuntu",
"osVersion": "24.04",
"rootPassword": "Str0ng!Passw0rd",
"billingPeriod": "monthly"
}'Response 201
{
"success": true,
"data": {
"instance": {
"id": "1f757fd5-98d1-490a-81a0-58b5798a5815",
"projectId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"planId": "4db565ff-782f-46c8-9324-e4670517233f",
"name": "web-1",
"hostname": "web-1",
"status": "provisioning",
"osType": "ubuntu",
"osVersion": "24.04",
"publicIp": "pending",
"cpuCores": 2,
"ramMb": 2048,
"diskGb": 40,
"billingPeriod": 1,
"priceSnapshot": "64900.00",
"autoRenew": true,
"expiresAt": "2026-11-15T08:22:12.000Z",
"lastJobType": "vps:create",
"lastJobStatus": "pending",
"createdAt": "2026-10-16T08:22:12.000Z"
},
"password": "Str0ng!Passw0rd"
}
}What happens
- The request is checked: project, plan, operating system, name, password, the billing period, the account's server limit (10 servers by default; terminated servers do not count) and the wallet balance. A failed check charges nothing.
- The wallet is charged for the whole period and the server is stored with
status: "provisioning"andpublicIp: "pending".expiresAtis set 30, 90, 180 or 365 days ahead. - Provisioning runs in the background. Poll
GET /vps/:iduntilstatusisrunning;publicIpis then filled in. The image's default firewall rules are recorded at that point (see Firewall). - If provisioning fails, the status becomes
error,lastJobErrorexplains why, and the charge is refunded to the wallet. Terminate the failed server and try again.
Retrying a create is not safe: there is no idempotency key, and each successful call charges the wallet. If a create timed out, list your servers first and check whether it was created.
Errors
| Status | Code | When |
|---|---|---|
422 | VALIDATION | A required field is missing, or name or rootPassword is out of range. |
400 | PROJECT_REQUIRED | projectId is empty. |
404 | PROJECT_NOT_FOUND | The project does not exist, is not yours, or is outside the key's project. |
400 | INVALID_BILLING_PERIOD | billingPeriod is not monthly, 3month, 6month or yearly. |
402 | INSUFFICIENT_BALANCE | The wallet cannot cover the period. error gives the required and available amounts. |
400 | REQUEST_ERROR | Invalid name or password, unknown plan or operating system, or server limit reached. Read error. |
Terminate an instance
/vps/:idcurl -X DELETE https://api.helipod.io/api/vps/1f757fd5-98d1-490a-81a0-58b5798a5815 \
-H "Authorization: Bearer hel_your_key"{ "success": true, "data": { "success": true } }Allowed when the server is running, stopped or error. The status changes to terminated immediately and the server and its disk are deleted in the background. This cannot be undone and the unused part of the period is not refunded. Copy off anything you need first, for example with a backup download.
Returns 409 VPS_BUSY while the server is busy, and 400 REQUEST_ERROR when it is already terminated or in a status that cannot be terminated.