---
name: void-api
description: Use when writing code or acting as an agent against the Void REST API (api.void.cd/v1, a Bearer "void_..." key) to check, register or renew domains, manage their Cloudflare zone, DNS records, settings and redirects, order and run VPS servers (power, reinstall, renewal, upgrade, firewall), order and manage web hosting, or set up webhooks on a Void account. Gives auth, the response envelope, every endpoint with parameters, a curl line and error codes, and the rules for money, secrets and limits.
---

# Void API 1.0.0 - skill for AI agents

Generated by build-skill.py from https://api.void.cd/openapi.json (59 operations); the human docs are https://api.void.cd/docs/ . English only.

## Basics

- Base URL: `https://api.void.cd/v1`. JSON in and out. HTTPS only.
- Auth: every call sends `Authorization: Bearer void_...`; only `GET /v1` (an index of resources) works without it, everything else answers `401 UNAUTHORIZED`.
- Keys are made by the account owner in the Telegram bot @voidhostbot: profile -> API -> create a key. The key is shown once; up to 5 keys per account, each revocable on the same screen. A key belongs to the account: whoever holds it can spend the balance.
- Envelope: success `{ "ok": true, "data": ... }`; failure `{ "ok": false, "error": { "code": "...", "message": "...", "details": ... } }` (`details` only when there is something structured: validation issues, the missing amount, a new quote).
- The API speaks in machine codes: switch on `error.code` (stable strings); `error.message` is English text for humans and may change.
- Types: money is a decimal string in USD with 2 places (`"14.50"`), never a float; times are ISO 8601 UTC; ids are UUID v4, except Cloudflare's own ids for DNS records and redirects; domain names are lowercase, without www.
- Lists: `offset` / `limit` in the query (max 100; servers and hosting return at most 50 per page); the answer is `{ items, total }`.
- Status: 200 ok, 201 created, 202 accepted and running in the background (server actions and upgrade, webhook test), 4xx your side (see the code), 5xx ours or upstream (safe to retry later).
- Rate limits (sliding window): 120 requests / minute per key, and a separate 20 / minute for check, search and ordering a domain, server or hosting. Over it: `429 RATE_LIMITED`; wait a few seconds and retry. Cache availability answers for a few minutes; prefer `/domains/search` (one call, all zones) to many single checks.

Shell setup for the curl lines below (`$ID`, `$RECORD_ID`, `$RULE_ID` are ids from earlier answers):

```sh
API=https://api.void.cd/v1
AUTH="Authorization: Bearer $VOID_KEY"   # the key comes from the environment, never from the code
JSON="Content-Type: application/json"
```

## Rules an agent must not break

1. Never print, log, echo or commit an API key, and never put it in code or in an answer to the user: read it from the environment or a secret store. It is a bearer secret that spends money.
2. Secrets come only from the credentials endpoints: `GET /servers/{id}/credentials` (root / Administrator password, a pre-installed app's login) and `GET /hosting/{id}/credentials` (panel password). Lists, details and order answers never carry them; do not look elsewhere, and show them only to a user who asked. A webhook `secret` is returned only by create and rotate-secret.
3. Orders (`POST /domains`, `/servers`, `/hosting`), renewals (`POST .../renew`), a server upgrade and a hosting plan change charge the balance immediately. Before each, show the user the price from its quote (`/domains/check`, `/renew-quote`, `/term`, the catalogue, `/upgrade/quote`, `/plan/quote`) and get a yes. If a later quote is higher than the price the user accepted, stop and ask again. Pass the quoted charge as `maxCharge` (upgrade, plan change); on `400 UPGRADE_PRICE_CHANGED` (details = the new price) ask the user again, never raise `maxCharge` on your own. The API cannot top up the balance; that is done in the bot.
4. Idempotency: reads are safe to retry. A repeated `POST /domains` for the same name, or `POST /hosting` for the same domain, fails with `409 DOMAIN_UNAVAILABLE` once the first succeeded. `POST /servers` and the renewals are not idempotent: every success is another server or another term. After a timeout, read the list or the object first (`GET /domains`, `/servers`, `/hosting`) and retry only if the call did not go through.
5. `/domains/check` verifies at the registry: only `available: true` means the name can be bought. `reason: UNCERTAIN` means the registry could not be reached (try again shortly), never "free". Renewal prices can differ from registration prices: read `/renew-quote` before renewing.
6. Server power, reinstall, upgrade and firewall calls need an `ACTIVE` server and run one at a time: `409 SERVER_BUSY` means wait until the running operation is done. A new server answers `201` in `PENDING`; poll `GET /servers/{id}` until `ACTIVE`. `reinstall` erases the disk.
7. Webhooks: answer 2xx within 10 seconds; verify `X-Void-Signature: sha256=<hex>` = HMAC-SHA256(secret, timestamp + "." + raw body) over the raw body before parsing, reject old `X-Void-Timestamp` values (unix seconds), and use `X-Void-Delivery` (unique per delivery) for idempotency. Failed deliveries are retried with backoff; 50 failures in a row switch the endpoint off (`active: false`) until re-enabled. Up to 5 endpoints, HTTPS only.

## Endpoints

Format: `METHOD /path` - what it does. `query` / `body`: fields as `name type`, `*` = required, `a|b` = one of. `ok`: the successful answer (inside `data`) | `errors`: codes worth handling for this call. Every call can also answer `401 UNAUTHORIZED` and `429 RATE_LIMITED`.

### Domains

#### Account

- `GET /me` - Account.
  ok: 200 balance and account id
  `curl -s "$API/me" -H "$AUTH"`

#### Availability & prices

Prices and registry-verified availability checks.

- `GET /domains/tlds` - Price list.
  ok: 200 registration prices per TLD
  `curl -s "$API/domains/tlds" -H "$AUTH"`
- `GET /domains/check` - Check one name.
  query: domain* string
  ok: 200 availability + price
  `curl -s "$API/domains/check?domain=example.xyz" -H "$AUTH"`
- `GET /domains/search` - Search a bare name across all TLDs.
  query: q* string
  ok: 200 list of Availability
  `curl -s "$API/domains/search?q=example" -H "$AUTH"`

#### Domains

Domains you own: list, register, nameservers, auto-renew.

- `GET /domains` - List your domains.
  query: offset int; limit int ..100
  ok: 200 items, total
  `curl -s "$API/domains" -H "$AUTH"`
- `POST /domains` - Register a domain (charges the balance).
  body: domain* string
  ok: 201 Domain | errors: 402 INSUFFICIENT_FUNDS; 409 DOMAIN_UNAVAILABLE
  `curl -s -X POST "$API/domains" -H "$AUTH" -H "$JSON" -d '{"domain":"example.xyz"}'`
- `GET /domains/{id}` - One domain.
  ok: 200 Domain
  `curl -s "$API/domains/$ID" -H "$AUTH"`
- `PATCH /domains/{id}` - Change nameservers and/or auto-renew.
  body: nameservers string[](2..4); autoRenew bool
  ok: 200 Domain
  `curl -s -X PATCH "$API/domains/$ID" -H "$AUTH" -H "$JSON" -d '{"autoRenew":true}'`

#### Renewal

- `GET /domains/{id}/renew-quote` - Renewal price and new expiry.
  ok: 200 price, years, expiresAfter
  `curl -s "$API/domains/$ID/renew-quote" -H "$AUTH"`
- `POST /domains/{id}/renew` - Renew (charges the balance).
  ok: 200 Domain | errors: 402 INSUFFICIENT_FUNDS
  `curl -s -X POST "$API/domains/$ID/renew" -H "$AUTH"`

### Cloudflare

#### Zone

Connect, disconnect, status.

- `GET /domains/{id}/cloudflare` - Cloudflare zone status.
  ok: 200 connected, nameservers, zoneStatus
  `curl -s "$API/domains/$ID/cloudflare" -H "$AUTH"`
- `POST /domains/{id}/cloudflare` - Connect the domain to Cloudflare.
  ok: 201 zone created; nameservers switched
  `curl -s -X POST "$API/domains/$ID/cloudflare" -H "$AUTH"`
- `DELETE /domains/{id}/cloudflare` - Disconnect from Cloudflare.
  ok: 200 disconnected
  `curl -s -X DELETE "$API/domains/$ID/cloudflare" -H "$AUTH"`
- `POST /domains/{id}/cloudflare/refresh` - Re-read zone status from Cloudflare.
  ok: 200 zone status
  `curl -s -X POST "$API/domains/$ID/cloudflare/refresh" -H "$AUTH"`

#### DNS records

- `GET /domains/{id}/dns` - DNS records (Cloudflare).
  ok: 200 list of DnsRecord
  `curl -s "$API/domains/$ID/dns" -H "$AUTH"`
- `POST /domains/{id}/dns` - Add a record.
  body: type* A|AAAA|CNAME|TXT|MX; name* string; content* string; proxied bool; priority int
  ok: 201 DnsRecord | errors: 409 DNS_EXISTS
  `curl -s -X POST "$API/domains/$ID/dns" -H "$AUTH" -H "$JSON" -d '{"type":"A","name":"@","content":"203.0.113.10"}'`
- `PUT /domains/{id}/dns/{recordId}` - Replace a record.
  body: type* A|AAAA|CNAME|TXT|MX; name* string; content* string; proxied bool; priority int
  ok: 200 DnsRecord
  `curl -s -X PUT "$API/domains/$ID/dns/$RECORD_ID" -H "$AUTH" -H "$JSON" -d '{"type":"A","name":"@","content":"203.0.113.10"}'`
- `DELETE /domains/{id}/dns/{recordId}` - Delete a record.
  ok: 200 deleted
  `curl -s -X DELETE "$API/domains/$ID/dns/$RECORD_ID" -H "$AUTH"`

#### Settings & cache

SSL mode, security switches, cache.

- `GET /domains/{id}/cloudflare/settings` - Zone settings.
  ok: 200 Settings
  `curl -s "$API/domains/$ID/cloudflare/settings" -H "$AUTH"`
- `PATCH /domains/{id}/cloudflare/settings` - Change any subset of settings.
  body: ssl off|flexible|full|strict; developmentMode bool; underAttack bool; alwaysOnline bool; botFightMode bool; regionBlock bool; bypassCache bool
  ok: 200 Settings after the change
  `curl -s -X PATCH "$API/domains/$ID/cloudflare/settings" -H "$AUTH" -H "$JSON" -d '{"ssl":"strict"}'`
- `POST /domains/{id}/cloudflare/purge` - Purge the cache.
  ok: 200 purged
  `curl -s -X POST "$API/domains/$ID/cloudflare/purge" -H "$AUTH"`

#### Redirects

- `GET /domains/{id}/cloudflare/redirects` - Redirect rules.
  ok: 200 list of Redirect
  `curl -s "$API/domains/$ID/cloudflare/redirects" -H "$AUTH"`
- `POST /domains/{id}/cloudflare/redirects` - Add a redirect.
  body: match* string; target* uri; permanent bool default true
  ok: 201 Redirect | errors: 409 REDIRECT_EXISTS / REDIRECT_LIMIT
  `curl -s -X POST "$API/domains/$ID/cloudflare/redirects" -H "$AUTH" -H "$JSON" -d '{"match":"*","target":"https://new-domain.com"}'`
- `DELETE /domains/{id}/cloudflare/redirects/{ruleId}` - Remove a redirect.
  ok: 200 deleted
  `curl -s -X DELETE "$API/domains/$ID/cloudflare/redirects/$RULE_ID" -H "$AUTH"`

### Servers

#### Order & manage

KVM VPS: catalogue, order, details, credentials, power and reinstall.

- `GET /servers/catalogue` - What can be ordered: images, apps, regions, configurator limits, proxy.
  ok: 200 ServerCatalogue
  `curl -s "$API/servers/catalogue" -H "$AUTH"`
- `GET /servers` - List your servers (newest first, deleted excluded).
  query: offset int 0.., default 0; limit int 1..100, default 20 (at most 50 items are returned per page)
  ok: 200 items, total
  `curl -s "$API/servers" -H "$AUTH"`
- `POST /servers` - Order a server (charges the first term). Heavy rate limit (20/min). Not idempotent: every success is another paid server. If provisioning fails for good the server ends FAILED and the first term is refunded.
  body: spec* Spec{vcpu* int, ramGb* int, diskGb* int}; os string <=40 chars (image code from the catalogue; required unless app is given); app string <=40 chars (pre-installed app code; forces its baseOs and needs minRamMb); period* MONTH|QUARTER|HALF_YEAR|YEAR; region string <=40 chars; proxy bool (front-proxy add-on, priced into every term); sshKeyIds uuid[](0..10) (keys saved on the account (managed in the bot); foreign ids are ignored)
  ok: 201 Server in PENDING; poll GET /servers/{id} until ACTIVE | errors: 400 VALIDATION_ERROR, NO_CAPACITY, NO_IP_AVAILABLE, NO_TEMPLATE, PROXY_NO_CAPACITY - nothing charged; 402 INSUFFICIENT_FUNDS
  `curl -s -X POST "$API/servers" -H "$AUTH" -H "$JSON" -d '{"spec":{"vcpu":2,"ramGb":4,"diskGb":80},"period":"MONTH"}'`
- `GET /servers/{id}` - One server.
  ok: 200 Server | errors: 404 SERVER_NOT_FOUND
  `curl -s "$API/servers/$ID" -H "$AUTH"`
- `PATCH /servers/{id}` - Change auto-renew and/or alias.
  body: autoRenew bool (ACTIVE / SUSPENDED servers only); alias string|null <=32 chars (null or empty clears it)
  ok: 200 Server | errors: 404 SERVER_NOT_FOUND
  `curl -s -X PATCH "$API/servers/$ID" -H "$AUTH" -H "$JSON" -d '{"autoRenew":true}'`
- `GET /servers/{id}/credentials` - Login and password (the only place secrets are returned).
  ok: 200 ServerCredentials | errors: 404 SERVER_NOT_FOUND
  `curl -s "$API/servers/$ID/credentials" -H "$AUTH"`

#### Power & reinstall

- `POST /servers/{id}/actions` - Start, stop, reboot or reinstall. reinstall erases the disk and clones a clean image (the same one when neither os nor app is given); IP, size and password are kept. NO_TEMPLATE if the image is not on the server's node.
  body: action* start|stop|reboot|reinstall; os string <=40 chars (reinstall only: another image); app string <=40 chars (reinstall only: a pre-installed app)
  ok: 202 queued | errors: 400 SERVER_NOT_ACTIVE, VALIDATION_ERROR; 404 SERVER_NOT_FOUND; 409 SERVER_BUSY
  `curl -s -X POST "$API/servers/$ID/actions" -H "$AUTH" -H "$JSON" -d '{"action":"start"}'`

#### Renewal, term, upgrade

Renewal, renewal term, upgrade (bigger only, pro rata).

- `POST /servers/{id}/renew` - Renew one term (charges the balance). Extends from max(expiresAt, now); a SUSPENDED server is resumed. Each success adds a term.
  ok: 200 Server | errors: 400 SERVER_NOT_ACTIVE; 402 INSUFFICIENT_FUNDS; 404 SERVER_NOT_FOUND
  `curl -s -X POST "$API/servers/$ID/renew" -H "$AUTH"`
- `GET /servers/{id}/term` - Renewal terms with today's prices.
  ok: 200 TermOption[] | errors: 404 SERVER_NOT_FOUND
  `curl -s "$API/servers/$ID/term" -H "$AUTH"`
- `PUT /servers/{id}/term` - Change the renewal term (nothing charged now). The new term and its price apply from the next renewal; expiresAt does not move.
  body: period* MONTH|QUARTER|HALF_YEAR|YEAR
  ok: 200 TermOption[] | errors: 404 SERVER_NOT_FOUND
  `curl -s -X PUT "$API/servers/$ID/term" -H "$AUTH" -H "$JSON" -d '{"period":"MONTH"}'`
- `POST /servers/{id}/upgrade/quote` - Price of an upgrade. Only bigger: every value the same or larger, at least one larger.
  body: spec* Spec{vcpu* int, ramGb* int, diskGb* int}
  ok: 200 UpgradeQuote | errors: 400 SERVER_NOT_ACTIVE, VALIDATION_ERROR; 404 SERVER_NOT_FOUND
  `curl -s -X POST "$API/servers/$ID/upgrade/quote" -H "$AUTH" -H "$JSON" -d '{"spec":{"vcpu":2,"ramGb":4,"diskGb":80}}'`
- `POST /servers/{id}/upgrade` - Upgrade (charges the balance, applied by a restart).
  body: spec* Spec{vcpu* int, ramGb* int, diskGb* int}; maxCharge* string (the most you agree to pay now - the quote's charge)
  ok: 202 UpgradeQuote that was charged; the resize is queued | errors: 400 UPGRADE_PRICE_CHANGED (details = fresh UpgradeQuote), NO_CAPACITY, SERVER_NOT_ACTIVE, VALIDATION_ERROR; 402 INSUFFICIENT_FUNDS; 404 SERVER_NOT_FOUND; 409 SERVER_BUSY
  `curl -s -X POST "$API/servers/$ID/upgrade" -H "$AUTH" -H "$JSON" -d '{"spec":{"vcpu":2,"ramGb":4,"diskGb":80},"maxCharge":"5.48"}'`

#### Firewall

Inbound allow-list on the hypervisor.

- `GET /servers/{id}/firewall` - Firewall state and rules.
  ok: 200 Firewall | errors: 404 SERVER_NOT_FOUND
  `curl -s "$API/servers/$ID/firewall" -H "$AUTH"`
- `PATCH /servers/{id}/firewall` - Turn the firewall on or off. On = inbound dropped except the rules. Tenant isolation and anti-spoofing stay on in every state; proxy 80/443 is allowed automatically.
  body: enabled* bool
  ok: 200 Firewall | errors: 400 SERVER_NOT_ACTIVE, VALIDATION_ERROR (malformed rule, 20 rules already); 404 SERVER_NOT_FOUND, NOT_FOUND; 409 SERVER_BUSY; 500 PROVIDER_ERROR - the hypervisor refused; previous rules kept
  `curl -s -X PATCH "$API/servers/$ID/firewall" -H "$AUTH" -H "$JSON" -d '{"enabled":true}'`
- `POST /servers/{id}/firewall/rules` - Add a rule. Up to 20 rules; an identical rule changes nothing.
  body: rule* string <=80 chars ("22", "80,443", "8000:8100/udp", "3306 from 1.2.3.4", "ping")
  ok: 201 Firewall | errors: as for the call above
  `curl -s -X POST "$API/servers/$ID/firewall/rules" -H "$AUTH" -H "$JSON" -d '{"rule":"3306 from 1.2.3.4"}'`
- `DELETE /servers/{id}/firewall/rules/{index}` - Remove the rule at a position.
  ok: 200 Firewall | errors: as for the call above
  `curl -s -X DELETE "$API/servers/$ID/firewall/rules/0" -H "$AUTH"`

### Hosting

Shared web hosting (HestiaCP): plans, order, credentials, renewal, term, plan change.

- `GET /hosting/catalogue` - Hosting plans and prices.
  ok: 200 HostingCatalogue
  `curl -s "$API/hosting/catalogue" -H "$AUTH"`
- `GET /hosting` - List your hosting accounts (PENDING, ACTIVE, SUSPENDED).
  query: offset int 0.., default 0; limit int 1..100, default 20 (at most 50 items are returned per page)
  ok: 200 items, total
  `curl -s "$API/hosting" -H "$AUTH"`
- `POST /hosting` - Order hosting (charges the term). Heavy rate limit (20/min). Created synchronously; a panel failure refunds the charge.
  body: plan* string <=40 chars (plan code); domain* string <=253 chars; period* MONTH|QUARTER|HALF_YEAR|YEAR
  ok: 201 Hosting, already ACTIVE | errors: 400 DOMAIN_INVALID, PLAN_INACTIVE, VALIDATION_ERROR; 402 INSUFFICIENT_FUNDS; 404 PLAN_NOT_FOUND; 409 DOMAIN_UNAVAILABLE - the domain is already on a hosting account; 500 PROVIDER_ERROR (refunded), PROVIDER_DISABLED
  `curl -s -X POST "$API/hosting" -H "$AUTH" -H "$JSON" -d '{"plan":"web-start","domain":"example.xyz","period":"MONTH"}'`
- `GET /hosting/{id}` - One hosting account.
  ok: 200 Hosting | errors: 404 HOSTING_NOT_FOUND
  `curl -s "$API/hosting/$ID" -H "$AUTH"`
- `PATCH /hosting/{id}` - Change auto-renew.
  body: autoRenew* bool
  ok: 200 Hosting | errors: 404 HOSTING_NOT_FOUND
  `curl -s -X PATCH "$API/hosting/$ID" -H "$AUTH" -H "$JSON" -d '{"autoRenew":true}'`
- `GET /hosting/{id}/credentials` - Panel URL, login and password.
  ok: 200 HostingCredentials | errors: 404 HOSTING_NOT_FOUND
  `curl -s "$API/hosting/$ID/credentials" -H "$AUTH"`
- `POST /hosting/{id}/renew` - Renew one term (charges the balance). Extends from max(expiresAt, now); a SUSPENDED account becomes ACTIVE.
  ok: 200 Hosting | errors: 400 HOSTING_NOT_ACTIVE; 402 INSUFFICIENT_FUNDS; 404 HOSTING_NOT_FOUND
  `curl -s -X POST "$API/hosting/$ID/renew" -H "$AUTH"`
- `GET /hosting/{id}/term` - Renewal terms with today's prices.
  ok: 200 TermOption[] | errors: 404 HOSTING_NOT_FOUND
  `curl -s "$API/hosting/$ID/term" -H "$AUTH"`
- `PUT /hosting/{id}/term` - Change the renewal term (nothing charged now).
  body: period* MONTH|QUARTER|HALF_YEAR|YEAR
  ok: 200 TermOption[] | errors: 404 HOSTING_NOT_FOUND
  `curl -s -X PUT "$API/hosting/$ID/term" -H "$AUTH" -H "$JSON" -d '{"period":"MONTH"}'`
- `POST /hosting/{id}/plan/quote` - Price of moving to a bigger plan. Only a more expensive plan is accepted.
  body: plan* string <=40 chars
  ok: 200 PlanQuote | errors: 400 HOSTING_NOT_ACTIVE, VALIDATION_ERROR; 404 HOSTING_NOT_FOUND, PLAN_NOT_FOUND
  `curl -s -X POST "$API/hosting/$ID/plan/quote" -H "$AUTH" -H "$JSON" -d '{"plan":"web-start"}'`
- `POST /hosting/{id}/plan` - Change to a bigger plan (charges the balance).
  body: plan* string <=40 chars; maxCharge* string
  ok: 200 PlanQuote that was charged | errors: 400 UPGRADE_PRICE_CHANGED (details = { charge }), HOSTING_NOT_ACTIVE, VALIDATION_ERROR; 402 INSUFFICIENT_FUNDS; 404 HOSTING_NOT_FOUND, PLAN_NOT_FOUND; 500 PROVIDER_ERROR - the panel refused; charge refunded, plan restored
  `curl -s -X POST "$API/hosting/$ID/plan" -H "$AUTH" -H "$JSON" -d '{"plan":"web-start","maxCharge":"4.52"}'`

### Webhooks

Signed event callbacks: domain.blocked, domain.unblocked, domain.registered, domain.renewed, domain.expiring, domain.expired.

- `GET /webhooks/events` - Event names.
  ok: 200 list of strings
  `curl -s "$API/webhooks/events" -H "$AUTH"`
- `GET /webhooks` - Your endpoints.
  ok: 200 list of Webhook
  `curl -s "$API/webhooks" -H "$AUTH"`
- `POST /webhooks` - Add an endpoint (secret returned once).
  body: url* uri; events string[]
  ok: 201 Webhook with secret | errors: 400 VALIDATION_ERROR (non-https / private url)
  `curl -s -X POST "$API/webhooks" -H "$AUTH" -H "$JSON" -d '{"url":"https://your.host/void-webhook"}'`
- `PATCH /webhooks/{id}` - Enable / disable.
  body: active* bool
  ok: 200 Webhook
  `curl -s -X PATCH "$API/webhooks/$ID" -H "$AUTH" -H "$JSON" -d '{"active":true}'`
- `DELETE /webhooks/{id}` - Remove.
  ok: 200 deleted
  `curl -s -X DELETE "$API/webhooks/$ID" -H "$AUTH"`
- `POST /webhooks/{id}/test` - Queue a ping.
  ok: 202 deliveryId
  `curl -s -X POST "$API/webhooks/$ID/test" -H "$AUTH"`
- `POST /webhooks/{id}/rotate-secret` - Issue a new secret.
  ok: 200 Webhook with new secret
  `curl -s -X POST "$API/webhooks/$ID/rotate-secret" -H "$AUTH"`
- `GET /webhooks/{id}/deliveries` - Last 20 deliveries.
  ok: 200 list of Delivery
  `curl -s "$API/webhooks/$ID/deliveries" -H "$AUTH"`

## Errors (all codes)

- 400 `VALIDATION_ERROR` - malformed request; details[] has path and message per field
- 400 `DOMAIN_INVALID` - not a valid domain name
- 400 `SERVER_NOT_ACTIVE, HOSTING_NOT_ACTIVE` - the service is not in a state that allows the operation (not ready yet, failed, deleted)
- 400 `NO_CAPACITY, NO_IP_AVAILABLE, NO_TEMPLATE, PROXY_NO_CAPACITY, PLAN_INACTIVE` - no room for this size, image, add-on or plan right now; nothing charged
- 400 `UPGRADE_PRICE_CHANGED` - the price is above your maxCharge; nothing charged, details has the new price
- 401 `UNAUTHORIZED` - missing, unknown or revoked key
- 402 `INSUFFICIENT_FUNDS` - balance too low; details = { required, balance, missing }
- 403 `USER_BANNED` - account suspended
- 404 `DOMAIN_NOT_FOUND, SERVER_NOT_FOUND, HOSTING_NOT_FOUND, PLAN_NOT_FOUND, DNS_NOT_FOUND, NOT_FOUND` - not on this account / no such endpoint
- 409 `DOMAIN_UNAVAILABLE` - taken, premium, or a zone we do not sell; for hosting - the domain is already on a hosting account
- 409 `SERVER_BUSY` - another operation on this server is queued or running - retry when it is done
- 409 `CLOUDFLARE_ALREADY, CLOUDFLARE_NOT_CONNECTED, CLOUDFLARE_ZONE_PENDING` - zone state does not allow the operation
- 409 `DNS_EXISTS, REDIRECT_EXISTS, REDIRECT_LIMIT` - duplicate or limit reached
- 429 `RATE_LIMITED` - over the per-key limit - back off
- 502 `REGISTRAR_ERROR, CLOUDFLARE_ERROR` - upstream failed; retry later, nothing was charged
- 503 `CLOUDFLARE_UNAVAILABLE, CLOUDFLARE_NO_ACCOUNTS` - temporarily out of capacity
- 5xx `PROVIDER_ERROR, PROVIDER_DISABLED` - the hypervisor or the hosting panel failed, or hosting sales are off; a paid order is refunded

## Objects (inside `data`)

- Domain: id uuid, domainName string, status PENDING|ACTIVE|EXPIRED|FAILED, nameservers string[], price string, renewalPrice string, renewalYears int, autoRenew bool, expiresAt date-time, createdAt date-time, blockFlags string[]
- Availability: domain string, available bool, price string|null, reason UNSUPPORTED_TLD|UNCERTAIN
- DnsRecord: id string, type string, name string, content string, proxied bool, priority int|null
- Settings: ssl off|flexible|full|strict, developmentMode bool, underAttack bool, alwaysOnline bool, botFightMode bool, regionBlock bool, bypassCache bool
- Redirect: id string, match string, target string, permanent bool
- Zone: connected bool, domainName string, zoneId string|null, nameservers string[], zoneStatus none|pending|active|moved, nsApplied bool
- RenewQuote: domainId string, domainName string, price string, years int, expiresAfter date-time
- Webhook: id string, url string, events string[], active bool, failures int, lastStatus int|null, lastDeliveredAt string|null, createdAt string, secret string (only in the create / rotate-secret response)
- Delivery: id string, event string, status PENDING|SENT|FAILED, attempts int, lastStatus int|null, lastError string|null, createdAt string, sentAt string|null, nextAt string|null
- Spec: vcpu int, ramGb int, diskGb int -- Integers inside the catalogue's custom limits and steps (disk in 20 GB steps).
- Server: id uuid, label string, alias string|null, hostname string, status PENDING|PROVISIONING|ACTIVE|SUSPENDED|FAILED|TERMINATED, power RUNNING|STOPPED|UNKNOWN (refreshed periodically; may lag behind an action), ip string|null, os string, osCode string, specs object, region string|null, period WEEK|MONTH|QUARTER|HALF_YEAR|YEAR (renewal term (WEEK only on legacy rows)), periodPrice string (what each renewal costs), autoRenew bool, expiresAt date-time, graceEndsAt date-time|null (set while SUSPENDED; the server is deleted after it), createdAt date-time, proxy object (front-proxy add-on), sshKeys int (number of keys installed at first boot), passwordLogin bool, app object|null (pre-installed app; login/password only via /credentials) -- No secrets: the root password and app login are only in ServerCredentials.
- ServerCredentials: ip string|null, login root|Administrator, password string|null, app object|null
- ServerCatalogue: plans object[] (the bot's fixed plans, for reference; the API orders by spec), os object[], apps object[], regions string[], custom {cpu, ramGb, diskGb, base, defaults} (configurator limits (max reflects free node capacity) and USD-per-month rates as plain numbers), periods object (price multiplier against one month), proxy object
- UpgradeQuote: serverId uuid, from Spec, to Spec, charge string (charged now: (new periodPrice - old periodPrice) x remaining / term length), periodPrice string (each renewal after the upgrade), period MONTH|QUARTER|HALF_YEAR|YEAR, expiresAt date-time
- FirewallRule: proto tcp|udp|icmp, port string ("22", "80,443" or "8000:8100"; absent for icmp), source string (IPv4 address or CIDR (/8-/32); absent = anywhere)
- Firewall: enabled bool (off = inbound open; on = inbound dropped except the rules), rules FirewallRule[](0..20) (until first changed: the defaults applied on enable (22 or 3389, 80,443, icmp)), proxySource string|null (front-proxy address allowed on 80/443 automatically while the add-on is on)
- TermOption: period MONTH|QUARTER|HALF_YEAR|YEAR, price string (price of one term at today's rates), current bool
- HostingPlan: id uuid, code string, nameEn string, nameRu string, sites int, diskMb int, bandwidthGb int, databases int, mailboxes int, prices object
- HostingCatalogue: available bool (false = hosting sales are off (orders fail with PROVIDER_DISABLED)), plans HostingPlan[]
- Hosting: id uuid, label string, status PENDING|ACTIVE|SUSPENDED|FAILED|TERMINATED, primaryDomain string, panelUrl string, login string, ip string (for A records and FTP), nameservers string[], specs object, planId uuid (HostingPlan.id), period WEEK|MONTH|QUARTER|HALF_YEAR|YEAR, periodPrice string, autoRenew bool, expiresAt date-time, graceEndsAt date-time|null, createdAt date-time -- No secrets: the panel password is only in HostingCredentials.
- HostingCredentials: panelUrl string, login string, password string|null
- PlanQuote: hostingId uuid, planCode string, charge string (charged now, pro rata for the rest of the paid term), periodPrice string (each renewal after the change), period MONTH|QUARTER|HALF_YEAR|YEAR
