Stavane

The API

Everything you do in the console, a script can do through the API: servers and their parts, the other products of your account and a read-only view of your money. It speaks JSON over HTTPS and follows the same rules as the console: your level, your credit and the systems and locations that can be built.

Make a token

  1. Open Access & security in the console and choose API tokens.
  2. Choose Add, give the token a name, choose its access (Read and write, or Read only for a script that only looks) and, if you like, the addresses it may be used from (one address or range per line). Leave that empty to allow every address. Choose Generate token.
  3. Copy the token. It is shown once; we keep only a hash of it.

A token acts as you. Disable it when you want a pause, and revoke it when it should never work again. Both are in the same list, and take effect at once.

Call the API

The API lives at https://api.stavane.com/v1, the first server of the OpenAPI description. The same API also answers at /api/v1 on the console, for example https://console.stavane.com/api/v1, with the same tokens and rules; scripts that already use that address keep working. Send the token as a Bearer token:

curl -H "Authorization: Bearer stv_..." https://api.stavane.com/v1/servers

A token can make 120 requests per minute. Beyond that the API answers 429 with a Retry-After header.

What you can do

Method and path What it does
GET /servers Your servers
GET /servers/{id} One server
POST /servers Build a server (see below)
POST /servers/{id}/actions/start Start a stopped server
POST /servers/{id}/actions/stop Stop a running server
POST /servers/{id}/actions/reboot Restart a running server
DELETE /servers/{id} Remove a server and everything on it
PATCH /servers/{id} Give a server a new name (name)
POST /servers/{id}/actions/resize Move a server to another plan of its family (plan); a running server stops for a moment and starts again by itself
POST /servers/{id}/actions/rebuild Put a fresh system on a server (image, ssh_keys, confirm, optional root_password_mail); everything on the disk goes
GET /servers/{id}/settings The settings of a server: protection, console, boot device and media
PATCH /servers/{id}/settings Change them, for example {"protected": false}
POST /servers/{id}/actions/reset-password Give root a new password; the answer holds it once
GET /servers/{id}/reverse-names What each address of a server answers when it is looked up backwards, and under request a name that waits for approval (pending) or was refused (rejected, with the reason)
PUT /servers/{id}/reverse-names Point one address at a name (address, name)
GET /servers/{id}/traffic The traffic of a server this month, in and out in GB, as the server page shows it; unlimited under fair use, so never a bill
GET /servers/{id}/addresses The extra IPv4 addresses of a server
POST /servers/{id}/addresses Add an extra IPv4 address; needs an Idempotency-Key
POST /servers/{id}/addresses/{address}/move Move an address to another of your servers in the same location (server)
DELETE /servers/{id}/addresses/{address} Give an address back; billing for it stops
GET /plans The plans you can order, with the price per month in your currency
GET /regions The locations you can build in
GET /images The systems you can build
GET /ssh-keys Your SSH keys, with the id to build with
POST /ssh-keys Add an SSH key (public_key, optional name)
PATCH /ssh-keys/{id} Give an SSH key another name (name)
DELETE /ssh-keys/{id} Remove an SSH key from your account; it stays on servers that already have it
GET /account Your credit in cents (balance_cents), the currency, and what this month cost so far and will cost
GET /addresses Every extra IPv4 address of your account, with the server it is on and its price per month
GET /openapi.json The whole API as an OpenAPI description; no token needed

Build a server

A request that builds needs an Idempotency-Key header: a string of your own, 8 to 100 characters. Sending the same request with the same key again gives back the same server instead of a second one, so a retry after a timeout is safe. Use a new key for every new request: the same key with other fields answers 422 with the error under Idempotency-Key, and nothing is built. A key belongs to one path, and a request we refused (an answer from 400 to 499) leaves the key free, so you can fix the fields and send it again under the same key.

curl -X POST https://api.stavane.com/v1/servers \
  -H "Authorization: Bearer stv_..." \
  -H "Idempotency-Key: build-web-1-2026-09-23" \
  -H "Content-Type: application/json" \
  -d '{"plan":"S-2","region":"ams","image":"ubuntu:24.04","name":"web-1","ssh_keys":["<id from GET /ssh-keys>"]}'

The fields are plan, region, image (as GET /images gives it) and name, and optionally ssh_keys (ids from GET /ssh-keys), root_password_mail (the root password by e-mail, as in the console; on unless you send false, and then at least one key is needed), cloud_init (a cloud-config document) or user_script (the id of a saved script), timezone (Europe/Amsterdam, America/New_York or UTC; without it the time zone of the location), project (the id of a project) and protected (protection against removing and rebuilding; on unless you send false).

The answer is 202 with the server. It is built in the background: read it again with GET /servers/{id} until its status is running.

Calls that wipe data

Rebuilding a server removes what is on the disk now. Send the name of the server as confirm, exactly as you would type it in the console, so a wrong id in a script cannot wipe the wrong machine.

A new server is protected against removing and rebuilding, as in the console. DELETE /servers/{id} and a rebuild of a protected server answer 422; send PATCH /servers/{id}/settings with {"protected": false} first.

Extra addresses

An extra address is billed by the hour from the moment it exists, exactly as in the console. The same Idempotency-Key twice gives the same address, so a retry never adds a second one. The guides of other parts of a server list their own calls under "Through the API".

The other products

Each product below is part of the API while it is part of your account. A call that makes something billed needs an Idempotency-Key, as building a server does. Removing something asks for its name as confirm, exactly as you would type it in the console. Each product has an options call with what can be made and its price per month in your currency.

Every call of every product is in the OpenAPI description at /openapi.json, which only lists what your account can use.

Your balance, usage and invoices

A token of an owner reads your money; a token of a member gets 403 here, and the billing role makes no tokens. A token can never change your money: topping up, cards, automatic top-ups and refunds stay in the console. Amounts are whole cents in the currency of your account.

Method and path What it does
GET /billing/balance Your credit per pot (paid_cents, bonus_cents, and promotional_cents for welcome and referral credit) and the estimated balance the console shows
GET /billing/usage What this month cost so far and will cost at today's prices, in total and per resource
GET /billing/transactions Your credit ledger, newest first: top-ups, bonus credit, usage per day, refunds
GET /billing/invoices The invoices, credit notes and usage statements we issued to you, newest first
GET /billing/invoices/{id} One document with its lines
GET /billing/invoices/{id}/pdf The document as the PDF we sent

These lists come a page at a time, see "Lists" below.

Support tickets

Ask us something from a script, and read our answer, as in Support in the console. Tickets are plain text; files cannot be attached. A ticket goes by its reference, such as T-123456. Opening a ticket and replying need an Idempotency-Key, so a retry never sends your message twice.

Method and path What it does
GET /tickets/options What you can ask about, and with a service contract the priorities
GET /tickets Your tickets, the latest conversation first, a page at a time (see "Lists" below)
POST /tickets Open a ticket (subject, category, body, optional server)
GET /tickets/{reference} One ticket with the whole conversation
POST /tickets/{reference}/replies Reply (body); a reply to a closed ticket opens it again
POST /tickets/{reference}/close Close a ticket

What stays in the console

The screen of a server needs a browser, so it is only in the console. See The console of your server.

Lists

A list that can grow without end comes a page at a time: your transactions, invoices, tickets and service contracts. A page has 25 rows unless you ask for up to 100 with limit. While there are more, the answer has a next_cursor: send it as cursor to read the next page; it is null on the last page. Every other list holds everything of your account, in the same order on every call, except the activity of a server, which gives the latest rows only (limit, at most 100). What we read from the systems that run your servers (firewall rules, reverse names and the like) comes in the order those systems give.

Answers and errors

Every answer is JSON with the result under data. When something is refused:

  • 400: a call that needs an Idempotency-Key came without one.
  • 401: no token, or a token that is unknown, disabled or revoked.
  • 403: the token may not be used from this address, it is a read-only token and the call changes something, or your role in the organisation does not allow the call (for example billing).
  • 404: there is no such thing of yours, or it belongs to a product that is not part of your account.
  • 422: a field is missing or refused, for example a plan your level does not open yet or credit that does not cover the first hours. The answer says which field and why, under errors, with the name of the field you sent (size_gb, or targets.0.port for a field inside a list). A refusal that is about the thing itself and not a field you sent is under the name of its path parameter, such as server. A field the call does not take is refused too, by its name, before anything happens: a typo such as "port" where the call takes "from" never passes unnoticed. The same Idempotency-Key with other fields than its first request answers 422 under Idempotency-Key.
  • 429: too many requests.