TurtleGuard

Docs / API

VPS API

Order, manage and renew VPS from your own code.

14 minute readUpdated 2026-11-07

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 currency next to it: cents for dollars.
  • Times are RFC 3339 strings, ids are UUIDs.
  • Every response has an X-Request-Id header. Include it when you contact support about a specific request.
list servers
curl -s -H "Authorization: Bearer $TOKEN" https://turtleguard.cloud/api/v1/cloud/vms

To 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/balance and GET /billing/transactions.
  • GET /auth/me and GET /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 countedHow many
All requests of one token300 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 response
{"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.

StatusWhenExample code
401No token, or it is wrong, expired or revokedauth.required, auth.invalid_token
402The balance is short, or the server is suspended for non-paymentbilling.insufficient_funds, cloud.suspended
403The token lacks the permission, the address is closed to tokens, the server is stopped for abuse, the email is not confirmedauth.scope, auth.token_area, cloud.abuse_stopped, auth.email_not_confirmed
404No such server, or it belongs to another account; unknown actionnot_found
409The 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 repeatedcloud.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
422Invalid data, the field is named in fieldvalidation.hostname, validation.password_too_short, cloud.invalid_ssh_key, cloud.image_unavailable, cloud.image_too_big
429Too many requests, wait as many seconds as Retry-After saysrate_limited

A busy server (cloud.vm_busy) is being created, reinstalled, moved or deleted. Wait until the status changes and retry.

Plans and images

MethodWhat it doesPermission
GET /cloud/plansLocations (countries), active plans (plans) and the per-core, per-GB memory and disk prices for a custom configuration (unit_prices)none
GET /cloud/cloud-imagesAvailable OS images, one row per os_keynone
  • You can order in locations with "available": true. The location code is the code field, for example de.
  • A plan's price per period is price_monthly. If a location has its own price, it is in country_prices under the location code. traffic_gb: null means unmetered traffic.
  • Prices in unit_prices are in millionths of the currency per month, together with the bounds min_cpu…max_disk_gb.

Ordering a server

MethodWhat it doesPermission
POST /cloud/vmsOrder a VPS: the first period is debited from the balance, the server is created in the backgroundcloud:write
order a plan
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
FieldWhat to send
hostnameRequired. Server name: Latin letters, digits, hyphens and dots
root_passwordRequired. Root password, 8 to 128 characters
plan_idPlan UUID from /cloud/plans. "custom", null or leaving it out means a custom configuration
cpu, ram_gb, disk_gbCustom configuration only: cores, memory and disk in GB
regionLocation code. Without it, the first available location
os_keyAn image from /cloud/cloud-images
ssh_keyauthorized_keys lines (ssh-ed25519, ssh-rsa, ecdsa-…), up to 32 keys, one per line
idempotency_keyYour 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

MethodWhat it doesPermission
GET /cloud/vmsYour VPS with their latest load figures. Marketplace app servers are not listedcloud:read
GET /cloud/vms/{id}The whole server: settings (vm), load (live), data freshness, the last 50 actions, the root passwordcloud: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.

StatusMeaning
creatingBeing created
running, stoppedOn or off
suspendedSuspended: the term ended and there was no money to renew
reinstalling, migratingBeing reinstalled or moved to another node
deleting, errorBeing deleted, or creation failed

Power and deletion

MethodWhat it doesPermission
POST /cloud/vms/{id}/startPower oncloud:write
POST /cloud/vms/{id}/shutdownShut down cleanly through the OScloud:write
POST /cloud/vms/{id}/stopPower off at once, like the power buttoncloud:write
POST /cloud/vms/{id}/rebootReboot through the OScloud:write
POST /cloud/vms/{id}/resetHard resetcloud:write
POST /cloud/vms/{id}/destroyDelete the server with its disks. The prepaid remainder is not refundedcloud: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

MethodWhat it doesPermission
POST /cloud/vms/{id}/reinstallInstall the system again from an image with a new root password. The disk is wipedcloud: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

MethodWhat it doesPermission
POST /cloud/vms/{id}/passwordChange the root password: {"password": "..."}, 8 to 128 characterscloud:write
POST /cloud/vms/{id}/sshkeysReplace the SSH keys: {"keys": "..."}, authorized_keys lines, up to 32. An empty string removes all keys. The answer says how many keys were storedcloud: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

MethodWhat it doesPermission
POST /cloud/vms/{id}/change-ipGive the server a new public IPv4cloud: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 gives 402.
  • 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_ip and the new ip. A non-empty apply_error means the address is assigned but could not be applied on the node right away; we retry on our own.

Changing the plan or size

MethodWhat it doesPermission
GET /cloud/vms/{id}/upgradesPlans bigger than the current one: the new price and what to pay now, plus the balancecloud:read
POST /cloud/vms/{id}/upgradeMove to a bigger plan: {"plan_id": "..."}cloud:write
POST /cloud/vms/{id}/resizeSet 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

MethodWhat it doesPermission
POST /cloud/vms/{id}/renewRenew 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 sentcloud:write
PATCH /cloud/vms/{id}/auto-renewTurn 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": true and "charged": 0. The new date is in expires_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

MethodWhat it doesPermission
GET /cloud/vms/{id}/snapshotsThe server's snapshots, the per-server limit (max) and data freshnesscloud:read
POST /cloud/vms/{id}/snapshotsTake a snapshot, optionally with a label: {"description": "..."}, up to 120 characters. The answer holds the snapshot namecloud:write
DELETE /cloud/vms/{id}/snapshots/{name}Delete a snapshotcloud:write
POST /cloud/vms/{id}/snapshots/{name}/rollbackRoll the server back to a snapshot: it stops, rolls back and starts againcloud: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

MethodWhat it doesPermission
POST /cloud/vms/{id}/rescueTurn booting from the rescue image on or off. Each call toggles the mode; the new state is in rescuecloud: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

MethodWhat it doesPermission
GET /cloud/vms/{id}/consoleA one-time VNC console pass: token, the VNC password password and its lifetime expires_in, 120 secondscloud: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

MethodWhat it doesPermission
GET /cloud/vms/{id}/metrics?timeframe=dayGraph points for hour, day (default), week, month or yearcloud: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

MethodWhat it doesPermission
PATCH /cloud/vms/{id}/profileChange the server's name, colour or picture and commentcloud: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

MethodWhat it doesPermission
GET /billing/balanceThe balance in the main currency (currency, balance) and every account in other currencies (accounts)billing:read
GET /billing/transactions?page=1&per_page=50Balance transactions, newest first. per_page is 1 to 500billing: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:

  1. Check the balance: GET /billing/balance. Orders and renewals are paid from it.
  2. Get plans, locations and images: GET /cloud/plans and GET /cloud/cloud-images.
  3. Order the server: POST /cloud/vms with idempotency_key set to the order number in your system. On a network error, retry with the same key.
  4. Poll GET /cloud/vms/{vm_id} until status is running.
  5. Give the customer the address from public_ipv4 and the root password: the one you set when ordering, also in root_password.
  6. Tag the server: PATCH /cloud/vms/{id}/profile with the customer's name in name and a contract number or contact in comment. The panel shows the name instead of the hostname, and the comment is in the server data through the API.
  7. Watch the term via expires_at: renew with POST /cloud/vms/{id}/renew when 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.