Skip to main content

Arianet Cloud API

The Arianet API is the public web service for buying and managing cloud servers. The arianet CLI, the Telegram bot and partner integrations all use it, and so can your own code. A machine-readable description is in openapi.yaml, published next to this documentation.

  • Base URL: https://api.ariaservice.net/api/v1
  • Format: JSON requests and responses over HTTPS
  • Auth: a personal API token sent as a Bearer token

Quick start​

1. Create a token​

Log in to the panel, open Settings → API Tokens, create a token, and copy it. It is shown once. You can limit a token to the scopes it needs and to the IP addresses it may be used from.

export ARIANET_TOKEN="paste-your-token-here"

2. Make a request​

curl https://api.ariaservice.net/api/v1/balance \
-H "Authorization: Bearer $ARIANET_TOKEN"

3. Buy a server​

Find the ids you need (datacenter, plan, operating system), then create the server. The Idempotency-Key header is required, see Idempotency.

BASE=https://api.ariaservice.net/api/v1
AUTH="Authorization: Bearer $ARIANET_TOKEN"

curl -s $BASE/regions -H "$AUTH" # datacenter ids
curl -s $BASE/plans/datacenter/9 -H "$AUTH" # plan ids and prices
curl -s $BASE/os/datacenter/9 -H "$AUTH" # os ids

curl -s -X POST $BASE/servers \
-H "$AUTH" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"plan_id":101,"datacenter_id":9,"os_id":55,"hostname":"web-1","auth_type":"password","auth_value":"Str0ng!Passw0rd"}'

The answer is 202 Accepted while the server is being provisioned. Poll GET /servers/{id}/status until status is active.

Authentication​

Send the token in the Authorization header of every request:

Authorization: Bearer <token>

A missing, invalid, expired or revoked token answers 401 UNAUTHORIZED.

Scopes​

A token can carry a list of scopes. A token created without scopes is unrestricted. A request outside the token's scopes answers 403 INSUFFICIENT_SCOPE.

ScopeAllows
regions:readGET /regions
os:readGET /os/datacenter/{id}
plans:readGET /plans/...
servers:readGET /servers, /servers/{id}, /servers/{id}/status, /servers/{id}/actions
servers:writecreate, delete, restart, power on/off, rename, reinstall, protection
balance:readGET /balance, /balance/transactions
invoices:readGET /invoices, /invoices/{number}
ssh-keys:read / ssh-keys:writelist and read / create, update and delete SSH keys
firewalls:read / firewalls:writelist and read / manage firewalls, rules and attachments
tokens:read / tokens:writeGET /auth/tokens / DELETE /auth/tokens/{id}

GET /auth/me and DELETE /auth/logout need no scope.

IP allowlist​

A token can be limited to a list of source addresses. A request from any other address answers 403 IP_NOT_ALLOWED.

Token hygiene​

  • Never put a token in client-side code or a repository; keep it in an environment variable or a secret store.
  • Create one token per application, with only the scopes it needs, so you can revoke one without breaking the others.
  • Rotate tokens regularly; revoke with DELETE /auth/tokens/{id}.

Responses​

Every answer uses one envelope.

{ "success": true, "data": { "id": 4821, "status": "active" } }

Lists add pagination:

{
"success": true,
"data": [ { "id": 4821 } ],
"pagination": { "current_page": 1, "per_page": 15, "total": 40, "last_page": 3 }
}

Errors:

{ "success": false, "error": { "code": "INSUFFICIENT_BALANCE", "message": "Not enough balance. Required balance: 4.50" } }

Branch on error.code. error.message is written for people and may change.

Lists take page (default 1) and limit (1-100, default 15; an out-of-range value falls back to 15). Timestamps are UTC, ISO 8601 (2026-10-02T09:30:00Z).

Error codes​

HTTPerror.codeMeaning
400VALIDATION_ERRORA field is missing or malformed; the message names it
400INVALID_IDA path id is not a number
400IDEMPOTENCY_KEY_REQUIREDPOST /servers without an Idempotency-Key
400INVALID_IDEMPOTENCY_KEYThe key is not 8-128 characters of A-Z a-z 0-9 _ - : .
400NO_DEFAULT_WALLETNo currency_id was given and the account has no default wallet
400BAD_REQUESTThe platform rejected the request as malformed
401UNAUTHORIZEDMissing, invalid, expired or revoked token
402INSUFFICIENT_BALANCEThe wallet cannot cover the order
403FORBIDDENThe account is not active or may not do this
403INSUFFICIENT_SCOPEThe token lacks the scope this endpoint needs
403IP_NOT_ALLOWEDThe request comes from an address outside the token's allowlist
404NOT_FOUNDThe resource does not exist in your account
409CONFLICTA request with the same Idempotency-Key is still running; retry shortly
422VALIDATION_ERRORWell formed but cannot be done now (server protected, plan sold out, key reused with a different body, ...)
422FEATURE_NOT_SUPPORTEDThe datacenter does not offer this resource (for example a firewall or SSH key); check supports in GET /regions
429RATE_LIMIT_EXCEEDEDToo many requests
502UPSTREAM_ERRORThe platform could not complete the request; retry
503UPSTREAM_UNAVAILABLE / WRITES_UNAVAILABLEThe platform is temporarily unreachable; retry with the same key
500INTERNAL_ERRORUnexpected error

Retry 429, 502 and 503 with back-off. Retrying a create is safe as long as you reuse the same Idempotency-Key.

Rate limits​

Limits are per user, except creation which is per token. They are enforced independently by each API instance, so treat them as a floor, not an exact figure.

GroupLimit
Reads120 / minute, burst 20
Writes (actions, SSH keys, firewalls, tokens)about 20 / minute, burst 5
POST /serversburst 5, then 5 / hour per token; the platform also caps 10 / hour per account

When a limit is hit the API answers 429 RATE_LIMIT_EXCEEDED. Creation also sends a Retry-After header (seconds). The API does not send X-RateLimit-* headers.

Asynchronous operations​

Creating, deleting, reinstalling, restarting and powering a server on or off answer 202 Accepted: the request was validated and started, the work is not finished. To know when it is:

  • poll GET /servers/{id}/status until status is active and instance_status is RUNNING (or STOPPED after a power-off), or
  • read GET /servers/{id}/actions for the operation history (status is pending, running, success or failed).

A typical wait loop:

until [ "$(curl -s $BASE/servers/4821/status -H "$AUTH" | jq -r .data.status)" = "active" ]; do
sleep 5
done

Renaming and changing protection apply at once and answer 200.

Idempotency​

Creating a server spends money, so POST /servers requires an Idempotency-Key header: 8-128 characters of A-Z a-z 0-9 _ - : ., for example a UUID.

  • Retry with the same key and the same body and you never get a second server. The original answer is replayed for 24 hours and carries Idempotent-Replayed: true.
  • The same key with a different body answers 422.
  • A retry while the first request is still running answers 409 CONFLICT; wait a few seconds and try again.
  • Generate a new key for every new order, and keep it until you have a definite answer.

The replayed answer contains the root password, so treat the key like a secret.


Reference

Account​

GET /auth/me​

The user the token belongs to.

{ "success": true, "data": { "id": 17, "email": "user@example.com", "mobile": null, "status": "active", "verified": true } }

GET /auth/tokens​

Needs scope tokens:read. Your API tokens. The secret value is never returned, only a prefix to recognise it.

{
"success": true,
"data": {
"tokens": [
{ "id": 3, "name": "ci", "prefix": "ak_3f9c", "last_used": "2026-10-02T08:12:44Z", "expires": null, "created_at": "2026-09-01T10:00:00Z" }
]
}
}

DELETE /auth/tokens/{id}​

Needs scope tokens:write. Revoke one of your tokens. Answers {"message": "Token revoked successfully"}.

DELETE /auth/logout​

Revoke the token used for this request.

Catalog​

Catalogue data you need before buying a server.

GET /regions​

Scope regions:read. Regions with their datacenters. A datacenter id is the datacenter_id used everywhere else.

supports tells you which optional resources a datacenter offers: ssh_keys and firewalls are true when you can create them there. Creating one in a datacenter where it is false is refused with 422 FEATURE_NOT_SUPPORTED before anything is sent to the platform.

{
"success": true,
"data": {
"items": [
{
"id": 2, "name": "Germany", "is_region": true, "status": "active", "is_default": false,
"country_code": "DE", "flag": "https://.../de.svg",
"datacenters": [
{ "id": 9, "name": "Falkenstein", "is_default": false, "status": "active", "country_code": "DE", "flag": "https://.../de.svg",
"features": [ { "name": "IPv6", "details": null, "quantity": 1 } ],
"supports": { "ssh_keys": true, "firewalls": true } }
]
}
]
}
}

GET /os/datacenter/{datacenter_id}​

Scope os:read. Operating systems grouped by family. The id of a template, or of one of its children (versions), is the os_id for creation and reinstall.

{
"success": true,
"data": {
"groups": [
{ "id": 1, "name": "Ubuntu", "type": "linux", "order": 1,
"templates": { "data": [
{ "id": 55, "name": "Ubuntu 24.04", "image_file": "ubuntu-24.04", "status": true,
"datacenter": { "id": 9, "name": "fsn1", "display_name": "Falkenstein" },
"os_group": { "id": 1, "name": "Ubuntu", "type": "linux" },
"children": { "data": [] } }
] } }
]
}
}

GET /plans/datacenter/{datacenter_id}​

Scope plans:read. Plans orderable in a datacenter, grouped by family, with prices per currency.

{
"success": true,
"data": {
"groups": [
{ "id": 1, "name": "Standard",
"plans": [
{ "id": 101, "name": "std-2", "display_name": "Standard 2", "recommended": true, "cycle": "hourly",
"cpu": { "size": 2, "unit": "vCPU" }, "ram": { "size": 4, "unit": "GB" }, "storage": { "size": 80, "unit": "GB" },
"prices": [ { "code": "USD", "currency": "US Dollar", "hourly": "0.0070", "monthly": "4.50", "yearly": "48.00" } ] }
] }
]
}
}

GET /plans/{id}​

Scope plans:read. One plan, same shape as an item above.

Servers​

A server looks like this wherever it is listed or fetched:

{
"id": 4821,
"status": "active",
"instance_status": "RUNNING",
"name": "web-1",
"hostname": "web-1",
"ip_addresses": [ { "ip": "203.0.113.10", "type": "primary" } ],
"protected": false,
"cycle": "hourly",
"plan": { "id": 101, "name": "std-2" },
"datacenter": { "id": 9, "name": "fsn1", "display_name": "Falkenstein", "country": "DE" },
"os": { "id": 55, "name": "Ubuntu 24.04" },
"created_at": "2026-10-02T09:30:00Z"
}

status is the platform state (creating, active, suspended, powering_on, powering_off, restarting, reinstalling_os, failed, terminated, ...); instance_status is what the provider reports (RUNNING, STOPPED, REBOOTING, REBUILDING, BUILD, ERROR, ...). Address type is primary, secondary or floating.

GET /servers​

Scope servers:read. Your servers, newest first. Query: page, limit, status (filter).

GET /servers/{id}​

Scope servers:read. One server.

GET /servers/{id}/status​

Scope servers:read. A small answer that is cheap to poll.

{ "success": true, "data": { "id": 4821, "status": "active", "instance_status": "RUNNING" } }

GET /servers/{id}/actions​

Scope servers:read. The operation history of a server, newest first. Query: limit (1-100, default 15).

{
"success": true,
"data": {
"server_id": 4821,
"actions": [
{ "id": "9b1f0c5e", "type": "restart", "status": "success",
"started_at": "2026-10-02T09:40:00Z", "finished_at": "2026-10-02T09:40:09Z", "created_at": "2026-10-02T09:40:00Z" }
]
}
}

POST /servers​

Scope servers:write. Headers: Idempotency-Key (required). Answers 202.

FieldRequiredDescription
plan_idyesFrom /plans/datacenter/{id}
datacenter_idyesFrom /regions
os_idyesFrom /os/datacenter/{id}
currency_idnoWallet currency; defaults to your primary wallet (NO_DEFAULT_WALLET if none)
hostnamenoServer name, up to 255 characters (hostnames is accepted as an alias and wins)
auth_typenopassword (default) or ssh
auth_valuewith passwordThe root password
ssh_key_idwith sshOne of your SSH keys (auth_id is accepted as an alias)
project_idnoProject to place the server in
service_countno1-10
{
"success": true,
"data": {
"id": 4821,
"status": "creating",
"instance_status": null,
"name": "web-1",
"ip_addresses": [],
"protected": false,
"cycle": "hourly",
"plan": { "name": "std-2" },
"datacenter": { "id": 9 },
"os": { "name": "Ubuntu 24.04" },
"root_password": "Str0ng!Passw0rd",
"created_at": "2026-10-02T09:30:00Z"
}
}

root_password is returned here only (and on an idempotent replay), and is absent for servers created with an SSH key. Addresses usually appear a little later; read the server again. The cost is reserved from the wallet at once. Possible errors: 402 INSUFFICIENT_BALANCE, 403, 409, 422, 429.

DELETE /servers/{id}​

Scope servers:write. Irreversible. A protected server must have protection switched off first. Answers 202.

Optional body: {"reason": "other", "note": "..."} (reason defaults to other, note up to 1000 characters).

{ "success": true, "data": { "server_id": 4821, "message": "Server deletion started" } }

POST /servers/{id}/restart​

Scope servers:write. Optional body {"mode": "soft" | "hard"}. Answers 202 with {"server_id": 4821, "message": "Restart started"}.

POST /servers/{id}/power-on​

Scope servers:write. Answers 202 with {"server_id": 4821, "message": "Power on started"}.

POST /servers/{id}/power-off​

Scope servers:write. Answers 202 with {"server_id": 4821, "message": "Power off started"}. A powered-off server may still be billed.

POST /servers/{id}/rename​

Scope servers:write. Body {"name": "web-2"} (1-30 characters). Answers 200 with {"server_id": 4821, "name": "web-2"}. The new label shows as name in server reads; hostname is the provider-side host name and does not change.

POST /servers/{id}/reinstall​

Scope servers:write. Body {"os_id": 56}. Erases all data on the server. Answers 202 with {"server_id": 4821, "message": "Reinstall started"}.

POST /servers/{id}/toggle-protection​

Scope servers:write. Body {"enabled": true} or {"enabled": false} sets the state explicitly; with no body the current state is flipped, so prefer the explicit form. Answers 200 with {"server_id": 4821, "protected": true}.

SSH keys​

A key belongs to one datacenter, because providers keep keys per location.

{ "id": 12, "name": "laptop", "key": "ssh-ed25519 AAAAC3Nza... me@laptop", "protected": false, "datacenter_id": 9, "created_at": "2026-10-01T12:00:00Z" }
EndpointScopeNotes
GET /ssh-keysssh-keys:readPaginated
GET /ssh-keys/{id}ssh-keys:read
POST /ssh-keysssh-keys:writeBody name, public_key, datacenter_id (all required), is_protected. Answers 201
PUT /ssh-keys/{id}ssh-keys:writeBody name and/or is_protected; at least one. The key material cannot be changed
DELETE /ssh-keys/{id}ssh-keys:writeServers already created with the key are not affected

Firewalls​

{
"id": 7, "name": "web", "protected": false, "datacenter_id": 9,
"rules": [ { "id": "r-91c2", "direction": "ingress", "protocol": "tcp", "port_range": "443", "remote_ip": "0.0.0.0/0", "description": "https" } ],
"created_at": "2026-10-01T12:00:00Z"
}
EndpointScopeNotes
GET /firewallsfirewalls:readPaginated
GET /firewalls/{id}firewalls:readWith its rules
POST /firewallsfirewalls:writeBody name, datacenter_id (required), description. Created empty; add rules afterwards. Answers 201
PUT /firewalls/{id}firewalls:writeBody name and/or description; at least one. The answer carries no rules
DELETE /firewalls/{id}firewalls:write
POST /firewalls/{id}/rulesfirewalls:writeBody direction (ingress/egress) and protocol (tcp, udp, icmp, esp, gre) required; port_range, remote_ip, description optional. port_range is a port or an ascending range within 1-65535 and remote_ip an IP address or CIDR, otherwise 400. Answers 201 with the new rule
DELETE /firewalls/{id}/rules/{rule_id}firewalls:writerule_id is the rule's id string
POST /firewalls/{id}/attachfirewalls:writeBody {"server_ids": [4821]}, 1-50 ids, servers in the firewall's datacenter
POST /firewalls/{id}/detachfirewalls:writeSame body

Billing​

GET /balance​

Scope balance:read. One wallet per currency. primary marks the default wallet used when currency_id is omitted at creation.

{
"success": true,
"data": { "wallets": [
{ "id": 31, "balance": 25.4, "primary": true, "status": "active",
"currency": { "id": 1, "code": "USD", "name": "US Dollar", "symbol": "$" } }
] }
}

GET /balance/transactions​

Scope balance:read. Paginated.

{ "id": 905, "mode": "debit", "amount": 4.5, "status": "paid", "reference": "0b6c...", "type": "service", "currency": { "id": 1, "code": "USD", "name": "US Dollar", "symbol": "$" }, "created_at": "2026-10-02T09:30:00Z" }

GET /invoices​

Scope invoices:read. Paginated, newest first; zero-amount invoices are not listed. Query: page, limit, status (draft, issued, paid, overdue, cancelled).

{
"number": "INV-2026-000123", "status": "paid", "payment_status": "paid",
"subtotal": 4.5, "discount": 0, "tax": 0, "total": 4.5,
"issued_at": "2026-10-02T09:30:00Z", "due_at": "2026-10-09T09:30:00Z", "paid_at": "2026-10-02T09:31:00Z"
}

GET /invoices/{number}​

Scope invoices:read. One invoice with its line items. The number is text (2041 or INV-2026-000123), never convert it to an integer.

{ "...": "invoice fields as above", "items": [ { "amount": 4.5, "currency": { "id": 1, "code": "USD", "name": "US Dollar", "symbol": "$" }, "description": "std-2 hourly" } ] }

Migrating from earlier versions​

  • The base URL is https://api.ariaservice.net/api/v1.
  • POST /servers now requires an Idempotency-Key header and answers 202; create and action answers are small fixed objects, not the full server.
  • Deleting a firewall rule uses the rule's id (DELETE /firewalls/{id}/rules/{rule_id}), not its position in the list.
  • A firewall is created empty; add rules with POST /firewalls/{id}/rules.
  • Creating an SSH key requires datacenter_id.
  • New: GET /servers/{id}/actions, GET /invoices, GET /invoices/{number}, scope invoices:read.