api v1

Void API — твои домены, серверы и хостинг по REST.

Регистрация и продление доменов, Cloudflare и DNS, заказ и управление VPS-серверами и веб-хостингом — из своего кода, на том же аккаунте и балансе, что и бот.

https://api.void.cd/v1 Bearer void_… JSON · UTC · USD строками

Серверы

KVM VPS: заказ, питание, оплата, файрвол

Те же серверы, что и в боте, на том же балансе. Заказ списывает первый срок и ставит создание в очередь: ответ — 201 с сервером в статусе PENDING; опрашивай GET /servers/{id}, пока он не станет ACTIVE (около минуты для Linux; Windows дольше и проходит через PROVISIONING). Секреты — пароль root / Administrator и логин предустановленного приложения — никогда не попадают в списки, детали и ответ на заказ: читай их только через GET /servers/{id}/credentials.

Питание, переустановка, апгрейд и файрвол требуют сервер в статусе ACTIVE (иначе 400 SERVER_NOT_ACTIVE) и выполняются по одной: пока другая операция в очереди или идёт, сервер отвечает 409 SERVER_BUSY.

# 1. что можно заказать прямо сейчас: образы, приложения, регионы, лимиты конфигуратора
curl -s "$API/servers/catalogue" -H "Authorization: Bearer $VOID_KEY"

# 2. заказ (списывает первый срок, возвращает 201 + сервер в PENDING)
curl -s -X POST "$API/servers" -H "Authorization: Bearer $VOID_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"vcpu":2,"ramGb":4,"diskGb":80},"os":"ubuntu-24","period":"MONTH"}'

# 3. опрашиваем до "status":"ACTIVE", затем читаем пароль root
curl -s "$API/servers/$ID" -H "Authorization: Bearer $VOID_KEY"
curl -s "$API/servers/$ID/credentials" -H "Authorization: Bearer $VOID_KEY"
// call() — как в быстром старте
const server = await call("POST", "/servers", {
  spec: { vcpu: 2, ramGb: 4, diskGb: 80 }, os: "ubuntu-24", period: "MONTH",
});

let s = server;
while (s.status === "PENDING" || s.status === "PROVISIONING") {
  await new Promise((r) => setTimeout(r, 15_000));
  s = await call("GET", `/servers/${server.id}`);
}
if (s.status !== "ACTIVE") throw new Error(`provisioning ended in ${s.status}`);

const { ip, login, password } = await call("GET", `/servers/${server.id}/credentials`);
# call() — как в быстром старте
import time

server = call("POST", "/servers", spec={"vcpu": 2, "ramGb": 4, "diskGb": 80}, os="ubuntu-24", period="MONTH")

s = server
while s["status"] in ("PENDING", "PROVISIONING"):
    time.sleep(15)
    s = call("GET", f"/servers/{server['id']}")
if s["status"] != "ACTIVE":
    raise RuntimeError(f"provisioning ended in {s['status']}")

creds = call("GET", f"/servers/{server['id']}/credentials")   # ip, login, password, app

Заказ и управление

GET/servers/catalogue

Что можно заказать прямо сейчас. os[].code и apps[].code идут в заказ; в custom — лимиты конфигуратора, причём max уже учитывает, сколько ещё может взять самый свободный узел; periods — множители цены относительно месяца (скидки за срок уже внутри); proxy — можно ли сейчас заказать фронт-прокси. plans — фиксированные тарифы бота, для справки: API заказывает по spec.

{ "ok": true, "data": {
    "plans":   [ { "code": "…", "nameEn": "…", "nameRu": "…", "vcpu": 1, "ramMb": 1024, "diskGb": 20, "trafficGb": null, "bandwidthMbps": …,
                   "prices": { "MONTH": "…", "QUARTER": "…", "HALF_YEAR": "…", "YEAR": "…" } }, … ],
    "os":      [ { "code": "ubuntu-24", "name": "Ubuntu 24.04" }, { "code": "windows-2022", "name": "Windows Server 2022" }, … ],
    "apps":    [ { "code": "3x-ui", "name": "3x-ui", "descEn": "…", "descRu": "…", "baseOs": "ubuntu-24", "minRamMb": 1024, "minutes": 2 }, … ],
    "regions": ["PL"],
    "custom":  { "cpu":    { "min": 1,  "max": 16,   "step": 1,  "monthly": 1.25 },
                 "ramGb":  { "min": 1,  "max": 64,   "step": 1,  "monthly": 1.5 },
                 "diskGb": { "min": 20, "max": 1000, "step": 20, "monthlyPer10Gb": 0.25 },
                 "base": { "monthly": 3 }, "defaults": { "vcpu": 2, "ramGb": 4, "diskGb": 80 } },
    "periods": { "MONTH": 1, "QUARTER": 2.925, "HALF_YEAR": 5.7, "YEAR": 11.1 },
    "proxy":   { "available": true, "monthly": 3, "free": 12, "total": 16 } } }

Ставки в custom и proxy — обычные числа, USD в месяц; значения выше — пример, бери их из ответа. Цена в месяц = base + vcpu × cpu.monthly + ramGb × ramGb.monthly + diskGb / 10 × monthlyPer10Gb (+ proxy.monthly с прокси); цена срока = месячная × periods[срок], с округлением до центов. Точная оплаченная сумма — periodPrice в ответе на заказ.

GET/servers?offset=0&limit=20

Твои серверы, новые первыми, без удалённых. Не больше 50 на страницу.

{ "ok": true, "data": { "items": [ Server, … ], "total": 2, "offset": 0, "limit": 20 } }
POST/servers201 · списывает баланс

Списывает первый срок, резервирует мощность и IPv4-адрес и ставит создание в очередь. Возвращает сервер в PENDING. Считается в лимит 20 / мин.

телотип
specобязательноobject{ "vcpu", "ramGb", "diskGb" }, целые числа в пределах и с шагом из custom каталога (диск — шагами по 20 ГБ)
osstringкод образа из catalogue.os; обязателен, если не указан app
appstringпредустановленное приложение из catalogue.apps; задаёт образ baseOs (os игнорируется) и требует не меньше minRamMb памяти
periodобязательноMONTH QUARTER HALF_YEAR YEARоплачиваемый срок; продления идут на тот же срок, пока ты его не сменишь
regionstringодин из catalogue.regions; не указан — любой регион, где есть место
proxybooleanфронт-прокси: веб-трафик через отдельный внешний адрес; его цена добавляется к каждому сроку. Только пока catalogue.proxy.available
sshKeyIdsuuid[] · до 10SSH-ключи, сохранённые на аккаунте (управляются в боте), ставятся при первой загрузке; чужие id игнорируются
{ "spec": { "vcpu": 2, "ramGb": 4, "diskGb": 80 }, "os": "ubuntu-24", "period": "QUARTER", "region": "PL" }
{ "spec": { "vcpu": 2, "ramGb": 2, "diskGb": 40 }, "app": "3x-ui", "period": "MONTH" }
402 INSUFFICIENT_FUNDS400 NO_CAPACITY · NO_IP_AVAILABLE · NO_TEMPLATE ничего не списано400 PROXY_NO_CAPACITY400 VALIDATION_ERROR spec вне пределов, неизвестное приложение, мало памяти для приложения

Не идемпотентно: каждый успешный вызов — ещё один оплаченный сервер. После таймаута сначала посмотри GET /servers, потом повторяй. Если создать сервер так и не удалось, он переходит в FAILED, а первый срок возвращается на баланс.

GET/servers/{id}

Один сервер — см. Server. У предустановленного приложения app.state идёт PENDING → INSTALLING → READY (или FAILED) уже после того, как сам сервер стал ACTIVE; app.url и app.note появляются, когда оно готово. power обновляется периодически и может немного отставать от действия.

GET/servers/{id}/credentials

Единственное место, где отдаются секреты. login — root, на Windows — Administrator; app добавляет адрес панели, логин и пароль предустановленного приложения, когда оно READY. Обращайся с ответом как с самим API-ключом — не пиши его в логи.

{ "ok": true, "data": { "ip": "203.0.113.25", "login": "root", "password": "…", "app": null } }

// с приложением:
{ "ok": true, "data": { "ip": "203.0.113.25", "login": "root", "password": "…",
    "app": { "code": "3x-ui", "name": "3x-ui", "state": "READY", "url": "https://…", "login": "…", "password": "…" } } }
PATCH/servers/{id}

Передавай только то, что меняешь; возвращает сервер.

телотип
autoRenewbooleanв момент истечения продлить на текущий срок с баланса; если денег не хватает, сервер приостанавливается. Только для ACTIVE / SUSPENDED
aliasstring ≤ 32 · nullтвоё имя сервера; null или "" сбрасывает
{ "autoRenew": true, "alias": "api-prod" }

Питание и переустановка

POST/servers/{id}/actions202

Ставит действие в очередь и сразу отвечает { "queued": true }; результат смотри в GET /servers/{id}.

телотип
actionобязательноstart stop reboot reinstall
osstringтолько reinstall: перейти на другой образ из каталога
appstringтолько reinstall: переустановить с предустановленным приложением (его базовый образ подразумевается; минимум памяти действует)
{ "action": "reboot" }
{ "action": "reinstall", "os": "ubuntu-24" }

reinstall стирает диск. Сервер заново клонируется из чистого образа — того же, если не передан ни os, ни app. IP-адрес, размер и пароль сохраняются (образу Windows может понадобиться пароль сложнее — перечитай /credentials), правила файрвола применяются заново. Выбрать можно только образы, доступные на узле сервера, иначе 400 NO_TEMPLATE.

400 SERVER_NOT_ACTIVE409 SERVER_BUSY другая операция в очереди или идёт

Продление, срок и апгрейд

POST/servers/{id}/renewсписывает баланс

Сразу списывает periodPrice и продлевает expiresAt на один срок — от более поздней из дат: текущего истечения или сейчас, так что раннее продление не съедает оплаченное время. Приостановленный (SUSPENDED) сервер запускается снова. Каждый успешный вызов добавляет ещё один срок. Возвращает сервер.

402 INSUFFICIENT_FUNDS400 SERVER_NOT_ACTIVE сбой создания или удалён
GET/servers/{id}/term

Все сроки продления с ценой по сегодняшним ставкам; current отмечает текущий срок сервера.

{ "ok": true, "data": [ { "period": "MONTH", "price": "13.50", "current": true }, { "period": "QUARTER", "price": "39.49", "current": false },
                        { "period": "HALF_YEAR", "price": "76.95", "current": false }, { "period": "YEAR", "price": "149.85", "current": false } ] }
PUT/servers/{id}/term

{ "period": "YEAR" } — меняет срок продления. Сейчас ничего не списывается, и текущий expiresAt не сдвигается: следующее продление, автоматическое или ручное, спишет цену нового срока и продлит на новый срок. Возвращает тот же список, что и GET. Только для ACTIVE / SUSPENDED.

POST/servers/{id}/upgrade/quote

{ "spec": { "vcpu", "ramGb", "diskGb" } } — сколько стоит апгрейд до этого размера. Апгрейд только вверх: каждое значение не меньше текущего, хотя бы одно больше, в пределах конфигуратора (уменьшение — через поддержку). charge списывается сейчас: разница цены срока пропорционально остатку оплаченного срока — (новая periodPrice − старая periodPrice) × остаток / длина срока. periodPrice — сколько будет стоить каждое продление после апгрейда.

{ "spec": { "vcpu": 4, "ramGb": 8, "diskGb": 80 } }

{ "ok": true, "data": { "serverId": "3b0c…", "from": { "vcpu": 2, "ramGb": 4, "diskGb": 80 }, "to": { "vcpu": 4, "ramGb": 8, "diskGb": 80 },
    "charge": "5.48", "periodPrice": "22.00", "period": "MONTH", "expiresAt": "2026-12-01T10:00:00.000Z" } }
POST/servers/{id}/upgrade202 · списывает баланс

Сразу списывает сумму из квоты и ставит изменение размера в очередь; возвращает применённую квоту. Новые CPU и память вступают в силу через перезапуск — работающий сервер перезагружается, будь готов к короткому простою. Виртуальный диск увеличивается на месте; если гостевая ОС не расширяет раздел при загрузке, расширь его сам.

телотип
specобязательноobjectцелевой размер, как в квоте
maxChargeобязательнострока-суммамаксимум, который ты готов заплатить сейчас, — передай charge из квоты
{ "spec": { "vcpu": 4, "ramGb": 8, "diskGb": 80 }, "maxCharge": "5.48" }

// цена стала выше maxCharge: ничего не списано, свежая квота — в details
{ "ok": false, "error": { "code": "UPGRADE_PRICE_CHANGED", "message": "…",
    "details": { "serverId": "3b0c…", "from": { … }, "to": { … }, "charge": "5.60", "periodPrice": "22.00", "period": "MONTH", "expiresAt": "…" } } }
400 UPGRADE_PRICE_CHANGED подтверди новую квоту400 NO_CAPACITY на узле нет места, ничего не списано402 INSUFFICIENT_FUNDS409 SERVER_BUSY

Файрвол

Входящий allow-list на гипервизоре, перед гостевой ОС. Выключен (по умолчанию): весь входящий трафик открыт. Включён: входящий трафик отбрасывается, кроме разрешённого твоими правилами. Уже установленные соединения переживают переключение. Изоляция от других клиентов и антиспуфинг работают в любом состоянии и в этот список не входят. При включённом прокси порты 80/443 с внешнего адреса сервера (proxySource) разрешаются автоматически, чтобы прокси продолжал работать. Изменения сразу записываются на гипервизор; если запись не удалась, остаётся прежний набор, а вызов отвечает PROVIDER_ERROR.

Синтаксис правил

правилоразрешает
22TCP-порт 22 откуда угодно
80,443несколько портов (до 10)
8000:8100/udpдиапазон портов; /udp или /tcp (по умолчанию TCP)
3306 from 1.2.3.4только с одного IPv4-адреса или из сети: from 10.0.0.0/8 (/8…/32)
ping · icmpICMP; тоже принимает from …

До 20 правил. Пока ты ничего не менял, rules показывает набор по умолчанию, который применится при включении: SSH (22, на Windows — 3389 RDP), 80,443 и ping.

GET/servers/{id}/firewall
{ "ok": true, "data": { "enabled": false,
    "rules": [ { "proto": "tcp", "port": "22" }, { "proto": "tcp", "port": "80,443" }, { "proto": "icmp" } ],
    "proxySource": null } }
// правило с источником: { "proto": "tcp", "port": "3306", "source": "1.2.3.4" }
PATCH/servers/{id}/firewall

{ "enabled": true | false } — включить или выключить allow-list; правила сохраняются в обоих случаях. Возвращает файрвол.

POST/servers/{id}/firewall/rules201

{ "rule": "3306 from 1.2.3.4" } — добавляет одно правило в синтаксисе выше и возвращает весь файрвол. Если такое правило уже есть, ничего не меняется.

400 VALIDATION_ERROR кривое правило или уже 20 правил409 SERVER_BUSY
DELETE/servers/{id}/firewall/rules/{index}

Удаляет правило на этой позиции в rules (с нуля) и возвращает файрвол. После удаления позиции сдвигаются — перечитай список, прежде чем удалять следующее. 404 NOT_FOUND, если такой позиции нет.