Огляд і базовий 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; вказуйте його у зверненнях до підтримки.
Швидкий старт
- Створіть ключ у кабінеті: Налаштування → API (
/my/api). Ключ показується один раз. - Викличте
GET /me— найдешевший спосіб перевірити ключ і побачити його скоупи та ліміти. - Далі: автентифікація, помилки, сценарій роботи з серверами.
curl https://vdsok.guru/api/v1/me \ -H "Authorization: Bearer vk_live_XXXXXXXXXXXXXXXXXXXXXXXX"
Автентифікація, скоупи та пресети
Кожен запит містить заголовок Authorization: Bearer <ключ>. Ключ починається з vk_live_ (бойовий) або vk_test_ (тестовий, див. Sandbox).
Ключі створює власник акаунта в кабінеті (/my/api): ім'я, режим live/test, набір скоупів, необов'язковий список дозволених IP/CIDR і вікно дії (not_before/expires_at). Створення підтверджується кодом TOTP або паролем; секрет показується один раз. Ключі не залежать від сесій кабінету: зміна пароля, «вийти всюди» та налаштування 2FA їх не чіпають; бан акаунта відкликає всі ключі. Через API ключі не створюються — лише переглядаються (GET /keys) і відкликається свій ключ.
Скоупи
Кожна операція перелічує потрібні скоупи в x-scopes специфікації; порожній список означає «будь-який валідний ключ» (каталог, /me). Зовсім без ключа доступні лише GET /health (завжди 200 зі status: ok | disabled — це проба доступності, а не захищена ручка) і GET /openapi.json. Брак скоупа → 403 insufficient_scope, у details.required — чого не вистачило.
| Скоуп | Що відкриває |
|---|---|
account:read | GET /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 |
operate | read_only + servers:manage, servers:order, domains:manage, invoices:pay |
full | усі скоупи, включно з balance:topup, servers:delete, domains:order, webhooks:manage |
Kill switch
DELETE /keys/{key_id} відкликає лише той ключ, яким зроблено запит, незалежно від його скоупів. Так витеклий ключ можна погасити звідусіль (із CI, із сервера), не даючи кожному ключу право знищити всю інтеграцію. Інші ключі відкликаються в кабінеті. Працює і для тестових ключів.
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 | імітація запису: замовлення, продовження, живлення, домени, поповнення |
forbidden | 403 sandbox_not_supported — усі ручки вебхуків; підписки лише для live |
Якщо тестові ключі вимкнено на боці VDSok, будь-який запит відповідає 403 sandbox_disabled.
# 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 одна форма помилки — на будь-який статус, включно з 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-статусом — так додавання коду не ламає інтеграції.
| Статус | Коди |
|---|---|
400 | invalid_request, validation_error, invalid_cursor, idempotency_key_required, os_not_allowed, invalid_period, invalid_action, upstream_rejected |
401 | invalid_token, key_expired, key_not_yet_valid |
402 | insufficient_funds — нічого не списано й не створено |
403 | insufficient_scope, ip_not_allowed, account_suspended, api_disabled_for_account, sandbox_not_supported, sandbox_disabled, server_blocked, domain_blocked |
404 | not_found — об'єкта немає на цьому акаунті (чужі об'єкти теж 404, не 403) |
409 | conflict, idempotency_conflict, idempotency_in_progress, operation_in_progress, service_state, no_capacity, tariff_unavailable, ip_limit_reached, domain_taken, domain_exists, cancel_pending |
413 / 415 | payload_too_large, unsupported_media_type |
429 | rate_limited |
500 | server_error — збій у нас, request id уже в логах |
502 | upstream_error — панель, реєстратор або шлюз відповіли помилкою; для грошових операцій нічого не списано, якщо відповідь не каже іншого |
503 | api_disabled, upstream_unavailable, temporarily_unavailable — повторіть після Retry-After |
504 | upstream_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 секунд. Співробітники VDSok можуть підняти ліміти конкретному ключу; поточну стелю й залишок повертають заголовки X-RateLimit-Limit і X-RateLimit-Remaining кожної відповіді, а налаштування конкретного ключа видно в GET /keys/{id}. Читання в кабінеті в рахунок API не йдуть.
Дорогі виклики позначено x-expensive: true у специфікації:
GET /servers/{server_id}/statusіGET /servers/{server_id}?include=live— живе опитування панелі;GET /domains/availability— запит до реєстратора;GET /catalog/quote;POST /servers,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-Reset | unix-час (секунди) скидання вікна |
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, — інакше повтор нескінченно отримував би idempotency_in_progress. Тому повтор ніколи не спише двічі. Замовлення сервера з таймаутом панелі (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 не використовується ніколи: 0.1 + 0.2 у двійковій арифметиці не дорівнює 0.3, а в білінгу така похибка перетворюється на розбіжність у рахунку.
- Розбирайте суми в десятковий тип:
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..720, якщо в тарифу hourly_available).
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.
# 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/хв. Сервери, імпортовані з історії з синтетичними id (>= 2000000000), запису в панелі не мають — 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 .password6. Додаткові 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..720}. Обов'язковий 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, balance_after і cancel_request_withdrawn — якщо була заявка на скасування, вона відкликається. Уже видалений сервер (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.amount, balance_after, cancel_request_withdrawn}'Домени: повний сценарій
Домени реєструються на контактні дані з профілю акаунта — ті самі, що використовує кабінет; через 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"]} замінює весь набір (2..4 унікальних імені). 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.revoked | API-ключ відкликано (клієнтом, самим ключем, співробітником або баном) | 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-Signature | v1=<hex HMAC-SHA256(secret, "{timestamp}.{body}")> |
X-Webhook-Timestamp | unix-секунди відправлення цієї спроби |
X-Webhook-Id | evt_… — id події, однаковий у всіх спроб; дедуплікуйте за ним |
X-Webhook-Event | тип події, наприклад server.created |
User-Agent | VDSok-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 ставить доставку в чергу заново з тими самими байтами. Тест і повтор — дорогі виклики.
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 і дотримуються тієї самої специфікації, що й цей сайт (у Node-пакеті типи генеруються з неї напряму). Спільне для всіх трьох:
- конструктор
(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-статус), перевизначити його не можна; - ітератори пагінації (
for server in client.servers.list()обходить усі сторінки); Webhooks.verify(secret, headers, rawBody, tolerance = 300)іconstruct_event()— перевірка підпису з розділу вебхуків;- групи
account,balance,invoices,catalog,servers,domains,ssh_keys,keys,webhooks; гроші — рядками/Decimal, дати — в нативні типи.
| Мова | Пакет | Встановлення | Вимоги |
|---|---|---|---|
| Node / TypeScript | @vdsok/sdk | npm install @vdsok/sdk | Node 18+, без runtime-залежностей (глобальний fetch), ESM і CJS |
| Python | vdsok | pip install vdsok | Python 3.9+, httpx; класи Vdsok і AsyncVdsok |
| PHP | vdsok/sdk | composer require vdsok/sdk guzzlehttp/guzzle | PHP 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:
raiseChangelog
Зміни 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).
Специфікація 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