Docs / API
VPS API
Order, manage and renew VPS from your own code.
The API lets you order, manage and renew VPS from your own code: a reseller panel, a bot, a script. It also shows the balance and the transaction history. Domains, DNS and protection rules are not managed through the API, only in the panel. Access needs a token.
Basics
- Base URL:
https://turtleguard.cloud/api/v1. - Header on every request:
Authorization: Bearer crp_.... - Request and response bodies are JSON. Ordering a server and every other action answer
200. - Money is an integer in the minor units of the
currencynext to it: cents for dollars. - Times are RFC 3339 strings, ids are UUIDs.
- Every response has an
X-Request-Idheader. Include it when you contact support about a specific request.
curl -s -H "Authorization: Bearer $TOKEN" https://turtleguard.cloud/api/v1/cloud/vmsTo check a token, call GET /auth/me: the answer holds the account, "auth": "token" and the token's permissions in scopes.
OpenAPI description
Every method, field and response is described in https://turtleguard.cloud/api/v1/openapi.json (OpenAPI 3.1, no token needed). Import it into Postman or Insomnia, or generate a client for your language from it. Where this page and the file disagree, the file is right.
What a token can reach
- Every
/cloud/…method on this page. GET /billing/balanceandGET /billing/transactions.GET /auth/meandGET /openapi.json.
Any other address answers a token with 403 and the code auth.token_area. Topping up the balance, changing the account password, creating or revoking tokens is not possible through the API: that is done in the cabinet.
Each method needs one of three permissions: cloud:read to see servers, cloud:write to order and change them, billing:read to see the balance and transactions. Plans and images (GET /cloud/plans, GET /cloud/cloud-images) are served without a token too.
Limits
| What is counted | How many |
|---|---|
| All requests of one token | 300 a minute |
Requests that change something (everything except GET) | 30 a minute |
Over the limit the API answers 429 with the code rate_limited and a Retry-After header: the number of seconds to wait before retrying. Each token has its own limits.
Do not poll too often
While a server is being created, checking its status every 5–10 seconds is enough. The server list already holds the latest load figures, so there is no need to fetch every server separately.
Errors
{"error": {"code": "billing.insufficient_funds", "message": "..."}}code is stable across versions, so parse the answer by it. message is an English hint. A data error adds field with the field name. A body that is not valid JSON for the method gets a plain-text answer with 400, 415 or 422.
| Status | When | Example code |
|---|---|---|
401 | No token, or it is wrong, expired or revoked | auth.required, auth.invalid_token |
402 | The balance is short, or the server is suspended for non-payment | billing.insufficient_funds, cloud.suspended |
403 | The token lacks the permission, the address is closed to tokens, the server is stopped for abuse, the email is not confirmed | auth.scope, auth.token_area, cloud.abuse_stopped, auth.email_not_confirmed |
404 | No such server, or it belongs to another account; unknown action | not_found |
409 | The action is not possible right now: the server is busy, there is no capacity or free address, a limit is reached, the same paid request was repeated | cloud.vm_busy, cloud.no_capacity, cloud.ip_pool_empty, cloud.duplicate_order, cloud.renew_duplicate, cloud.ip_change_cooldown, cloud.ip_change_vm_daily, cloud.ip_change_user_daily, cloud.snapshot_limit |
422 | Invalid data, the field is named in field | validation.hostname, validation.password_too_short, cloud.invalid_ssh_key, cloud.image_unavailable, cloud.image_too_big |
429 | Too many requests, wait as many seconds as Retry-After says | rate_limited |
A busy server (cloud.vm_busy) is being created, reinstalled, moved or deleted. Wait until the status changes and retry.
Plans and images
| Method | What it does | Permission |
|---|---|---|
GET /cloud/plans | Locations (countries), active plans (plans) and the per-core, per-GB memory and disk prices for a custom configuration (unit_prices) | none |
GET /cloud/cloud-images | Available OS images, one row per os_key | none |
- You can order in locations with
"available": true. The location code is thecodefield, for examplede. - A plan's price per period is
price_monthly. If a location has its own price, it is incountry_pricesunder the location code.traffic_gb: nullmeans unmetered traffic. - Prices in
unit_pricesare in millionths of the currency per month, together with the boundsmin_cpu…max_disk_gb.
Ordering a server
| Method | What it does | Permission |
|---|---|---|
POST /cloud/vms | Order a VPS: the first period is debited from the balance, the server is created in the background | cloud:write |
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"plan_id": "6f1c2b9e-4d1a-4c47-9b52-1f0e7a3d8c21", "region": "de", "os_key": "ubuntu-24.04",
"hostname": "client-42", "root_password": "q7-Lm2vX9rTp", "idempotency_key": "order-1042"}' \
https://turtleguard.cloud/api/v1/cloud/vms| Field | What to send |
|---|---|
hostname | Required. Server name: Latin letters, digits, hyphens and dots |
root_password | Required. Root password, 8 to 128 characters |
plan_id | Plan UUID from /cloud/plans. "custom", null or leaving it out means a custom configuration |
cpu, ram_gb, disk_gb | Custom configuration only: cores, memory and disk in GB |
region | Location code. Without it, the first available location |
os_key | An image from /cloud/cloud-images |
ssh_key | authorized_keys lines (ssh-ed25519, ssh-rsa, ecdsa-…), up to 32 keys, one per line |
idempotency_key | Your order number, so a retry does not charge twice |
The answer holds vm_id, hostname, the assigned address ip, the amount charged price with currency, and duplicate. The server shows up at once with status creating and turns running when ready. If it cannot be created, the money goes back to the balance.
Ordering needs a confirmed account email. A short balance gives 402; no capacity in the location or no free address gives 409 with cloud.no_capacity or cloud.ip_pool_empty.
Retrying without a double charge
If the answer to an order is lost (the connection dropped, a timeout hit), repeat the same request with the same idempotency_key. No second server is created: the API returns the first order with "duplicate": true. Any non-empty key works and is scoped to your account; your own order number is a good choice. Without a key every request is a new order and a new charge.
Server list and details
| Method | What it does | Permission |
|---|---|---|
GET /cloud/vms | Your VPS with their latest load figures. Marketplace app servers are not listed | cloud:read |
GET /cloud/vms/{id} | The whole server: settings (vm), load (live), data freshness, the last 50 actions, the root password | cloud:read |
The key fields of vm: status, the addresses public_ipv4 and ipv6, expires_at (paid until), auto_renew, price_monthly, and the period's traffic traffic_in and traffic_out in bytes. The root password is in root_password: the last one set, or an empty string when unknown.
| Status | Meaning |
|---|---|
creating | Being created |
running, stopped | On or off |
suspended | Suspended: the term ended and there was no money to renew |
reinstalling, migrating | Being reinstalled or moved to another node |
deleting, error | Being deleted, or creation failed |
Power and deletion
| Method | What it does | Permission |
|---|---|---|
POST /cloud/vms/{id}/start | Power on | cloud:write |
POST /cloud/vms/{id}/shutdown | Shut down cleanly through the OS | cloud:write |
POST /cloud/vms/{id}/stop | Power off at once, like the power button | cloud:write |
POST /cloud/vms/{id}/reboot | Reboot through the OS | cloud:write |
POST /cloud/vms/{id}/reset | Hard reset | cloud:write |
POST /cloud/vms/{id}/destroy | Delete the server with its disks. The prepaid remainder is not refunded | cloud:write |
The request waits for the action to finish and returns the new status: {"ok": true, "action": "reboot", "status": "running"}. A server suspended for non-payment cannot be started (402): renew it first. One stopped for abuse cannot either (403).
Reinstall
| Method | What it does | Permission |
|---|---|---|
POST /cloud/vms/{id}/reinstall | Install the system again from an image with a new root password. The disk is wiped | cloud:write |
Body: os_key and root_password are required, ssh_key is optional. The reinstall runs in the background; meanwhile the status is reinstalling. For apps and game servers os_key is ignored: the container is recreated from the same image and the app is installed again with its current settings.
Password and SSH keys
| Method | What it does | Permission |
|---|---|---|
POST /cloud/vms/{id}/password | Change the root password: {"password": "..."}, 8 to 128 characters | cloud:write |
POST /cloud/vms/{id}/sshkeys | Replace the SSH keys: {"keys": "..."}, authorized_keys lines, up to 32. An empty string removes all keys. The answer says how many keys were stored | cloud:write |
The password and keys are written through cloud-init. On a running server the password is also changed through the guest agent, in the background.
Changing the IP
| Method | What it does | Permission |
|---|---|---|
POST /cloud/vms/{id}/change-ip | Give the server a new public IPv4 | cloud:write |
- The change is paid; the price is shown in the panel on the server page. The money is debited from the balance, and the amount comes back in
charged. A short balance gives402. - A running server is restarted to pick up the new address.
- Changes on one server need at least 60 seconds between them, otherwise
409 cloud.ip_change_cooldown. - The number of changes in the last 24 hours is limited, separately per server (
cloud.ip_change_vm_daily) and for the whole account (cloud.ip_change_user_daily). - The answer holds the old address
old_ipand the newip. A non-emptyapply_errormeans the address is assigned but could not be applied on the node right away; we retry on our own.
Changing the plan or size
| Method | What it does | Permission |
|---|---|---|
GET /cloud/vms/{id}/upgrades | Plans bigger than the current one: the new price and what to pay now, plus the balance | cloud:read |
POST /cloud/vms/{id}/upgrade | Move to a bigger plan: {"plan_id": "..."} | cloud:write |
POST /cloud/vms/{id}/resize | Set a custom size: {"cpu": 4, "ram_gb": 8, "disk_gb": 80} | cloud:write |
On a plan change the surcharge covers all the prepaid time, not just the current month: price difference × days left ÷ days in the period. A started day counts as a whole one. If the server is renewed several periods ahead, you pay for all of them. Each option from /upgrades shows the amount in advance in charge_now, along with days_left and period_days. Moving to a smaller plan is not possible.
A custom size is priced by the per-core, memory and disk prices of the server's location. If it costs more than before, the price difference for a period is charged at once; if it costs less, nothing is charged or refunded and the new price applies from the next renewal. The disk can only grow.
Either way a running server is restarted ("restarting": true in the answer). A short balance gives 402. Marketplace app servers cannot be changed this way.
Renewal
| Method | What it does | Permission |
|---|---|---|
POST /cloud/vms/{id}/renew | Renew for one period from the balance. Optional body {"token": "…", "confirm": false}: a retry with the same token (8–64 characters of A-Z a-z 0-9 _ -) is not charged again. A second manual renewal of the same server within a minute is refused with 409 cloud.renew_duplicate unless "confirm": true is sent | cloud:write |
PATCH /cloud/vms/{id}/auto-renew | Turn auto-renew on or off: {"on": true} | cloud:write |
- A renewal is tied to the current expiry date: repeating the same request does not charge again but returns
"duplicate": trueand"charged": 0. The new date is inexpires_at. - A server suspended for non-payment starts again on its own after renewal.
- With auto-renew the money is debited from the balance on the expiry day.
- If the balance is short on that day, the server is powered off and gets the status
suspended. Top up within the grace period and it is renewed and started on its own; otherwise it is deleted.
Snapshots
| Method | What it does | Permission |
|---|---|---|
GET /cloud/vms/{id}/snapshots | The server's snapshots, the per-server limit (max) and data freshness | cloud:read |
POST /cloud/vms/{id}/snapshots | Take a snapshot, optionally with a label: {"description": "..."}, up to 120 characters. The answer holds the snapshot name | cloud:write |
DELETE /cloud/vms/{id}/snapshots/{name} | Delete a snapshot | cloud:write |
POST /cloud/vms/{id}/snapshots/{name}/rollback | Roll the server back to a snapshot: it stops, rolls back and starts again | cloud:write |
Taking and deleting run in the background, so the list is not updated at once. With max snapshots already there, no new one is taken: 409 cloud.snapshot_limit. POST /cloud/vms/{id}/snapshot is the same as POST …/snapshots.
Rescue mode
| Method | What it does | Permission |
|---|---|---|
POST /cloud/vms/{id}/rescue | Turn booting from the rescue image on or off. Each call toggles the mode; the new state is in rescue | cloud:write |
Use it when the system does not boot. The server restarts and boots from the rescue image while your disk stays attached, so you can mount and repair it. Calling the method again restores the normal boot, and the server restarts again.
Console
| Method | What it does | Permission |
|---|---|---|
GET /cloud/vms/{id}/console | A one-time VNC console pass: token, the VNC password password and its lifetime expires_in, 120 seconds | cloud:write |
With the pass, open the websocket wss://turtleguard.cloud/api/v1/vnc/{token} (subprotocol binary), for example with noVNC, and enter password. The pass works once. Do not add an Authorization header to this connection: the pass is already in the address, and with a token the address answers 403.
Load graphs
| Method | What it does | Permission |
|---|---|---|
GET /cloud/vms/{id}/metrics?timeframe=day | Graph points for hour, day (default), week, month or year | cloud:read |
Each point has time in Unix seconds and the values cpu, mem, maxmem, disk, maxdisk, netin, netout (network in bytes per second).
Name and comment
| Method | What it does | Permission |
|---|---|---|
PATCH /cloud/vms/{id}/profile | Change the server's name, colour or picture and comment | cloud:write |
Fields: name up to 64 characters, comment up to 200, avatar a #rrggbb colour or a data:image/... picture up to 400,000 bytes. An empty string clears a field; fields you leave out stay as they are. In the server data they show up as display_name, comment and avatar.
Balance and transactions
| Method | What it does | Permission |
|---|---|---|
GET /billing/balance | The balance in the main currency (currency, balance) and every account in other currencies (accounts) | billing:read |
GET /billing/transactions?page=1&per_page=50 | Balance transactions, newest first. per_page is 1 to 500 | billing:read |
A transaction has the amount amount (debits are negative), the balance after it balance_after, the type kind (for example cloud_vm for a server order, renewal or plan change, cloud_ip for an IP change, topup for a top-up), a description and ref_id, the server it belongs to. The balance cannot be topped up through the API, only in the cabinet.
For resellers
The usual flow when you sell our VPS to your own customers:
- Check the balance:
GET /billing/balance. Orders and renewals are paid from it. - Get plans, locations and images:
GET /cloud/plansandGET /cloud/cloud-images. - Order the server:
POST /cloud/vmswithidempotency_keyset to the order number in your system. On a network error, retry with the same key. - Poll
GET /cloud/vms/{vm_id}untilstatusisrunning. - Give the customer the address from
public_ipv4and the root password: the one you set when ordering, also inroot_password. - Tag the server:
PATCH /cloud/vms/{id}/profilewith the customer's name innameand a contract number or contact incomment. The panel shows the name instead of the hostname, and the comment is in the server data through the API. - Watch the term via
expires_at: renew withPOST /cloud/vms/{id}/renewwhen the customer has paid, or turn on auto-renew and keep a reserve on the balance.
This needs a Manage VPS token. A storefront that only shows customers the state of their servers can do with a Read only token.