logo
logo

Високопродуктивні віртуальні сервери в Росії та Європі.

    ПОСЛУГИ
  • Віртуальні сервери
  • Виділені сервери
  • Домени
  • LIR-послуги
    ПІДТРИМКА
  • Написати в Telegram
  • support@vdsok.guru
  • abuse@as202831.network
  • Контакти
    ІНСТРУМЕНТИ
  • API для розробників
  • Looking Glass
    ПРАВОВА ІНФОРМАЦІЯ
  • Користувацька угода
  • Політика конфіденційності
  • Публічна оферта
  • TrustPilot
© 2026 VDSok.guru. Усі права захищено.
CryptoBot Lolz.Guru Heleket FreeKassa Lava.ru 2328.io NOWPayments
DMCA.com Protection Status
Client API v1

API для розробників

Усе, що ви робите в кабінеті, — з коду: сервери, домени, баланс, рахунки та вебхуки. JSON поверх HTTPS, ключі зі скоупами, sandbox і SDK для Node, Python і PHP.

Інтерактивний довідникЗавантажити openapi.jsonШвидкий старт
Зміст
Початок
Швидкий старт
Огляд і базовий URL
Автентифікація, скоупи та пресети
Sandbox: тестові ключі
Словник термінів
Домовленості
Помилки: конверт і коди
Ліміти запитів і заголовки
Пагінація
Ідемпотентність
Гроші та дати
Сценарії
Сервери: повний сценарій1. Каталог і котирування2. Замовлення3. Статус4. Живлення5. Перевстановлення та скидання пароля6. Додаткові IP та PTR7. Продовження та автопродовження8. Видалення з поверненням
Домени: повний сценарій1. Зони та доступність2. Реєстрація3. NS, privacy, автопродовження4. Продовження та трансфер
Білінг: баланс, поповнення, рахункиБаланс і транзакціїПоповнення: посилання на оплатуРахунки: список, PDF, оплата
ВебхукиПідпискаПеревірка підписуТест, журнал, повтор
Інструменти
SDK
Changelog
Усі ендпоінти
Специфікація OpenAPI
Підтримка та зворотний зв'язок
Зміст
Початок
Швидкий старт
Огляд і базовий URL
Автентифікація, скоупи та пресети
Sandbox: тестові ключі
Словник термінів
Домовленості
Помилки: конверт і коди
Ліміти запитів і заголовки
Пагінація
Ідемпотентність
Гроші та дати
Сценарії
Сервери: повний сценарій1. Каталог і котирування2. Замовлення3. Статус4. Живлення5. Перевстановлення та скидання пароля6. Додаткові IP та PTR7. Продовження та автопродовження8. Видалення з поверненням
Домени: повний сценарій1. Зони та доступність2. Реєстрація3. NS, privacy, автопродовження4. Продовження та трансфер
Білінг: баланс, поповнення, рахункиБаланс і транзакціїПоповнення: посилання на оплатуРахунки: список, PDF, оплата
ВебхукиПідпискаПеревірка підписуТест, журнал, повтор
Інструменти
SDK
Changelog
Усі ендпоінти
Специфікація OpenAPI
Підтримка та зворотний зв'язок

Швидкий старт

П'ять хвилин від нуля до першої відповіді API. Потрібні лише акаунт VDSok і термінал.

1. Отримайте ключ

Ключі випускає власник акаунта в кабінеті: Налаштування → API (/my/api). При створенні обираються режим (live або test), набір скоупів і, якщо треба, список дозволених IP. Секрет показується один раз — удруге його не покаже ніхто, ключ доведеться випустити заново.

Почніть із тестового ключа (vk_test_…): він ходить на ту саму адресу й читає ваші справжні дані, але жодного запису не виконує по-справжньому — подробиці в розділі Sandbox.

2. Перший запит

GET /me — найдешевший спосіб переконатися, що ключ живий. Приклади чотирма мовами — просто під цим розділом.

У відповіді дивіться на три речі: key.mode (live або test), scopes — що саме дозволено цьому ключу, і заголовок X-Request-ID, який варто називати в будь-якому зверненні до підтримки. Залишок ліміту приходить у X-RateLimit-Remaining.

3. Перший запис — безкоштовно

Тестовим ключем можна пройти весь шлях замовлення, нічого не витративши: баланс не рухається, VM не створюється, платіжні шлюзи не викликаються. Кожна така відповідь несе заголовок X-Sandbox: true, а створений сервер отримує фейковий id від 9000000000 і живе 24 години.

BASE=https://vdsok.guru/api/v1
KEY=vk_test_XXXXXXXXXXXXXXXXXXXXXXXX

# замовлення: поверне invoice_id замовлення і server_id майбутньої машини
curl -sS -X POST $BASE/servers \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"tariff_id": 12, "os": "ubuntu-22.04", "months": 1}'

# як іде замовлення — за invoice_id з відповіді вище, не за server_id
curl -sS $BASE/orders/4242 -H "Authorization: Bearer $KEY"

# прибирання: видалення теж імітується
curl -sS -X DELETE $BASE/servers/9000000001 \
  -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)"

Коли те саме запрацює бойовим ключем, єдина різниця — префікс ключа й відсутність заголовка X-Sandbox.

Куди далі

  • Автентифікація, скоупи та пресети — що саме дозволяти ключу.
  • Помилки: конверт і коди — з їх розбору починається будь-який робочий клієнт.
  • Сервери: повний сценарій — замовлення, статус, живлення, продовження, видалення з поверненням.
  • SDK — якщо не хочеться писати HTTP руками.
  • Словник термінів — якщо незнайоме слово «котирування» або неясно, чим замовлення відрізняється від рахунку.
curl -sS https://vdsok.guru/api/v1/me -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX"

Огляд і базовий URL

VDSok Client API — REST-інтерфейс поверх HTTPS із JSON-тілами. Через нього доступне все, що клієнт робить у кабінеті, крім тікетів, виділених серверів, суб-акаунтів і редагування профілю: баланс і рахунки, каталог VDS, сервери (замовлення, продовження, живлення, перевстановлення, IP, PTR, SSH-ключі, видалення з поверненням), домени, API-ключі та вебхуки.

Базовий URL

Усі шляхи в цьому посібнику відносні:

https://vdsok.guru/api/v1

Версія живе в шляху. Ламкі зміни підуть у /v2; v1 лише зростає: нові поля й ендпоінти можуть з'явитися будь-коли, тому невідомі поля у відповідях ігноруйте.

Домовленості

  • Лише JSON. Тіло POST/PUT/PATCH не в application/json відхиляється з 415, тіло понад 64 КБ — з 413.
  • Час — RFC 3339 в UTC із суфіксом Z: 2026-09-15T10:00:00Z.
  • Гроші — десятковий рядок із 2–4 знаками після крапки, ніколи не float, завжди поруч із полем currency (ISO 4217).
  • server_id — id віртуальної машини в панелі, те саме число, що кабінет показує в URL.
  • Кожна відповідь містить X-Request-ID; вказуйте його у зверненнях до підтримки.

Якщо ви тут уперше — почніть зі швидкого старту: ключ, перший запит і безкоштовне коло замовлення в пісочниці.

Автентифікація, скоупи та пресети

Кожен запит містить заголовок Authorization: Bearer <ключ>. Ключ починається з vk_live_ (бойовий) або vk_test_ (тестовий, див. Sandbox).

Ключі створює власник акаунта в кабінеті (/my/api): ім'я, режим live/test, набір скоупів, необов'язковий список дозволених IP/CIDR і вікно дії (not_before/expires_at). Секрет показується один раз. Ключ не залежить від сесій кабінету й діє, поки не спливе або не буде відкликаний. Через API ключі не створюються — лише переглядаються (GET /keys) і відкликається свій ключ.

Скоупи

Кожна операція перелічує потрібні скоупи в x-scopes специфікації; порожній список означає «будь-який валідний ключ» (каталог, /me). Зовсім без ключа доступні лише GET /health (завжди 200 зі status: ok | disabled — це проба доступності, а не захищена ручка) і GET /openapi.json. Брак скоупа → 403 insufficient_scope, у details.required — чого не вистачило.

СкоупЩо відкриває
account:readGET /account — профіль, група, знижка
balance:readбаланс, транзакції, ліміти поповнення
balance:topupстворення рахунку на поповнення та посилання на оплату
invoices:readсписок рахунків, рахунок, PDF
invoices:payоплата рахунку з балансу, платіжне посилання
servers:readсервери, живий статус, IP, SSH-ключі, замовлення, котирування повернення
servers:manageживлення, перевстановлення, скидання пароля, PTR, PATCH, додавання та видалення SSH-ключів
servers:orderзамовлення, продовження, купівля та звільнення IP
servers:deleteвидалення сервера з поверненням
domains:readдомени, перевірка доступності
domains:manageавтопродовження, WHOIS-privacy, NS
domains:orderреєстрація, продовження, трансфер
keys:readсписок ключів акаунта
webhooks:manageпідписки, доставки, тест, повтор

Пресети

Пресет — готовий набір скоупів; після вибору склад можна змінити.

ПресетСклад
read_onlyусі *:read
operateread_only + servers:manage, domains:manage, invoices:pay
fullусі скоупи, включно з balance:topup, servers:order, servers:delete, domains:order, webhooks:manage

Kill switch

DELETE /keys/{key_id} відкликає лише той ключ, яким зроблено запит, незалежно від його скоупів. Витеклий ключ можна погасити звідусіль — із CI, із сервера. Інші ключі відкликаються в кабінеті. Працює і для тестових ключів.

Зберігайте ключі в секретах CI або менеджері секретів, не в репозиторії. Для кожної інтеграції заводьте окремий ключ із мінімальними скоупами та IP-списком — так витік видно за last_used_ip і його простіше локалізувати.
# A key without servers:delete gets 403 with the missing scope in details:
curl -i -X DELETE https://vdsok.guru/api/v1/servers/2001 \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Idempotency-Key: $(uuidgen)"
# HTTP/1.1 403 Forbidden
# {"error":{"code":"insufficient_scope","message":"This key lacks servers:delete",
#           "request_id":"9f1c2a9d-4b7e-4a21-8d3f-0c6e5b2a1d44","details":{"required":["servers:delete"]}}}

# Kill switch: revoke the calling key itself
KEY_ID=$(curl -s https://vdsok.guru/api/v1/me -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq .key.id)
curl -X DELETE https://vdsok.guru/api/v1/keys/$KEY_ID -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX"

Sandbox: тестові ключі

Тестовий ключ (vk_test_…) створюється в тому самому кабінеті, що й бойовий, і ходить на той самий базовий URL — окремого стенда немає.

Тестові ключі читають реальні дані вашого акаунта. GET /servers, GET /balance, GET /invoices тестовим ключем повертають справжні сервери, справжній баланс і справжні рахунки. Відмінність лише в записі: жодна мутація не виконується по-справжньому.

Що робить тестовий ключ:

  • Не рухає гроші: замовлення, продовження, поповнення, оплата рахунку та видалення повертають правдоподібну відповідь, але баланс не змінюється, VM і домени не створюються, платіжні шлюзи не викликаються.
  • Замовлення сервера повертає фейковий сервер з id >= 9000000000. Він живе 24 години й видимий у GET /servers лише цьому ключу; над ним можна викликати статус, живлення, перевстановлення, IP і видалення — усе теж імітується.
  • Кожна відповідь тестовому ключу містить заголовок X-Sandbox: true. Перевіряйте його в тестах, щоб не сплутати режими.
  • Ідемпотентність, ліміти та помилки валідації працюють як у live: 402, 409, 429 можна отримати й у пісочниці.

Поведінку кожної операції під тестовим ключем описано в x-sandbox специфікації:

x-sandboxЗначення
realте саме, що live — усі читання, DELETE /keys/{key_id}
fakeімітація запису: замовлення, продовження, живлення, домени, поповнення
forbidden403 sandbox_not_supported — усі ручки вебхуків; підписки лише для live
# Same URL, test key: the order is simulated, the header says so
curl -i -X POST https://vdsok.guru/api/v1/servers \
  -H "Authorization: Bearer vk_test_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"tariff_id": 12, "os": "ubuntu-24.04", "name": "sandbox-01", "months": 1}'
# HTTP/1.1 201 Created
# X-Sandbox: true
# {"server":{"id":9000000042,"name":"sandbox-01",...},"root_password":"...","charged":"5.90",...}

Словник термінів

Слова, які трапляються у відповідях API і далі в цьому посібнику.

ТермінЩо означає
tariffКонфігурація з ціною: ядра, пам'ять, диск, трафік, локація. Лежить у каталозі, адресується tariff_id. Купити можна лише те, що є в GET /catalog/tariffs.
serverОплачена віртуальна машина — екземпляр тарифу. Адресується server_id, тим самим числом, що кабінет показує в адресі сторінки.
orderПроцес покупки: рахунок виставлено, оплачено, машина створюється. Адресується invoice_id, а не server_id — GET /orders/{invoice_id}. Це перше, об що спотикаються.
invoiceРахунок: на замовлення, на продовження або на поповнення балансу. Має статус, PDF і можливість оплати з балансу.
quoteПопередній розрахунок без списання: GET /catalog/quote — скільки коштуватиме замовлення, GET /servers/{server_id}/refund-quote — скільки повернеться при видаленні.
panelПанель віртуалізації, де насправді живе машина. Звідти приходить живий статус і туди йдуть живлення та перевстановлення.
scopeПраво, видане ключу: servers:read, balance:topup тощо. Брак — 403 insufficient_scope.
presetГотовий набір скоупів у кабінеті: read_only, operate, full. Ярлик, а не окрема сутність.
live key / test keyКлюч із префіксом vk_live_ працює по-справжньому; vk_test_ читає справжні дані, але записи імітує.
sandboxРежим, у якому працює тестовий ключ. Відповідь позначена заголовком X-Sandbox: true.
cursorНепрозорий рядок-закладка для наступної сторінки списку. Не розбирайте його — передавайте як є.
Idempotency-KeyВаш ідентифікатор спроби для грошових операцій. Повтор із тим самим ключем поверне попередній результат замість другого списання.
deliveryОдна спроба доставки вебхука на вашу адресу. Повторна відправка тієї самої спроби — redeliver.
PTRЗворотний DNS-запис для IP-адреси: за адресою повертається ім'я. Потрібен поштовим серверам.
punycodeASCII-запис домену з нелатинськими літерами (пример.рф → xn--e1afmkfd.xn--p1ai). API приймає обидві форми, повертає punycode.
expensiveПозначка операції, яка смикає зовнішню систему. Для таких — окремий, вужчий кошик ліміту.
↑ Нагору

Помилки: конверт і коди

У API одна форма помилки — на будь-який статус, включно з 404/405/413/500 рівня застосунку:

{
  "error": {
    "code": "insufficient_funds",
    "message": "Balance 4.10 USD is below the required 5.90 USD",
    "request_id": "9f1c2a9d-4b7e-4a21-8d3f-0c6e5b2a1d44",
    "details": {"required": "5.90", "balance": "4.10", "shortfall": "1.80", "currency": "USD"}
  }
}
  • code — машинний код із закритого списку нижче. Розгалужуйтесь за ним, а не за message.
  • message — людський англійський текст, не для парсингу; формулювання можуть змінюватись.
  • request_id — той самий, що в заголовку X-Request-ID; вказуйте його підтримці.
  • details — доповнення, залежне від коду: required (скоупи), fields (помилки валідації за полями), грошові цифри для insufficient_funds, status для service_state.

Список кодів закритий за кожним статусом, але клієнт має терпіти невідомі коди й обробляти їх за HTTP-статусом — так додавання коду не ламає інтеграції.

СтатусКоди
400invalid_request, validation_error, invalid_cursor, idempotency_key_required, os_not_allowed, invalid_period, invalid_action, upstream_rejected
401invalid_token, key_expired, key_not_yet_valid
402insufficient_funds — нічого не списано й не створено
403insufficient_scope, ip_not_allowed, account_suspended, api_disabled_for_account, sandbox_not_supported, sandbox_disabled, server_blocked, domain_blocked
404not_found — об'єкта немає на цьому акаунті (чужі об'єкти теж 404, не 403)
409conflict, idempotency_conflict, idempotency_in_progress, operation_in_progress, service_state, no_capacity, tariff_unavailable, ip_limit_reached, domain_taken, domain_exists, cancel_pending
405 / 413 / 415method_not_allowed, payload_too_large, unsupported_media_type
429rate_limited
500server_error — збій на нашому боці; у зверненні назвіть request_id
502upstream_error — панель, реєстратор або шлюз відповіли помилкою; для грошових операцій нічого не списано, якщо відповідь не каже іншого
503api_disabled, upstream_unavailable, temporarily_unavailable — повторіть після Retry-After
504upstream_timeout — панель, реєстратор або шлюз не відповіли вчасно; повторюйте з тим самим Idempotency-Key

Що повторювати: 429, 502, 503, 504 — безпечно для GET і для мутацій з Idempotency-Key. Інші мутації без ключа ідемпотентності не повторюйте наосліп.

# -f makes curl exit non-zero on 4xx/5xx; -s hides progress; the body still has the envelope
curl -s -w "\n%{http_code}\n" https://vdsok.guru/api/v1/servers/2001 \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
# {"error":{"code":"not_found","message":"Server 2001 not found","request_id":"9f1c2a9d-4b7e-4a21-8d3f-0c6e5b2a1d44"}}
# 404

# Extract the code with jq
curl -s https://vdsok.guru/api/v1/servers/2001 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq -r '.error.code // "ok"'

Ліміти запитів і заголовки

За замовчуванням — 120 запитів на хвилину на ключ плюс окремий кошик 20 на хвилину для дорогих викликів. Ліміти рахуються на ключ (не на акаунт і не на IP), вікно фіксоване — 60 секунд. Стелю й залишок повертають заголовки X-RateLimit-Limit і X-RateLimit-Remaining кожної відповіді, ліміти конкретного ключа — у GET /keys/{key_id}. Якщо штатної стелі не вистачає, напишіть у підтримку.

Дорогі виклики позначено x-expensive: true у специфікації:

  • GET /servers/{server_id}/status і GET /servers/{server_id}?include=live — живе опитування панелі;
  • GET /domains/availability — запит до реєстратора;
  • GET /catalog/quote;
  • GET /servers/{server_id}/ips, GET /servers/{server_id}/ips/quote;
  • POST /servers, POST /servers/{server_id}/renew, POST /servers/{server_id}/ips, POST /balance/topup;
  • GET /invoices/{invoice_id}/pdf;
  • POST /webhooks/{webhook_id}/test, POST /webhooks/deliveries/{delivery_id}/redeliver.

Окремо: POST /servers/{server_id}/actions/reset-password — не більше 5 разів за 5 хвилин на сервер. Запити з невалідним ключем обмежені 30 на хвилину на IP — проти перебору.

Заголовки

ЗаголовокСенс
X-RateLimit-Limitскільки запитів на хвилину дозволено в кошику, у який потрапив виклик (звичайний чи дорогий)
X-RateLimit-Remainingскільки лишилося в поточному 60-секундному вікні
X-RateLimit-Resetunix-час (секунди) скидання вікна
Retry-Afterсекунди до повтору; є у 429 і 503, а також у 409 idempotency_in_progress

При 429 rate_limited у details вказано bucket (normal/expensive) і limit. Правильна реакція — почекати Retry-After і повторити, а не довбати в циклі: вікно фіксоване, раніше воно не відкриється.

Не опитуйте GET /servers/{id}/status частіше, ніж потрібно: 20 дорогих викликів на хвилину ділять між собою всі живі статуси, перевірки доменів і замовлення цього ключа. Для моніторингу десятків серверів використовуйте GET /servers (не дорогий) і вебхуки server.suspended/server.terminated.
curl -sD - -o /dev/null https://vdsok.guru/api/v1/servers/2001/status \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | grep -i -E "x-ratelimit|retry-after"
# X-RateLimit-Limit: 20          <- expensive bucket
# X-RateLimit-Remaining: 19
# X-RateLimit-Reset: 1789466460

Пагінація

Списки з потенційно великою кількістю елементів (/servers, /invoices, /transactions, /orders, /domains, /webhooks/{id}/deliveries) віддають сторінку й курсор:

{"data": [ … ], "next_cursor": "eyJpZCI6MjAwMSwic2lnIjoi…"}
  • limit — від 1 до 100, за замовчуванням 50.
  • next_cursor — передайте назад як ?cursor=, щоб отримати наступну сторінку; null означає, що це остання.
  • Курсори непрозорі й підписані: не збирайте їх руками й не парсіть — зіпсований курсор дає 400 invalid_cursor. Курсор прив'язаний до набору фільтрів: змінюєте status — починайте з першої сторінки.
  • Порядок фіксований: нові спочатку. Додані під час обходу елементи можуть потрапити на початок, але дублікатів і пропусків усередині обходу немає.

Короткі довідники (/catalog/*, /servers/{id}/ips, /ssh-keys, /keys, /webhooks) повертають {"data": [...]} цілком, без курсора.

# First page, 20 active servers
curl -s "https://vdsok.guru/api/v1/servers?status=active&limit=20" \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '{n: (.data|length), next: .next_cursor}'

# Next page: pass next_cursor back verbatim (URL-encode it)
CURSOR=$(curl -s "https://vdsok.guru/api/v1/servers?status=active&limit=20" \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq -r '.next_cursor')
curl -s "https://vdsok.guru/api/v1/servers?status=active&limit=20&cursor=$CURSOR" \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX"

Ідемпотентність

Операції, що рухають гроші або чіпають зовнішні системи, позначено x-idempotent: true і вони вимагають заголовок Idempotency-Key: POST /balance/topup, POST /invoices/{id}/pay, POST /servers, DELETE /servers/{id}, POST /servers/{id}/renew, POST /servers/{id}/ips, POST /domains, POST /domains/{id}/renew, POST /domains/transfers.

Ключ — будь-який рядок 16..128 символів, унікальний для логічної операції; UUID v4 підходить. Генеруйте його до першої спроби й перевикористовуйте при повторах — у цьому весь сенс. Ключ запам'ятовується на 7 днів разом із дайджестом ендпоінта й тіла:

СитуаціяВідповідь
той самий ключ, той самий запитзбережена відповідь відтворюється; секрети на кшталт root_password у повторі затерті
той самий ключ, інше тіло або інший ендпоінт409 idempotency_conflict
той самий ключ, поки перший запит ще виконується409 idempotency_in_progress з Retry-After
ключа немає400 idempotency_key_required

Підсумок операції зберігається завжди, включно з відповідями 5xx, тому повтор ніколи не спише двічі. Замовлення сервера з таймаутом панелі (202 provisioning) при повторі з тим самим ключем поверне той самий 202 з тим самим invoice_id — далі опитуйте GET /orders/{invoice_id}.

Пароль від нового сервера приходить лише в першому 201. Якщо ви отримали мережеву помилку після успішного створення й повторили запит, у відповіді буде порожній root_password — використовуйте POST /servers/{id}/actions/reset-password.
# Generate the key once, reuse it for every retry of this renewal
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST https://vdsok.guru/api/v1/servers/2001/renew \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{"months": 3}'

# Sending it again replays the stored answer, nothing is charged twice
curl -X POST https://vdsok.guru/api/v1/servers/2001/renew \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{"months": 3}'

Гроші та дати

Гроші

Усі суми — рядки на кшталт "5.90", "0.0083", "-12.00" (шаблон ^-?[0-9]+\.[0-9]{2,4}$), поруч завжди є currency — ISO 4217 код валюти акаунта (наприклад, USD). Float у JSON не використовується ніколи.

  • Розбирайте суми в десятковий тип: Decimal у Python, BigInt у мінімальних одиницях або decimal.js у Node, bcmath/BigDecimal у PHP.
  • У тілі запитів (amount при поповненні) надсилайте рядок із двома знаками: "25.00". Число 25 теж приймається, але 25.005 — ні.
  • Погодинні ціни (price_hourly) мають до 4 знаків: "0.0083".
  • Транзакції містять amount за модулем і окреме поле direction (credit/debit); Money зі знаком трапляється лише в коригуваннях.

Дати

Усі моменти часу — RFC 3339 в UTC із суфіксом Z: 2026-09-15T10:00:00Z. Поля, які можуть бути порожніми (next_due_at, expires_at, paid_at), приходять як null, а не як порожній рядок чи 0000-00-00.

  • У запитах (since/until у /transactions) теж надсилайте RFC 3339 із Z або зміщенням.
  • X-RateLimit-Reset і X-Webhook-Timestamp — unix-секунди, не мілісекунди.
  • Повернення при видаленні рахуються за цілими днями невикористаного періоду (days_left у RefundQuote), порівнюйте свої розрахунки в днях, а не в секундах.
# Sum debits of the last 30 days with jq: strings -> numbers only at the very end
SINCE=$(date -u -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ)
curl -s "https://vdsok.guru/api/v1/transactions?direction=debit&since=$SINCE&limit=100" \
  -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '[.data[].amount | tonumber] | add'
↑ Нагору

Сервери: повний сценарій

Нижче — життєвий цикл одного VDS від каталогу до видалення. Усе те саме, що робить кабінет, з тими самими перевірками й цінами. server_id скрізь — id віртуальної машини в панелі (id у відповідях /servers).

Статуси сервера (status): active — послуга активна; stopped — вимкнений клієнтом; suspended — послугу призупинено, зазвичай за несплату; pending_cancel — схвалено заявку на скасування наприкінці періоду; cancelled — видалений клієнтом або за заявкою; terminated — видалений після тривалої несплати. Прапорці flags.blocked (на сервері стоїть блокування, будь-яка мутація → 403 server_blocked), flags.expired, flags.is_test уточнюють, що можна зробити.

Поки над сервером триває замовлення, видалення або зміна IP, паралельна мутація того самого об'єкта відповідає 409 operation_in_progress — дочекайтесь і повторіть.

1. Каталог і котирування

GET /catalog/tariffs віддає тарифи категорії VDS, доступні до замовлення, уже з урахуванням знижки вашої групи (price_monthly; list_price_monthly — публічна ціна). in_stock: false означає, що в локації немає місць — замовлення відповість 409 no_capacity. GET /catalog/os?tariff_id=12 виключає образи, заборонені тарифом (excluded_os); slug образу — це значення os при замовленні.

GET /catalog/quote рахує ціну тими самими функціями, що й замовлення: базова сума, знижки групи/лояльності/обсягу/періоду, промокод — і каже, чи вистачає балансу (balance_sufficient, shortfall). Промокод перевіряється, але не витрачається. Вкажіть або months (1, 3, 6, 12), або hours (1..2160, якщо в тарифу hourly_available). Один тариф цілком віддає GET /catalog/tariffs/{tariff_id}, а список локацій із кодами та країнами — GET /catalog/locations.

curl -s https://vdsok.guru/api/v1/catalog/tariffs -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '.data[] | {id, name, loc: .location.code, price_monthly, in_stock}'
curl -s "https://vdsok.guru/api/v1/catalog/os?tariff_id=12" -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '.data[].slug'
curl -s "https://vdsok.guru/api/v1/catalog/quote?tariff_id=12&months=3" -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '{total, currency, balance_sufficient, shortfall, discounts}'

2. Замовлення

POST /servers працює синхронно, як кабінет: перевіряє тариф, ОС, ім'я та наявність місць, рахує ціну, до будь-якого запису перевіряє баланс (402 insufficient_funds не має побічних ефектів), потім списує і створює VM. Обов'язковий Idempotency-Key.

  • 201 — сервер створено й запущено; у відповіді server, одноразовий root_password, invoice_id, charged, balance_after. Збережіть пароль одразу: у повторі за тим самим ключем ідемпотентності він порожній.
  • 202 — панель не відповіла вчасно (і лише в цьому разі: таймаут панелі не перетворюється на 504): гроші списано, замовлення триває у фоні. У відповіді Order з order_url. Опитуйте GET /orders/{invoice_id} раз на кілька секунд, поки status лишається provisioning. Успіх — status: active із server_id; будь-який інший термінальний стан означає, що сервер не створено, а списання вже повернено на баланс.

SSH-ключі: покладіть публічні ключі в POST /ssh-keys один раз і передавайте їхні id у ssh_key_ids при замовленні та перевстановленні. Якщо password не вказано, він буде згенерований і повернутий один раз. Промокод передається в promo_code; поля форми замовлення, налаштовані VDSok, — у custom_fields. Список ключів акаунта — GET /ssh-keys, видалення — DELETE /ssh-keys/{key_id}: сервери, де ключ уже встановлено, воно не чіпає.

# Store an SSH key once
curl -s -X POST https://vdsok.guru/api/v1/ssh-keys -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" -H "Content-Type: application/json" \
  -d '{"name": "laptop", "public_key": "'"$(cat ~/.ssh/id_ed25519.pub)"'"}' | jq .id

# Order: months=1, key id 3 injected into the image
curl -s -X POST https://vdsok.guru/api/v1/servers -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"tariff_id": 12, "os": "ubuntu-24.04", "name": "web-01", "months": 1, "ssh_key_ids": [3]}' \
  | jq '{status: (.server.status // .status), id: (.server.id // .server_id), ip: .server.ip, root_password, invoice_id}'

# On 202 poll the order while it is still provisioning
curl -s https://vdsok.guru/api/v1/orders/10231 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '{status, server_id, invoice_id}'

3. Статус

GET /servers/{server_id} — картка сервера: статус, тариф, IP, ресурси, billing (цикл, next_due_at, recurring_amount, auto_renew) і refund_quote — скільки повернулося б при видаленні просто зараз. Це дешево й підходить для частого опитування.

GET /servers/{server_id}/status (або ?include=live у картки) іде в панель за живими даними: power (running/stopped/unknown), CPU, пам'ять, диск, аптайм. Це дорогий виклик із кошика 20/хв. У частини серверів живих даних немає — такий виклик відповідає 409 service_state.

curl -s https://vdsok.guru/api/v1/servers/2001 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '{status, ip, next_due: .billing.next_due_at, auto_renew: .billing.auto_renew, refund: .refund_quote.amount}'
curl -s https://vdsok.guru/api/v1/servers/2001/status -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '{power, cpu_percent, mem: .memory, uptime_seconds}'

4. Живлення

POST /servers/{server_id}/actions/power з тілом {"action": "start" | "stop" | "restart"}. Відповідь 202 означає, що панель прийняла команду; фактичний стан перевіряйте через живий статус за кілька секунд. Невідома дія → 400 invalid_action; сервер у suspended або blocked → 409 service_state / 403 server_blocked. Скоуп servers:manage.

curl -s -X POST https://vdsok.guru/api/v1/servers/2001/actions/power -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -d '{"action": "restart"}'
# {"server_id":2001,"action":"restart","status":"accepted","message":null}

5. Перевстановлення та скидання пароля

POST /servers/{server_id}/actions/reinstall з {"os": "<slug>", "password"?: "...", "ssh_key_ids"?: [...]} знищує всі дані на диску. Образ має бути дозволений тарифу, інакше 400 os_not_allowed. Якщо password не передано, новий генерується й повертається один раз у root_password; 202 зі status: reinstalling. Коли встановлення закінчиться, прийде вебхук server.reinstalled.

POST /servers/{server_id}/actions/reset-password генерує новий root-пароль і показує його один раз. Обмеження — 5 викликів за 5 хвилин на сервер.

curl -s -X POST https://vdsok.guru/api/v1/servers/2001/actions/reinstall -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -d '{"os": "debian-12", "ssh_key_ids": [3]}'
# {"server_id":2001,"status":"reinstalling","os":"debian-12","root_password":"..."}

curl -s -X POST https://vdsok.guru/api/v1/servers/2001/actions/reset-password -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq -r .password

6. Додаткові IP та PTR

GET /servers/{server_id}/ips — усі адреси сервера (v4 і v6) з primary, ptr, price_monthly. GET /servers/{server_id}/ips/quote заздалегідь каже, скільки спишеться просто зараз (prorated_now — за залишок поточного періоду) і скільки адрес ще можна додати (extra_ips / max_extra_ips).

POST /servers/{server_id}/ips (з Idempotency-Key, скоуп servers:order) купує один IPv4: списує prorated_now і підвищує billing.recurring_amount. Відповідь — {"success", "server_id", "charged", "currency", "days"}: самої адреси в ній немає, панель видає її асинхронно — прочитайте через GET /servers/{server_id}/ips. Ліміт на сервер → 409 ip_limit_reached. DELETE /servers/{server_id}/ips/{ip_id} звільняє додаткову адресу без повернення; recurring_amount знижується з наступного періоду. Основну адресу звільнити не можна (409 conflict).

PUT /servers/{server_id}/ips/{ip_id}/ptr з {"domain": "mail.example.com"} ставить зворотний запис і відповідає {"id", "ptr"}; ім'я — щонайменше дві мітки з літер, цифр, крапок і дефісів. Поле зветься domain (не ptr), а "" чи null знімають запис.

curl -s https://vdsok.guru/api/v1/servers/2001/ips/quote -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '{prorated_now, price_monthly, extra_ips, max_extra_ips}'
curl -s -X POST https://vdsok.guru/api/v1/servers/2001/ips -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" -H "Idempotency-Key: $(uuidgen)" \
  | jq '{success, charged, currency, days}'   # the address arrives asynchronously
curl -s https://vdsok.guru/api/v1/servers/2001/ips -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '.data[] | select(.primary | not) | {id, ip}'
curl -s -X PUT https://vdsok.guru/api/v1/servers/2001/ips/501/ptr -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -d '{"domain": "mail.example.com"}' | jq '{id, ptr}'
curl -s -X DELETE https://vdsok.guru/api/v1/servers/2001/ips/501 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX"

7. Продовження та автопродовження

POST /servers/{server_id}/renew списує з балансу й продовжує billing.next_due_at; дострокове продовження додається до поточного періоду. Місячні сервери приймають {"months": 1|3|6|12}, погодинні — {"hours": 1..2160}. Обов'язковий Idempotency-Key; за браку коштів — 402 без побічних ефектів.

PATCH /servers/{server_id} змінює auto_renew, name і notes. За ввімкненого автопродовження рахунок виставляється й оплачується з балансу автоматично перед next_due_at; сума — billing.recurring_amount. Стежте за GET /balance → upcoming_7d/low_balance або підпишіться на вебхук balance.low.

curl -s -X POST https://vdsok.guru/api/v1/servers/2001/renew -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" -d '{"months": 3}' \
  | jq '{charged, balance_after, next_due_at}'

curl -s -X PATCH https://vdsok.guru/api/v1/servers/2001 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -d '{"auto_renew": true, "notes": "prod, do not stop"}' \
  | jq '.billing.auto_renew'

8. Видалення з поверненням

Спочатку GET /servers/{server_id}/refund-quote: amount — скільки повернеться на баланс, refundable і excluded_reason — чому може бути нуль (promo_tariff — тариф позначено як неповоротний, blocked — на сервері стоїть блокування, unpaid — послуга прострочена, no_payment_record — оплачених рахунків немає; так само виходить із тестовими серверами). Повернення рахується пропорційно цілим дням невикористаного оплаченого періоду за реально оплаченими рахунками (breakdown), а не за ціною тарифу; виплачені реферальні бонуси за цими рахунками віднімаються (referral_adjustment).

DELETE /servers/{server_id} (скоуп servers:delete, Idempotency-Key) видаляє VM із панелі й зараховує повернення; у відповіді DeleteResult з refund, currency, balance_after і refund_quote — тим самим розрахунком, що й котирування. Заявка на скасування, яка очікує, відкликається мовчки, у відповіді цього немає. Уже видалений сервер (cancelled/terminated) відповідає 409 service_state, заблокований — 403 server_blocked. Якщо панель відповіла помилкою, відмінною від «уже видалено», повернеться 502, і нічого не зміниться. Прийде вебхук server.terminated.

Видалення незворотне, а котирування змінюється щодня. Показуйте користувачу refund_quote.amount і вимагайте підтвердження перед DELETE.
curl -s https://vdsok.guru/api/v1/servers/2001/refund-quote -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '{amount, currency, refundable, excluded_reason, days_left: .breakdown[0].days_left}'

curl -s -X DELETE https://vdsok.guru/api/v1/servers/2001 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" -H "Idempotency-Key: $(uuidgen)" \
  | jq '{status, refunded: .refund, balance_after}'

Домени: повний сценарій

Домени реєструються на контактні дані з профілю акаунта — ті самі, що використовує кабінет; через API їх не змінити. Статуси (status): pending — реєстратор прийняв заявку, домен активується на наступній синхронізації; active; expired; transfer_pending; cancelled. blocked: true — на домені стоїть блокування, мутації відповідають 403 domain_blocked. locked — трансфер-лок у реєстратора.

Усі грошові операції з доменами (POST /domains, /domains/{id}/renew, /domains/transfers) вимагають Idempotency-Key і скоуп domains:order; читання — domains:read; NS, privacy та автопродовження — domains:manage.

1. Зони та доступність

GET /catalog/zones — список TLD з цінами реєстрації, продовження та трансферу, допустимими термінами (min_years/max_years) і прапорцями privacy_supported, transfer_supported.

GET /domains/availability?name=example.com запитує реєстратора (дорогий виклик): available, reason (taken, premium, reserved, invalid, unsupported_tld), price_register, price_renew. IDN можна передавати і в Unicode, і в punycode.

curl -s https://vdsok.guru/api/v1/catalog/zones -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '.data[] | select(.tld == ".com")'
curl -s "https://vdsok.guru/api/v1/domains/availability?name=example.com" -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '{available, reason, price_register, currency}'

2. Реєстрація

POST /domains з {"name", "years"?, "nameservers"?, "privacy"?, "auto_renew"?, "promo_code"?} списує з балансу й реєструє домен. За замовчуванням years: 1, privacy: true, auto_renew: false, NS — сервери VDSok.

  • 201 зі status: registered — реєстратор підтвердив одразу.
  • 202 зі status: pending — заявку прийнято, домен стане active на наступній синхронізації; прийде вебхук domain.registered.
  • 409 domain_taken — ім'я зайняте; 409 domain_exists — уже є на акаунті; 402 — не вистачає коштів, нічого не списано.
curl -s -X POST https://vdsok.guru/api/v1/domains -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name": "example.com", "years": 1, "privacy": true, "auto_renew": true}' \
  | jq '{status, id: .domain.id, expires_at: .domain.expires_at, charged, balance_after}'

3. NS, privacy, автопродовження

PUT /domains/{domain_id}/nameservers з {"nameservers": ["ns1.example.net", "ns2.example.net"]} замінює весь набір — рівно два імені. PATCH /domains/{domain_id} перемикає auto_renew і privacy; privacy застосовується в реєстратора синхронно — якщо він відмовив, відповідь 400 upstream_rejected і нічого не зберігається.

GET /domains — посторінковий список із фільтром status; GET /domains/{domain_id} — картка з expires_at, price_renew, nameservers, locked. За 30, 7 і 1 день до закінчення приходить вебхук domain.expiring.

curl -s -X PUT https://vdsok.guru/api/v1/domains/77/nameservers -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{"nameservers": ["ns1.example.net", "ns2.example.net"]}' | jq .nameservers

curl -s -X PATCH https://vdsok.guru/api/v1/domains/77 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -d '{"privacy": false, "auto_renew": true}' | jq '{privacy, auto_renew}'

4. Продовження та трансфер

POST /domains/{domain_id}/renew з {"years": 1..10} списує price_renew × years і продовжує expires_at. Відповідь status: renewed, або pending_sync — реєстратор прийняв продовження, але нова дата ще не видна; її підбере щоденна синхронізація, і прийде вебхук domain.renewed.

POST /domains/transfers з {"name", "auth_code", "privacy"?, "auto_renew"?} списує ціну трансферу (зазвичай включає рік продовження) і запускає перенесення від іншого реєстратора; домен з'являється зі статусом transfer_pending. auth_code — EPP-код у поточного реєстратора; перед трансфером зніміть там transfer-lock.

curl -s -X POST https://vdsok.guru/api/v1/domains/77/renew -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" -d '{"years": 1}' \
  | jq '{status, charged, expires_at: .domain.expires_at}'

curl -s -X POST https://vdsok.guru/api/v1/domains/transfers -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"name": "example.org", "auth_code": "AbC-123-xyz"}' | jq '{status, charged}'

Білінг: баланс, поповнення, рахунки

Усі послуги оплачуються з балансу акаунта в його валюті (currency). Поповнення йде через платіжний шлюз за посиланням; API не приймає карток і не зберігає платіжних даних. Замовлення, продовження та купівля IP списують із балансу синхронно, тому перед ними варто перевірити GET /balance або GET /catalog/quote → balance_sufficient.

Баланс і транзакції

GET /balance (скоуп balance:read): balance, upcoming_7d і upcoming_30d — сума продовжень у найближчі 7 і 30 днів, low_balance — баланс не покриває найближчі 7 днів, auto_renew_total_monthly. Той самий об'єкт приходить у вебхуку balance.low.

GET /transactions — історія руху коштів, нові спочатку, з фільтрами direction (credit/debit), since, until і курсорною пагінацією. type: topup, payment, refund, bonus, referral, adjustment, other; invoice_id пов'язує транзакцію з рахунком.

GET /account (скоуп account:read) — профіль: логін, e-mail, валюта, група клієнта з її знижкою, знижка лояльності, прапорці email_verified і two_factor_enabled.

curl -s https://vdsok.guru/api/v1/balance -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '{balance, currency, upcoming_7d, low_balance}'
curl -s "https://vdsok.guru/api/v1/transactions?direction=credit&limit=5" -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '.data[] | {created_at, type, amount, gateway, invoice_id}'

Поповнення: посилання на оплату

GET /balance/topup-info — валюта, межі суми min/max, бонус першого поповнення (first_topup_bonus_percent, first_topup_eligible) і gateways — плоский список кодів увімкнених шлюзів (["cryptobot", …], а не об'єктів).

POST /balance/topup (скоуп balance:topup, Idempotency-Key, дорогий виклик) з {"amount": "25.00", "gateway": "<код із gateways>"} створює неоплачений рахунок типу topup і повертає payment_url шлюзу. Полів рівно два: будь-яке інше (зокрема return_url) — 400 validation_error. Надішліть користувача за посиланням. Баланс змінюється лише після підтвердження шлюзом — подія invoice.paid або GET /invoices/{invoice_id} → status: paid. У sandbox повертається фейковий payment_url.

curl -s https://vdsok.guru/api/v1/balance/topup-info -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '{min, max, currency, gateways}'
curl -s -X POST https://vdsok.guru/api/v1/balance/topup -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"amount": "25.00", "gateway": "cryptobot"}' | jq '{invoice_id, payment_url, gateway}'

Рахунки: список, PDF, оплата

GET /invoices — рахунки, нові спочатку, з фільтрами status (not_paid, paid, cancelled, refunded) і type (topup, vds_purchase, vds_renewal, ip_purchase, domain_registration, domain_renewal, domain_transfer, other). GET /invoices/{invoice_id} — картка з рядками items (у кожному — server_id або domain_id) і pdf_url. GET /invoices/{invoice_id}/pdf віддає application/pdf (дорогий виклик).

POST /invoices/{invoice_id}/pay (скоуп invoices:pay, Idempotency-Key) оплачує неоплачений рахунок із балансу синхронно і відповідає 200 з {"status": "paid", "amount", "balance_after"}. Якщо рахунок замовляв сервер, той розгортається фоном уже після відповіді — стежте за ним через GET /orders або вебхук server.created, як у сценарії серверів. За браку коштів — 402, і нічого не змінюється. POST /invoices/{invoice_id}/payment-link дає посилання на оплату рахунку через шлюз (необов'язкове тіло {"gateway"}; інших полів ручка не приймає). Прострочені неоплачені рахунки породжують вебхук invoice.overdue.

curl -s "https://vdsok.guru/api/v1/invoices?status=not_paid" -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '.data[] | {id, type, amount, due_at}'
curl -s https://vdsok.guru/api/v1/invoices/10231/pdf -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" -o invoice-10231.pdf
curl -s -X POST https://vdsok.guru/api/v1/invoices/10231/pay -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" -H "Idempotency-Key: $(uuidgen)" \
  | jq '{status, amount, balance_after}'
curl -s -X POST https://vdsok.guru/api/v1/invoices/10231/payment-link -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq .payment_url

Вебхуки

Вебхук — це POST від VDSok на ваш https:// URL при події в акаунті. Підписки створюються лише бойовим ключем зі скоупом webhooks:manage; тестовому ключу всі ручки /webhooks* відповідають 403 sandbox_not_supported. URL має бути на публічній адресі, редиректи не виконуються.

Каталог подій

ПодіяКолиdata.object
server.createdсервер створено (замовлення або оплачений рахунок)Server
server.suspendedпослугу призупинено; data.previous.status — попередній статусServer
server.unsuspendedпослугу відновленоServer
server.terminatedвидалено (клієнтом із поверненням, за заявкою або за тривалу несплату)Server
server.reinstalledперевстановлення ОС завершеноServer
invoice.createdновий рахунок (продовження, замовлення, поповнення)Invoice
invoice.paidрахунок оплачено (шлюз або баланс)Invoice
invoice.overdueрахунок прострочено й не оплаченоInvoice
balance.lowбаланс упав нижче порога найближчих списаньBalance
domain.registeredдомен став активним у реєстратораDomain
domain.expiringдомен скоро закінчується (за 30, 7 і 1 день)Domain
domain.renewedдомен продовжено (вручну або автопродовженням)Domain
key.createdу кабінеті створено новий API-ключApiKey
key.revokedAPI-ключ відкликаноApiKey
pingтестова подія від POST /webhooks/{id}/test{subscription_id, message: "pong"}

Актуальний список з описами віддає GET /webhooks/events.

Конверт

{
  "id": "evt_01J7ZK3Q9X4R",
  "type": "server.suspended",
  "created_at": "2026-09-15T10:00:00Z",
  "livemode": true,
  "account_id": 57,
  "api_version": "1",
  "data": {
    "object": {"id": 2001, "name": "web-01", "status": "suspended", "...": "full Server object"},
    "previous": {"status": "active"}
  },
  "resource": "/api/v1/servers/2001"
}

data.object — повний знімок об'єкта в тому самому вигляді, що віддає REST; data.previous — поля, що змінилися, або null; resource — шлях об'єкта в API. Конверт серіалізується один раз при постановці в чергу: повтори й переадресації шлють ті самі байти, тому id і created_at не змінюються. А X-Webhook-Timestamp — це час конкретної спроби, тому підпис на кожній спробі рахується заново: дедуплікуйте за X-Webhook-Id, а не за підписом.

Заголовки доставки

ЗаголовокЗначення
X-Webhook-Signaturev1=<hex HMAC-SHA256(secret, "{timestamp}.{body}")>
X-Webhook-Timestampunix-секунди відправлення цієї спроби
X-Webhook-Idevt_… — id події, однаковий у всіх спроб; дедуплікуйте за ним
X-Webhook-Eventтип події, наприклад server.created
User-AgentVDSok-Webhooks/1.0

Доставка та повтори

Відповідайте будь-яким 2xx протягом 10 секунд — тіло відповіді ігнорується. Обробляйте подію асинхронно: прийміть, поставте у свою чергу, відповідайте 200. Інакше — повтори через 60 с, 5 хв, 15 хв, 1 год і 6 год; після 6-ї невдачі доставка позначається dead. 20 невдач поспіль автоматично вимикають підписку (причина — у disabled_reason), а на пошту акаунта надходить лист; увімкнути назад — PATCH /webhooks/{id} з {"active": true}. Журнал: доставлені записи зберігаються 14 днів, мертві — 30.

Порядок доставки не гарантується, а одна подія може прийти двічі (повтор після таймауту, переадресація). Ідемпотентний обробник із дедуплікацією за X-Webhook-Id — обов'язковий.

Підписка

POST /webhooks з {"url", "events": [...], "description"?} створює підписку й повертає secret (whsec_…) один раз — збережіть його в секретах застосунку. Втрачений секрет не відновлюється, лише замінюється: POST /webhooks/{id}/rotate-secret видає новий, старий перестає працювати одразу, а вже поставлені в чергу доставки будуть підписані новим у момент відправлення.

curl -s https://vdsok.guru/api/v1/webhooks/events -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '.data[] | .type'

curl -s -X POST https://vdsok.guru/api/v1/webhooks -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" -H "Content-Type: application/json" \
  -d '{"url": "https://hooks.example.com/vdsok",
       "events": ["server.created", "server.suspended", "server.terminated", "invoice.paid", "balance.low"],
       "description": "billing sync"}' \
  | jq '{id, secret, events}'     # secret is shown once

Перевірка підпису

Алгоритм: візьміть сирі байти тіла запиту (не пересеріалізований JSON — будь-яка зміна пробілів ламає підпис), складіть рядок "{X-Webhook-Timestamp}.{body}", порахуйте HMAC-SHA256 із секретом підписки, порівняйте hex-результат зі значенням після v1= сталим за часом порівнянням і відхиліть доставку, якщо |now − timestamp| > 300 секунд (захист від повтору). Лише після цього розбирайте JSON. SDK роблять те саме в Webhooks.verify() / construct_event().

Відповідайте 2xx швидко й обробляйте подію у фоні; помилку перевірки підпису логуйте й відповідайте 400 — така доставка буде повторена, і за журналом ви побачите проблему.

import hmac, hashlib, json, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = b"whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"   # from POST /webhooks, keep it secret
seen = set()   # use a persistent store (Redis, DB) in production

def verify(secret: bytes, timestamp: str, body: bytes, signature: str, tolerance: int = 300) -> bool:
    try:
        ts = int(timestamp)
    except (TypeError, ValueError):
        return False
    if abs(time.time() - ts) > tolerance:            # replay protection
        return False
    expected = hmac.new(secret, timestamp.encode() + b"." + body, hashlib.sha256).hexdigest()
    given = signature.removeprefix("v1=")
    return hmac.compare_digest(expected, given)       # constant time

@app.post("/vdsok")
def vdsok_webhook():
    raw = request.get_data()                          # raw bytes, before any JSON parsing
    if not verify(SECRET, request.headers.get("X-Webhook-Timestamp", ""), raw,
                  request.headers.get("X-Webhook-Signature", "")):
        abort(400)
    event_id = request.headers["X-Webhook-Id"]
    if event_id in seen:                              # duplicate retry/redelivery
        return "", 200
    seen.add(event_id)
    event = json.loads(raw)
    if event["type"] == "server.suspended":
        print("server", event["data"]["object"]["id"], "was", event["data"]["previous"]["status"])
    # enqueue heavy work here; answer within 10 s
    return "", 200

Тест, журнал, повтор

POST /webhooks/{id}/test шле подію ping синхронно, оминаючи чергу, і повертає статус і затримку вашого приймача (ok, status, latency_ms, detail) — зручно при налаштуванні. GET /webhooks/{id}/deliveries — журнал із фільтрами status (pending, delivered, dead) і event_type; кожен запис містить payload (той самий конверт), attempts, last_status, last_error і перші 1000 байт відповіді. POST /webhooks/deliveries/{delivery_id}/redeliver ставить доставку в чергу заново з тими самими байтами. Тест і повтор — дорогі виклики. Одну доставку цілком віддає GET /webhooks/deliveries/{delivery_id}, а DELETE /webhooks/{webhook_id} видаляє саму підписку. Свої підписки видно в GET /webhooks, одну — у GET /webhooks/{webhook_id}.

curl -s -X POST https://vdsok.guru/api/v1/webhooks/5/test -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq '{ok, status, latency_ms, detail}'
curl -s "https://vdsok.guru/api/v1/webhooks/5/deliveries?status=dead" -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" \
  | jq '.data[] | {id, event_type, attempts, last_status, last_error}'
curl -s -X POST https://vdsok.guru/api/v1/webhooks/deliveries/9012/redeliver -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" | jq .status
curl -s -X PATCH https://vdsok.guru/api/v1/webhooks/5 -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX" -H "Content-Type: application/json" -d '{"active": true}'
↑ Нагору

SDK

Офіційні SDK покривають увесь v1 і дотримуються тієї самої специфікації, що й цей сайт. Спільне для всіх трьох:

  • конструктор (apiKey, {baseUrl, timeout = 30 s, maxRetries = 2}); заголовки Authorization, Accept, User-Agent: vdsok-sdk-<lang>/<ver>;
  • автоматичні повтори на 429/502/503/504 з урахуванням Retry-After — лише для GET і мутацій з Idempotency-Key; на грошових ручках SDK сам генерує ключ ідемпотентності й віддає його тому, хто викликає;
  • єдина помилка ApiError {status, code, message, requestId, details} і RateLimitInfo із заголовків. У PHP машинний код лежить у errorCode, а не в code: у \Exception уже є успадковане $code (HTTP-статус), перевизначити його не можна;
  • пагінація: усі сторінки обходять client.servers.list_all() у Python, for await (… of await client.servers.list()) у Node і $client->servers->iterate() у PHP; list() віддає одну сторінку;
  • Webhooks.verify(secret, headers, rawBody, tolerance = 300) і construct_event() — перевірка підпису з розділу вебхуків;
  • групи account, balance, invoices, catalog, servers, domains, ssh_keys, keys, webhooks; гроші — рядками/Decimal, дати — в нативні типи.
МоваПакетВстановленняВимоги
Node / TypeScript@vdsok/sdknpm install @vdsok/sdkNode 18+, без runtime-залежностей (глобальний fetch), ESM і CJS
Pythonvdsokpip install vdsokPython 3.9+, httpx; класи Vdsok і AsyncVdsok
PHPvdsok/sdkcomposer config repositories.vdsok vcs https://github.com/VDSok/sdk-php → composer require vdsok/sdk:^1.0 guzzlehttp/guzzlePHP 8.1+, PSR-18/PSR-17 (Guzzle — рекомендований провайдер)

Версії SDK 1.x відповідають API v1; ламкі зміни API вийдуть лише разом із /v2 і новою мажорною версією SDK. Вихідні коди й issue-трекер — на GitHub в організації VDSok (sdk-node, sdk-python, sdk-php).

# pip install vdsok
from vdsok import Vdsok, ApiError

client = Vdsok("vk_live_XXXXXXXXXXXXXXXXXXXXXXXX")   # or Vdsok(api_key, base_url=..., timeout=30, max_retries=2)

for server in client.servers.list_all(status="active"):   # walks every page
    print(server.id, server.name, server.billing.next_due_at)

try:
    result = client.servers.renew(2001, months=3)       # Idempotency-Key generated by the SDK
    print("charged", result.charged, "next due", result.next_due_at)
except ApiError as e:
    if e.code == "insufficient_funds":
        print("short by", e.details["shortfall"])
    else:
        raise

Changelog

Зміни API документуються тут і в полі info.version специфікації. У v1 усе додається лише сумісно: нові поля, нові ендпоінти, нові коди помилок і події вебхуків. Клієнт зобов'язаний ігнорувати невідомі поля й обробляти невідомі коди за HTTP-статусом.

1.0.0 — 2026-09-15

  • Перший публічний випуск Client API v1: /me, акаунт, баланс і транзакції, поповнення, рахунки (список, PDF, оплата з балансу, платіжне посилання), каталог (тарифи, ОС, локації, зони, котирування), сервери (замовлення, статус, живлення, перевстановлення, скидання пароля, продовження, видалення з поверненням, нотатки й автопродовження), замовлення, додаткові IP і PTR, SSH-ключі, домени (доступність, реєстрація, продовження, NS, privacy, трансфер), API-ключі (перегляд і kill switch), вебхуки (підписки, журнал, повтор, тест).
  • Ключі vk_live_/vk_test_, скоупи та пресети, IP-списки, вікна дії.
  • Ідемпотентність на грошових операціях, курсорна пагінація, ліміти 120/20 на хвилину.
  • Sandbox: тестові ключі читають реальні дані й імітують записи.
  • 14 типів подій вебхуків з HMAC-SHA256 підписом і повторами.
  • SDK: @vdsok/sdk (Node), vdsok (Python), vdsok/sdk (PHP).

Усі ендпоінти

Повний список операцій v1 — те саме, що в інтерактивному довіднику, але однією таблицею і без JavaScript.

Колонки: Скоуп — що має бути в ключа (прочерк означає «підійде будь-який валідний ключ»); Idem — чи обов'язковий Idempotency-Key; Дорога — чи рахується виклик в окремому кошику ліміту; Sandbox — поведінка під тестовим ключем (real — як у live, fake — імітація без запису, forbidden — 403).

Meta

ОпераціяСкоупIdemДорогаSandbox
GET /health———real
GET /openapi.json———real
GET /me———real

Account

ОпераціяСкоупIdemДорогаSandbox
GET /accountaccount:read——real
GET /balancebalance:read——real
GET /transactionsbalance:read——real
GET /balance/topup-infobalance:read——real
POST /balance/topupbalance:topupтактакfake

Invoices

ОпераціяСкоупIdemДорогаSandbox
GET /invoicesinvoices:read——real
GET /invoices/{invoice_id}invoices:read——real
GET /invoices/{invoice_id}/pdfinvoices:read—такreal
POST /invoices/{invoice_id}/payinvoices:payтак—fake
POST /invoices/{invoice_id}/payment-linkinvoices:pay——fake

Catalog

ОпераціяСкоупIdemДорогаSandbox
GET /catalog/tariffs———real
GET /catalog/tariffs/{tariff_id}———real
GET /catalog/os———real
GET /catalog/locations———real
GET /catalog/zones———real
GET /catalog/quote——такreal

Servers

ОпераціяСкоупIdemДорогаSandbox
GET /serversservers:read——real
POST /serversservers:orderтактакfake
GET /servers/{server_id}servers:read——real
DELETE /servers/{server_id}servers:deleteтак—fake
PATCH /servers/{server_id}servers:manage——fake
GET /servers/{server_id}/statusservers:read—такreal
GET /servers/{server_id}/refund-quoteservers:read——real
POST /servers/{server_id}/renewservers:orderтактакfake

Server actions

ОпераціяСкоупIdemДорогаSandbox
POST /servers/{server_id}/actions/powerservers:manage——fake
POST /servers/{server_id}/actions/reinstallservers:manage——fake
POST /servers/{server_id}/actions/reset-passwordservers:manage——fake

Orders

ОпераціяСкоупIdemДорогаSandbox
GET /ordersservers:read——real
GET /orders/{invoice_id}servers:read——real

IPs

ОпераціяСкоупIdemДорогаSandbox
GET /servers/{server_id}/ipsservers:read—такreal
POST /servers/{server_id}/ipsservers:orderтактакfake
GET /servers/{server_id}/ips/quoteservers:read—такreal
DELETE /servers/{server_id}/ips/{ip_id}servers:order——fake
PUT /servers/{server_id}/ips/{ip_id}/ptrservers:manage——fake

SSH keys

ОпераціяСкоупIdemДорогаSandbox
GET /ssh-keysservers:read——real
POST /ssh-keysservers:manage——fake
DELETE /ssh-keys/{key_id}servers:manage——fake

Domains

ОпераціяСкоупIdemДорогаSandbox
GET /domains/availabilitydomains:read—такreal
GET /domainsdomains:read——real
POST /domainsdomains:orderтак—fake
GET /domains/{domain_id}domains:read——real
PATCH /domains/{domain_id}domains:manage——fake
POST /domains/{domain_id}/renewdomains:orderтак—fake
PUT /domains/{domain_id}/nameserversdomains:manage——fake
POST /domains/transfersdomains:orderтак—fake

Keys

ОпераціяСкоупIdemДорогаSandbox
GET /keyskeys:read——real
GET /keys/{key_id}keys:read——real
DELETE /keys/{key_id}———real

Webhooks

ОпераціяСкоупIdemДорогаSandbox
GET /webhookswebhooks:manage——forbidden
POST /webhookswebhooks:manage——forbidden
GET /webhooks/eventswebhooks:manage——forbidden
GET /webhooks/{webhook_id}webhooks:manage——forbidden
DELETE /webhooks/{webhook_id}webhooks:manage——forbidden
PATCH /webhooks/{webhook_id}webhooks:manage——forbidden
POST /webhooks/{webhook_id}/rotate-secretwebhooks:manage——forbidden
POST /webhooks/{webhook_id}/testwebhooks:manage—такforbidden
GET /webhooks/{webhook_id}/deliverieswebhooks:manage——forbidden
GET /webhooks/deliveries/{delivery_id}webhooks:manage——forbidden
POST /webhooks/deliveries/{delivery_id}/redeliverwebhooks:manage—такforbidden

Специфікація OpenAPI

Машиночитаний опис API — документ OpenAPI 3.1, той самий, з якого зібрано цей сайт і SDK. Він доступний без ключа:

  • Завантажити openapi.json — кешована копія; живий документ віддає GET /api/v1/openapi.json.
  • Інтерактивний довідник — усі операції, схеми, приклади запитів і відповідей, з можливістю виконати запит своїм ключем просто з браузера.

У документі використовуються розширення: x-scopes (потрібні скоупи), x-idempotent (потрібен Idempotency-Key), x-expensive (рахується в кошик 20/хв), x-sandbox (real | fake | forbidden — поведінка під тестовим ключем). Зі специфікації можна згенерувати клієнт під будь-яку мову — openapi-typescript, openapi-generator, oapi-codegen — або підключити її в Postman/Insomnia.

curl -s https://vdsok.guru/api/v1/openapi.json -o vdsok-openapi.json
jq '.info.version, (.paths | keys | length)' vdsok-openapi.json

Підтримка та зворотний зв'язок

Що додати до звернення

Кожна відповідь API несе заголовок X-Request-ID. За ним ми знаходимо ваш запит у журналах за секунди. Тому у зверненні:

  • request_id із заголовка X-Request-ID (він же в тілі помилки);
  • метод і шлях: POST /servers/2001/renew, а не «продовження не працює»;
  • час у UTC — хоча б із точністю до хвилини;
  • ідентифікатор ключа з GET /keys і його режим (live або test). Сам ключ надсилати не потрібно ніколи — якщо він потрапив у листування, відкличте його через DELETE /keys/{key_id} і випустіть новий.

Куди писати

  • Тікет у кабінеті — основний шлях: видно акаунт, послуги та історію.
  • Пошта: support@vdsok.guru.
  • Контакти й час роботи: сторінка контактів.

Доступність API

Публічної сторінки статусу немає. Проба доступності — GET /health: вона відповідає 200 без ключа й каже, чи ввімкнено API (status: ok | disabled). Це саме проба, а не захищена ручка: за нею зручно зрозуміти, що мовчить — API чи ваш бік.

Помилка в SDK або в документації

  • SDK живуть на GitHub: sdk-node, sdk-python, sdk-php — issue заводьте там же.
  • Неточність на цій сторінці — тікетом або листом, із якорем розділу (наприклад #idempotency).
↑ Нагору