API conventions
Every Arianet API is REST over HTTPS, takes and returns JSON, and authenticates with a Bearer token. This page puts the shared rules and each product's differences in one place, so your client works correctly against all of them.
Base URLs and versions
| API | Base URL | OpenAPI document |
|---|---|---|
| Arianet Cloud | https://api.ariaservice.net/cloud/v1 | openapi.json |
| VPS | https://api.ariaservice.net/api/v1 | openapi.yaml |
| Regional VPS | https://api.ariaservice.net/vps/v1 | openapi.json |
| Hosting | https://api.ariaservice.net/hosting/v1 | openapi.json |
| Dedicated servers | https://api.ariaservice.net/baremetal/v1 | openapi.json |
| SMS | https://api.ariaservice.net/sms/v1 | openapi.json |
The version is part of the path (v1). Write clients that ignore unknown response fields; new fields and values are added without a version change.
Requests and responses
POST,PUTandPATCHbodies are JSON and needContent-Type: application/json.- Times are UTC in ISO 8601 / RFC 3339 (
2026-10-02T09:30:00Z). - Ids are integers unless the product page says otherwise (invoice numbers and SMS message ids, for example, are strings).
PATCHchanges only the fields you send.
Successful responses come in two shapes:
- Cloud, regional VPS, hosting, dedicated, SMS
- VPS (/api/v1)
{ "data": { "id": 12, "name": "web-1" } }
{ "success": true, "data": { "id": 4821, "status": "active" } }
Errors
Every error carries a stable, machine-readable code. Base your logic on the HTTP status and the code, never on the message; the message is for people and may change.
| API | Error format |
|---|---|
| Cloud | {"error": "...", "code": "not_found", "reason": "..."}; reason, when present, narrows the case down |
| Regional VPS, hosting, dedicated servers | {"error": "...", "code": "not_found"} |
| SMS | {"code": "not_found", "message": "..."} |
VPS (/api/v1) | {"success": false, "error": {"code": "NOT_FOUND", "message": "..."}} |
HTTP statuses mean the same thing everywhere:
| HTTP | Means | Retry? |
|---|---|---|
400 / 422 | Invalid input, or a valid request that cannot be done right now | No; fix the request |
401 | No token, or an invalid one | No |
402 | Not enough wallet balance | After topping up |
403 | Not allowed | No |
404 | The resource does not exist in your account | No |
409 / 423 | The resource's state does not allow it now, or another operation holds it | Yes, a little later |
429 | Rate limited | Yes, after Retry-After |
500 / 502 / 503 | Temporary server-side error | Yes, with growing back-off |
504 | The answer did not arrive in time; the request may have run | Read the state first |
The full list of codes for each API is on its product page and in its OpenAPI document.
Rate limits
| API | Limit |
|---|---|
| Cloud | 60 requests a minute and 3,600 an hour per account and user |
VPS (/api/v1) | Reads 120 a minute; writes about 20 a minute; creating servers 5 an hour per token (details) |
| Regional VPS, hosting, dedicated servers, SMS | Per caller; the current values are in the RateLimit-* headers |
The newer APIs send these headers on every response:
| Header | Meaning |
|---|---|
RateLimit-Limit | Requests allowed in the current window |
RateLimit-Remaining | Requests left |
RateLimit-Reset | Seconds until the next window |
Over the limit, the answer is 429 and Retry-After (in seconds) says how long to wait.
Retrying
Retry 429, 502, 503 and network errors with exponential back-off, honouring Retry-After when present. Only retry requests that spend money with an idempotency key.
- Python
- bash
import os
import random
import time
import requests
RETRY = {429, 502, 503}
def call(method, url, **kwargs):
headers = {"Authorization": f"Bearer {os.environ['ARIANET_TOKEN']}", **kwargs.pop("headers", {})}
for attempt in range(6):
resp = requests.request(method, url, headers=headers, timeout=30, **kwargs)
if resp.status_code not in RETRY:
return resp
wait = float(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait + random.random())
return resp
for attempt in 1 2 3 4 5; do
code=$(curl -s -o /tmp/out.json -w '%{http_code}' "$URL" -H "Authorization: Bearer $ARIANET_TOKEN")
case "$code" in
429|502|503) sleep $((2 ** attempt)) ;;
*) break ;;
esac
done
cat /tmp/out.json
The CLI retries rate-limited reads by itself; it never retries changes automatically.
Pagination
| API | Parameters | Page information in the response |
|---|---|---|
| Cloud | page, per_page (1 to 100, default 25); without them the whole list comes in one page | meta.pagination: page, per_page, previous_page, next_page, last_page |
| SMS | page, per_page (up to 200) | meta: total, current_page, per_page |
VPS (/api/v1) | page, limit (1 to 100, default 15) | pagination: current_page, per_page, total, last_page |
| Regional VPS, hosting, dedicated servers | Lists come back whole; action lists take limit | - |
Asynchronous operations
Work that takes time (creating, deleting, reinstalling, resizing, power) answers 201 or 202 at once and runs in the background. Follow it to the end like this:
| API | What to poll | Final state |
|---|---|---|
| Cloud | GET /actions/{id} with the response's action_id | status: success or error |
| Regional VPS, hosting | GET /actions/{id} | state: success or error |
| Dedicated servers | GET /orders/{id} (delivery steps) | Order delivered or cancelled |
| SMS | GET /batches/{id} or the webhook | completed, partial or failed |
VPS (/api/v1) | GET /servers/{id}/status or GET /servers/{id}/actions | status is active / action success or failed |
Poll every few seconds (every 5 seconds, say) and cap the total wait. The CLI does this with --wait and actions wait.
Safe retries (idempotency)
If the answer to an order never reaches you (a dropped connection, a 504), you cannot know whether it was placed. With an idempotency key you can safely send the same request again: no second order is made and the first answer comes back.
| API | Requests | How |
|---|---|---|
VPS (/api/v1) | POST /servers | Idempotency-Key header, required (details) |
| Regional VPS | POST /servers | Idempotency-Key header |
| Hosting | POST /accounts | Idempotency-Key header |
| Dedicated servers | POST /orders, POST /servers/{id}/renew | Idempotency-Key header |
| SMS | POST /messages | client_reference field (or the Idempotency-Key header) |
Make a new key (a UUID, for example) for every new order, and reuse it for retries until you have a definite answer.
curl -s -X POST https://api.ariaservice.net/vps/v1/servers \
-H "Authorization: Bearer $ARIANET_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"datacenter":"thr-1","plan_id":4,"image":"ubuntu-24.04","hostname":"web-1","billing_mode":"hourly"}'
Regions
Regional VPS and hosting have a separate install per region. Pick the region with the Arianet-Region header; without it, the request goes to the product's default region:
curl -s https://api.ariaservice.net/hosting/v1/accounts \
-H "Authorization: Bearer $ARIANET_TOKEN" \
-H "Arianet-Region: iran-1"
An unknown region answers 404 region_not_found. SMS runs only in iran-1 and dedicated servers have a single install; neither needs the header.
Timeouts and 504
Requests that travel to another region can end with 504 gateway_timeout even though the work was done. Before retrying, read the state (the resource list or the actions) or use an idempotency key.