API quickstart

Call the VPSNine API with curl and a bearer token. Check who you are, manage SSH keys, reboot a server, and read the errors it returns.

The API lives at https://my.vpsnine.com/api/v1. It speaks JSON and takes an API token as a bearer token. It covers your account, SSH keys, servers, firewalls, snapshots, reverse DNS and invoices. The full reference lists every endpoint with an example, and the vps9 CLI uses this same API.

All you need is curl, and jq makes the output nicer (apt install -y jq).

Set up

Make a token under tokens in the panel. read only can make every GET request. Anything that changes something, like adding a key or rebooting a server, needs read and write.

Read the token from a file so it stays out of your shell history (Create and use API tokens shows how to store it):

bash
export VPS9_TOKEN="$(cat ~/.vps9-token)"
API=https://my.vpsnine.com/api/v1

Then ask who you are:

bash
curl -s -H "Authorization: Bearer $VPS9_TOKEN" "$API/me"
json
{"id":"usr_…","email":"[email protected]","emailVerified":true,"createdAt":"2026-10-09T12:00:00.000Z"}

If that comes back, your token works.

SSH keys

List them:

bash
curl -s -H "Authorization: Bearer $VPS9_TOKEN" "$API/ssh-keys"

You get an object with a data list. Each key has an id, name, type, fingerprint, publicKey and createdAt. With jq, just the names and fingerprints:

bash
curl -s -H "Authorization: Bearer $VPS9_TOKEN" "$API/ssh-keys" | jq -r '.data[] | "\(.name)  \(.fingerprint)"'

Add one. Let jq build the JSON so the key text gets quoted properly:

bash
jq -n --arg name laptop --arg key "$(cat ~/.ssh/id_ed25519.pub)" '{name: $name, publicKey: $key}' |
  curl -s -X POST -H "Authorization: Bearer $VPS9_TOKEN" -H "Content-Type: application/json" --data @- "$API/ssh-keys"

A new key comes back with status 201 and its id. The rules match the panel: Ed25519, ECDSA, or RSA of 2048 bits or more, one key per request, 50 at most. name is optional.

Delete one by its id:

bash
curl -s -X DELETE -H "Authorization: Bearer $VPS9_TOKEN" "$API/ssh-keys/key_…"

That returns 204 and no body.

Servers

List them, showing the id, hostname and state:

bash
curl -s -H "Authorization: Bearer $VPS9_TOKEN" "$API/servers" | jq -r '.data[] | "\(.id)  \(.hostname)  \(.state)"'

Reboot one by its id:

bash
curl -s -X POST -H "Authorization: Bearer $VPS9_TOKEN" -H "Content-Type: application/json" \
  -d '{"action":"reboot"}' "$API/servers/srv_…/actions"

It answers 202 straight away, with the server's pendingAction set to reboot. The reboot itself happens in the background, so fetch the server again to see when it's done. start, stop, reinstall and delete work the same way. The API doesn't ask you to confirm a reinstall or delete, so check the id first.

Orders open at launch. When they do, POST /servers places one. Send an Idempotency-Key header with a value you choose, a UUID for example, and keep it until you get an answer. If the request times out, send it again with the same key, and you get the order you already placed instead of a second one.

When it says no

Errors are JSON too, with a code you can match on and a message meant for people:

json
{"error":{"code":"insufficient_scope","message":"This token does not have the write scope."}}
Status Code What it means
401 unauthorized Token missing, wrong, revoked or expired.
400 invalid_json The body isn't valid JSON.
403 insufficient_scope A read-only token tried to change something.
403 email_unverified Ordering needs a confirmed email address. Confirm it first.
404 not_found No such key or server, or no such endpoint.
405 method_not_allowed The path exists, but not with that method. The Allow header lists the ones it takes.
409 duplicate_key That key's already on your account.
409 action_pending, invalid_state The server is busy with another action, or in a state where that action can't run.
413 body_too_large Bodies are 16 KB at most.
415 unsupported_media_type A body sent without -H "Content-Type: application/json". Plain curl -d sends a form, which the API refuses.
422 invalid_request The body isn't a JSON object, or has a field the endpoint doesn't know or of the wrong type (a number where text goes, say).
422 invalid_key, invalid_name, too_many_keys The key or name was refused; the message says why.
429 rate_limited Over 60 requests a minute on this token, or 120 across all your account's tokens. Wait the Retry-After seconds.

Add -i to any of these curl commands to see the status line and headers. And don't bother trying a browser cookie: the API ignores cookies entirely. A token is the only way in.

Be first when orders open

Join the waitlist and we'll send you one email when VPSNine launches. Pick a plan if you already know which one you want, and we'll size the first servers around it.