Тема
Рассчитать и зафиксировать котировку
POST /quotes
operationId: createQuote
Котировка фиксирует цену и payload, но не резервирует доменное имя и не означает исполнение. Срок по умолчанию 15 минут, источник истины expires_at. Исполнять должен тот же bearer-клиент, тот же customer и environment. Нельзя передать свою цену, скидку или сумму. GET/DELETE котировки и изменение выданной котировки не реализованы. Для доменных renew/restore используйте target UUID; restore проверяет период восстановления даже для локально удалённого домена. Все данные ограничены текущим реселлером и средой ключа test/live. Смена среды параметром запроса не поддерживается.
Scopes: quotes.create
Параметры
Idempotency-Key
Расположение: header. Обязательный. Обязателен для POST/PUT/PATCH/DELETE, включая check, sync, test, rotate-secret. После trim 1..160 символов. Один ключ на логическую операцию и API-ключ. Повторите те же method/path/query и байты JSON. TTL HTTP replay по умолчанию 24 часа от первого резервирования; не продлевается. Не переиспользуйте ключ для новой операции, доменная задача дополнительно сохраняет связь с ним.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | minLength: 1; maxLength: 160 |
Пример
json
"webhook-create-20261005-0001"Тело запроса
Обязательное.
application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["items"] | Котировка привязывается к текущему ключу, реселлеру, покупателю и среде. Срок задаётся сервером, по умолчанию 15 минут; используйте expires_at. Создание котировки не регистрирует домен и не резервирует имя. Схема: CoreCreateQuoteRequest |
$.customer_uuid | string / null | Нет | format: "uuid" | Обязателен в managed; aggregate допускает отсутствие. |
$.items | array | Да | minItems: 1; maxItems: 100 | Строки котировки. |
$.items[] | object | Да | required: ["resource_type","resource_uuid","operation","period_unit","period_count"]; Дополнительные поля запрещены | Цена выводится из опубликованного предложения, тарифного плана и правил реселлера. Схема: CoreQuoteInputItem |
$.items[].resource_type | string | Да | enum: "product", "domain_zone" | Тип ресурса. |
$.items[].resource_uuid | string | Да | format: "uuid" | resource_uuid из каталога; не uuid предложения. |
$.items[].operation | string | Да | enum: "order", "add-on", "register", "renew", "transfer", "restore" | Допустимые значения валидатора. order/register исполняются через orders; renew/transfer/restore через доменные методы. Наличие add-on в валидаторе НЕ означает отдельный метод исполнения add-on. |
$.items[].period_unit | string | Да | enum: "day", "month", "year" | Для доменов только year. |
$.items[].period_count | integer | Да | format: "int64"; minimum: 1; maximum: 120 | От 1 до 120 на уровне quote. Регистрация домена дополнительно ограничена 1..10 годами; transfer/restore используют 1 год. |
$.items[].quantity | integer | Нет | format: "int64"; minimum: 1; maximum: 100; default: 1 | По умолчанию 1. При регистрации/платных доменных операциях ровно 1. |
$.items[].currency | string | Нет | minLength: 3; maxLength: 3 | Код длиной 3. Валюта всей котировки выбирается из первой строки либо настроек реселлера; currency последующих строк не переключает валюту. Передавайте одинаковую валюту во всех строках. |
$.items[].payload | object | Нет | required: []; Дополнительные поля разрешены | Payload сохраняется в котировке. Для register обязательность domain_name/contact_uuids и ограничения NS проверяются при создании заказа, а не только quotes. Для renew/restore укажите domain_service_uuid (domain_uuid поддерживается как альтернативное имя). Для product/order передавайте domain_name для хостинга. Неизвестные поля могут храниться, но не получают автоматической семантики. Для product/order этот контракт гарантирует только поля, реально используемые OrderCreator: domain_name, notes, external_id. Произвольный payload не передаётся автоматически как команда Plesk. Схема: CoreQuotePayload |
$.items[].payload.domain_name | string | Нет | maxLength: 253 | Домен, соответствующий зоне; для hosting домен сайта. Для хостинга отсутствие/ошибка домена может проявиться при асинхронном provisioning, даже если quote/заказ приняты. |
$.items[].payload.contact_uuids | object | Нет | required: ["owner","admin","tech","billing"] | Все четыре роли обязательны при регистрации; допустим один UUID для нескольких ролей. Схема: CoreContactRoles |
$.items[].payload.contact_uuids.owner | string | Да | format: "uuid" | Владелец. |
$.items[].payload.contact_uuids.admin | string | Да | format: "uuid" | Административный контакт. |
$.items[].payload.contact_uuids.tech | string | Да | format: "uuid" | Технический контакт. |
$.items[].payload.contact_uuids.billing | string | Да | format: "uuid" | Платёжный контакт. |
$.items[].payload.nameservers | array | Нет | minItems: 2; maxItems: 6 | Необязательно при регистрации; при наличии от 2 до 6, hostname уникальны без учёта регистра. |
$.items[].payload.nameservers[] | object | Да | required: ["hostname"] | NS домена; IP передаются при необходимости glue. Схема: CoreNameserver |
$.items[].payload.nameservers[].hostname | string | Да | maxLength: 253 | Имя NS. |
$.items[].payload.nameservers[].ipv4 | string / null | Нет | format: "ipv4" | IPv4. |
$.items[].payload.nameservers[].ipv6 | string / null | Нет | format: "ipv6" | IPv6. |
$.items[].payload.purpose | string / null | Нет | maxLength: 1000 | Назначение домена. |
$.items[].payload.notes | string / null | Нет | maxLength: 1000 | Примечание заказа. |
$.items[].payload.external_id | string / null | Нет | maxLength: 255 | Идентификатор создаваемой услуги, уникален для реселлера+среды. |
$.items[].payload.whois_privacy_enabled | boolean | Нет | Запрос приватности при регистрации; поддержка зависит от регистратора. | |
$.items[].payload.domain_service_uuid | string | Нет | format: "uuid" | UUID домена для операции над существующим доменом. |
$.items[].payload.domain_uuid | string | Нет | format: "uuid" | Альтернативное имя domain_service_uuid. |
Регистрация
json
{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "register",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "reseller-example.kz",
"external_id": "domain-3042",
"contact_uuids": {
"owner": "019a0000-0000-7000-8000-000000000012",
"admin": "019a0000-0000-7000-8000-000000000012",
"tech": "019a0000-0000-7000-8000-000000000012",
"billing": "019a0000-0000-7000-8000-000000000012"
},
"nameservers": [
{
"hostname": "ns1.example.com"
},
{
"hostname": "ns2.example.com"
}
],
"purpose": "Сайт организации",
"whois_privacy_enabled": false
}
}
]
}bash
API_BASE_URL='https://api.b.websoft.kz/api/reseller/v1'
: "${RESELLER_API_TOKEN:?Set RESELLER_API_TOKEN in your environment}"
: "${IDEMPOTENCY_KEY:?Set one persisted unique key per logical request}"
curl --request POST "$API_BASE_URL/quotes" \
--connect-timeout 5 --max-time 20 --fail-with-body \
--header "Authorization: Bearer $RESELLER_API_TOKEN" \
--header 'Accept: application/json' \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "register",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "reseller-example.kz",
"external_id": "domain-3042",
"contact_uuids": {
"owner": "019a0000-0000-7000-8000-000000000012",
"admin": "019a0000-0000-7000-8000-000000000012",
"tech": "019a0000-0000-7000-8000-000000000012",
"billing": "019a0000-0000-7000-8000-000000000012"
},
"nameservers": [
{
"hostname": "ns1.example.com"
},
{
"hostname": "ns2.example.com"
}
],
"purpose": "Сайт организации",
"whois_privacy_enabled": false
}
}
]
}'php
<?php
declare(strict_types=1);
// PHP 8.0+; extensions: curl, json. Persist the key/body before any write.
function requiredEnv(string $name): string
{
$value = getenv($name);
if ($value === false || $value === '' || str_contains($value, "\r") || str_contains($value, "\n")) {
throw new RuntimeException('Set a valid environment variable: ' . $name);
}
return $value;
}
$baseUrl = rtrim(getenv('API_BASE_URL') ?: 'https://api.b.websoft.kz/api/reseller/v1', '/');
$path = '/quotes';
$url = $baseUrl . $path;
$headers = [
'Authorization: Bearer ' . requiredEnv('RESELLER_API_TOKEN'),
'Accept: application/json',
'Idempotency-Key: ' . requiredEnv('IDEMPOTENCY_KEY'),
'Content-Type: application/json',
];
$body = '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "register",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "reseller-example.kz",
"external_id": "domain-3042",
"contact_uuids": {
"owner": "019a0000-0000-7000-8000-000000000012",
"admin": "019a0000-0000-7000-8000-000000000012",
"tech": "019a0000-0000-7000-8000-000000000012",
"billing": "019a0000-0000-7000-8000-000000000012"
},
"nameservers": [
{
"hostname": "ns1.example.com"
},
{
"hostname": "ns2.example.com"
}
],
"purpose": "Сайт организации",
"whois_privacy_enabled": false
}
}
]
}';
$responseHeaders = [];
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException('Cannot initialize cURL.');
}
try {
if (!curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 20,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HEADERFUNCTION => static function (CurlHandle $curl, string $line) use (&$responseHeaders): int {
if (str_starts_with($line, 'HTTP/')) {
$responseHeaders = [];
} elseif (str_contains($line, ':')) {
[$name, $value] = explode(':', $line, 2);
$responseHeaders[strtolower(trim($name))] = trim($value);
}
return strlen($line);
},
])) {
throw new RuntimeException('Cannot configure cURL.');
}
$raw = curl_exec($handle);
if ($raw === false) {
throw new RuntimeException('Transport failure; result may be unknown. cURL code: ' . curl_errno($handle));
}
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
} finally {
unset($handle);
}
$requestId = $responseHeaders['x-request-id'] ?? null;
$retryAfter = $responseHeaders['retry-after'] ?? null;
$data = null;
if ($raw !== '' && !in_array($status, [204, 205], true)) {
try {
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $error) {
if ($status >= 200 && $status < 300) {
throw new RuntimeException('Unexpected non-JSON success response.', 0, $error);
}
}
}
if ($status < 200 || $status >= 300) {
// Inspect $data['error']; queue 429 using $retryAfter, never create a new write key blindly.
throw new RuntimeException('HTTP ' . $status . '; request_id=' . ($requestId ?? '-') . '; retry_after=' . ($retryAfter ?? '-'));
}
// $data contains the decoded response; 202 means accepted, not completed.
echo 'HTTP ' . $status . PHP_EOL;js
// Node.js 22+; save as request.mjs. No third-party dependencies.
function requiredEnv(name) {
const value = process.env[name];
if (!value || /[\r\n]/u.test(value)) throw new Error('Set a valid environment variable: ' + name);
return value;
}
const baseUrl = (process.env.API_BASE_URL || "https://api.b.websoft.kz/api/reseller/v1").replace(/\/$/u, '');
let path = "/quotes";
const url = baseUrl + path;
const body = `{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "register",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "reseller-example.kz",
"external_id": "domain-3042",
"contact_uuids": {
"owner": "019a0000-0000-7000-8000-000000000012",
"admin": "019a0000-0000-7000-8000-000000000012",
"tech": "019a0000-0000-7000-8000-000000000012",
"billing": "019a0000-0000-7000-8000-000000000012"
},
"nameservers": [
{
"hostname": "ns1.example.com"
},
{
"hostname": "ns2.example.com"
}
],
"purpose": "Сайт организации",
"whois_privacy_enabled": false
}
}
]
}`;
const response = await fetch(url, {
method: "POST",
redirect: 'manual',
signal: AbortSignal.timeout(20_000),
headers: {
Authorization: 'Bearer ' + requiredEnv('RESELLER_API_TOKEN'),
Accept: 'application/json',
'Idempotency-Key': requiredEnv('IDEMPOTENCY_KEY'),
"Content-Type": "application/json",
},
body,
});
const requestId = response.headers.get('x-request-id');
const retryAfter = response.headers.get('retry-after');
const raw = await response.text();
let data = null;
if (raw && ![204, 205].includes(response.status)) {
try { data = JSON.parse(raw); }
catch (cause) { if (response.ok) throw new Error('Unexpected non-JSON success response.', { cause }); }
}
if (!response.ok) {
// Inspect data?.error; schedule 429 using retryAfter. Do not generate another write key.
throw new Error('HTTP ' + response.status + '; request_id=' + requestId + '; retry_after=' + retryAfter);
}
// A timeout may have an unknown result. No automatic mutation retry in this example.
// data contains the response; 202 means accepted, not completed.
console.log('HTTP', response.status);python
# Python 3.10+; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import HTTPRedirectHandler, Request, build_opener
def required_env(name):
value = os.environ.get(name, "")
if not value or "\r" in value or "\n" in value:
raise RuntimeError("Set a valid environment variable: " + name)
return value
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
base_url = os.environ.get("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").rstrip("/")
path = "/quotes"
url = base_url + path
headers = {
"Authorization": "Bearer " + required_env("RESELLER_API_TOKEN"),
"Accept": "application/json",
"Idempotency-Key": required_env("IDEMPOTENCY_KEY"),
"Content-Type": "application/json",
}
body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"register\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"reseller-example.kz\",\n \"external_id\": \"domain-3042\",\n \"contact_uuids\": {\n \"owner\": \"019a0000-0000-7000-8000-000000000012\",\n \"admin\": \"019a0000-0000-7000-8000-000000000012\",\n \"tech\": \"019a0000-0000-7000-8000-000000000012\",\n \"billing\": \"019a0000-0000-7000-8000-000000000012\"\n },\n \"nameservers\": [\n {\n \"hostname\": \"ns1.example.com\"\n },\n {\n \"hostname\": \"ns2.example.com\"\n }\n ],\n \"purpose\": \"Сайт организации\",\n \"whois_privacy_enabled\": false\n }\n }\n ]\n}".encode("utf-8")
request = Request(url, data=body, headers=headers, method="POST")
opener = build_opener(NoRedirect())
try:
response = opener.open(request, timeout=20)
except HTTPError as error:
response = error # HTTP failure still has a response body and headers.
# Network/timeout exceptions propagate: a write may already have been accepted.
with response:
status = response.status
request_id = response.headers.get("X-Request-ID")
retry_after = response.headers.get("Retry-After")
raw = response.read()
data = None
if raw and status not in (204, 205):
try:
data = json.loads(raw)
except (ValueError, UnicodeDecodeError):
if 200 <= status < 300:
raise RuntimeError("Unexpected non-JSON success response.")
if not 200 <= status < 300:
# Inspect data['error']; schedule 429 with retry_after, keeping the same persisted key.
raise RuntimeError(f"HTTP {status}; request_id={request_id}; retry_after={retry_after}")
# data contains the response. Do not log secrets/PII; 202 is not completion.
print("HTTP", status)go
// Go 1.22+; standard library only. Save as main.go and run: go run main.go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func requiredEnv(name string) string {
value := os.Getenv(name)
if value == "" || strings.ContainsAny(value, "\r\n") { panic("Set a valid environment variable: " + name) }
return value
}
func main() {
baseURL := os.Getenv("API_BASE_URL")
if baseURL == "" { baseURL = "https://api.b.websoft.kz/api/reseller/v1" }
path := "/quotes"
endpoint := strings.TrimRight(baseURL, "/") + path
request, err := http.NewRequest("POST", endpoint, strings.NewReader("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"register\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"reseller-example.kz\",\n \"external_id\": \"domain-3042\",\n \"contact_uuids\": {\n \"owner\": \"019a0000-0000-7000-8000-000000000012\",\n \"admin\": \"019a0000-0000-7000-8000-000000000012\",\n \"tech\": \"019a0000-0000-7000-8000-000000000012\",\n \"billing\": \"019a0000-0000-7000-8000-000000000012\"\n },\n \"nameservers\": [\n {\n \"hostname\": \"ns1.example.com\"\n },\n {\n \"hostname\": \"ns2.example.com\"\n }\n ],\n \"purpose\": \"Сайт организации\",\n \"whois_privacy_enabled\": false\n }\n }\n ]\n}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
request.Header.Set("Accept", "application/json")
request.Header.Set("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: 20 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
}
response, err := client.Do(request)
if err != nil { panic("Transport failure; operation result may be unknown") }
defer response.Body.Close()
raw, err := io.ReadAll(response.Body)
if err != nil { panic("Response read failed; operation result may be unknown") }
requestID := response.Header.Get("X-Request-ID")
retryAfter := response.Header.Get("Retry-After")
var data any
if len(raw) > 0 && response.StatusCode != 204 && response.StatusCode != 205 {
decoder := json.NewDecoder(strings.NewReader(string(raw)))
decoder.UseNumber() // Preserve integer money/identifiers without float rounding.
if err := decoder.Decode(&data); err != nil && response.StatusCode >= 200 && response.StatusCode < 300 { panic("Unexpected non-JSON success response") }
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
// Inspect data; schedule 429 using retryAfter. Never create a new key blindly.
panic(fmt.Sprintf("HTTP %d; request_id=%s; retry_after=%s", response.StatusCode, requestID, retryAfter))
}
// data holds the JSON value; 202 means accepted, not completed. Do not log secrets.
fmt.Println("HTTP", response.StatusCode)
}java
// Java 17+; standard library only. Save as RequestExample.java.
// Run: java RequestExample.java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class RequestExample {
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty() || value.contains("\r") || value.contains("\n")) {
throw new IllegalStateException("Set a valid environment variable: " + name);
}
return value;
}
private static String envOr(String name, String fallback) {
String value = System.getenv(name);
return value == null || value.isEmpty() ? fallback : value;
}
public static void main(String[] args) throws Exception {
String baseUrl = envOr("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").replaceAll("/+$", "");
String path = "/quotes";
URI uri = URI.create(baseUrl + path);
String body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"register\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"reseller-example.kz\",\n \"external_id\": \"domain-3042\",\n \"contact_uuids\": {\n \"owner\": \"019a0000-0000-7000-8000-000000000012\",\n \"admin\": \"019a0000-0000-7000-8000-000000000012\",\n \"tech\": \"019a0000-0000-7000-8000-000000000012\",\n \"billing\": \"019a0000-0000-7000-8000-000000000012\"\n },\n \"nameservers\": [\n {\n \"hostname\": \"ns1.example.com\"\n },\n {\n \"hostname\": \"ns2.example.com\"\n }\n ],\n \"purpose\": \"Сайт организации\",\n \"whois_privacy_enabled\": false\n }\n }\n ]\n}";
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(20))
.header("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
.header("Accept", "application/json")
.header("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
// Reuse HttpClient in production. A timeout may mean an unknown write result.
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
int status = response.statusCode();
String requestId = response.headers().firstValue("X-Request-ID").orElse("");
String retryAfter = response.headers().firstValue("Retry-After").orElse("");
String responseBody = (status == 204 || status == 205) ? "" : response.body();
if (status < 200 || status >= 300) {
// Decode responseBody with your JSON library; schedule 429 using retryAfter.
throw new IllegalStateException("HTTP " + status + "; request_id=" + requestId + "; retry_after=" + retryAfter);
}
// Java SE has no JSON object mapper: pass responseBody to your project's JSON parser.
// Do not parse an empty 204 body; 202 means accepted, not completed.
System.out.println("HTTP " + status);
}
}csharp
// C# / .NET 8+ console app; standard library only. Save as Program.cs.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
static string RequiredEnv(string name)
{
string? value = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(value) || value.Contains('\r') || value.Contains('\n'))
throw new InvalidOperationException("Set a valid environment variable: " + name);
return value;
}
string baseUrl = (Environment.GetEnvironmentVariable("API_BASE_URL") ?? "https://api.b.websoft.kz/api/reseller/v1").TrimEnd('/');
string path = "/quotes";
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(20) };
// Reuse HttpClient (or IHttpClientFactory) in production, not one client per request.
using var request = new HttpRequestMessage(new HttpMethod("POST"), baseUrl + path);
request.Headers.Add("Authorization", "Bearer " + RequiredEnv("RESELLER_API_TOKEN"));
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Idempotency-Key", RequiredEnv("IDEMPOTENCY_KEY"));
request.Content = new StringContent("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"register\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"reseller-example.kz\",\n \"external_id\": \"domain-3042\",\n \"contact_uuids\": {\n \"owner\": \"019a0000-0000-7000-8000-000000000012\",\n \"admin\": \"019a0000-0000-7000-8000-000000000012\",\n \"tech\": \"019a0000-0000-7000-8000-000000000012\",\n \"billing\": \"019a0000-0000-7000-8000-000000000012\"\n },\n \"nameservers\": [\n {\n \"hostname\": \"ns1.example.com\"\n },\n {\n \"hostname\": \"ns2.example.com\"\n }\n ],\n \"purpose\": \"Сайт организации\",\n \"whois_privacy_enabled\": false\n }\n }\n ]\n}", Encoding.UTF8, "application/json");
// Transport exceptions may have an unknown result; do not issue a new mutation automatically.
using var response = await client.SendAsync(request);
int status = (int)response.StatusCode;
string requestId = response.Headers.TryGetValues("X-Request-ID", out var ids) ? string.Join(",", ids) : "";
string retryAfter = response.Headers.TryGetValues("Retry-After", out var delays) ? string.Join(",", delays) : "";
string raw = await response.Content.ReadAsStringAsync();
JsonElement? data = null;
if (raw.Length > 0 && status != 204 && status != 205)
{
try { using var document = JsonDocument.Parse(raw); data = document.RootElement.Clone(); }
catch (JsonException) { if (response.IsSuccessStatusCode) throw; }
}
if (!response.IsSuccessStatusCode)
{
// Inspect data; schedule 429 using retryAfter and preserve the same key/body.
throw new HttpRequestException($"HTTP {status}; request_id={requestId}; retry_after={retryAfter}");
}
// data contains JSON, or null for 204. HTTP 202 is not completion.
Console.WriteLine($"HTTP {status}");Продление
json
{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "renew",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}bash
API_BASE_URL='https://api.b.websoft.kz/api/reseller/v1'
: "${RESELLER_API_TOKEN:?Set RESELLER_API_TOKEN in your environment}"
: "${IDEMPOTENCY_KEY:?Set one persisted unique key per logical request}"
curl --request POST "$API_BASE_URL/quotes" \
--connect-timeout 5 --max-time 20 --fail-with-body \
--header "Authorization: Bearer $RESELLER_API_TOKEN" \
--header 'Accept: application/json' \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "renew",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}'php
<?php
declare(strict_types=1);
// PHP 8.0+; extensions: curl, json. Persist the key/body before any write.
function requiredEnv(string $name): string
{
$value = getenv($name);
if ($value === false || $value === '' || str_contains($value, "\r") || str_contains($value, "\n")) {
throw new RuntimeException('Set a valid environment variable: ' . $name);
}
return $value;
}
$baseUrl = rtrim(getenv('API_BASE_URL') ?: 'https://api.b.websoft.kz/api/reseller/v1', '/');
$path = '/quotes';
$url = $baseUrl . $path;
$headers = [
'Authorization: Bearer ' . requiredEnv('RESELLER_API_TOKEN'),
'Accept: application/json',
'Idempotency-Key: ' . requiredEnv('IDEMPOTENCY_KEY'),
'Content-Type: application/json',
];
$body = '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "renew",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}';
$responseHeaders = [];
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException('Cannot initialize cURL.');
}
try {
if (!curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 20,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HEADERFUNCTION => static function (CurlHandle $curl, string $line) use (&$responseHeaders): int {
if (str_starts_with($line, 'HTTP/')) {
$responseHeaders = [];
} elseif (str_contains($line, ':')) {
[$name, $value] = explode(':', $line, 2);
$responseHeaders[strtolower(trim($name))] = trim($value);
}
return strlen($line);
},
])) {
throw new RuntimeException('Cannot configure cURL.');
}
$raw = curl_exec($handle);
if ($raw === false) {
throw new RuntimeException('Transport failure; result may be unknown. cURL code: ' . curl_errno($handle));
}
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
} finally {
unset($handle);
}
$requestId = $responseHeaders['x-request-id'] ?? null;
$retryAfter = $responseHeaders['retry-after'] ?? null;
$data = null;
if ($raw !== '' && !in_array($status, [204, 205], true)) {
try {
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $error) {
if ($status >= 200 && $status < 300) {
throw new RuntimeException('Unexpected non-JSON success response.', 0, $error);
}
}
}
if ($status < 200 || $status >= 300) {
// Inspect $data['error']; queue 429 using $retryAfter, never create a new write key blindly.
throw new RuntimeException('HTTP ' . $status . '; request_id=' . ($requestId ?? '-') . '; retry_after=' . ($retryAfter ?? '-'));
}
// $data contains the decoded response; 202 means accepted, not completed.
echo 'HTTP ' . $status . PHP_EOL;js
// Node.js 22+; save as request.mjs. No third-party dependencies.
function requiredEnv(name) {
const value = process.env[name];
if (!value || /[\r\n]/u.test(value)) throw new Error('Set a valid environment variable: ' + name);
return value;
}
const baseUrl = (process.env.API_BASE_URL || "https://api.b.websoft.kz/api/reseller/v1").replace(/\/$/u, '');
let path = "/quotes";
const url = baseUrl + path;
const body = `{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "renew",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}`;
const response = await fetch(url, {
method: "POST",
redirect: 'manual',
signal: AbortSignal.timeout(20_000),
headers: {
Authorization: 'Bearer ' + requiredEnv('RESELLER_API_TOKEN'),
Accept: 'application/json',
'Idempotency-Key': requiredEnv('IDEMPOTENCY_KEY'),
"Content-Type": "application/json",
},
body,
});
const requestId = response.headers.get('x-request-id');
const retryAfter = response.headers.get('retry-after');
const raw = await response.text();
let data = null;
if (raw && ![204, 205].includes(response.status)) {
try { data = JSON.parse(raw); }
catch (cause) { if (response.ok) throw new Error('Unexpected non-JSON success response.', { cause }); }
}
if (!response.ok) {
// Inspect data?.error; schedule 429 using retryAfter. Do not generate another write key.
throw new Error('HTTP ' + response.status + '; request_id=' + requestId + '; retry_after=' + retryAfter);
}
// A timeout may have an unknown result. No automatic mutation retry in this example.
// data contains the response; 202 means accepted, not completed.
console.log('HTTP', response.status);python
# Python 3.10+; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import HTTPRedirectHandler, Request, build_opener
def required_env(name):
value = os.environ.get(name, "")
if not value or "\r" in value or "\n" in value:
raise RuntimeError("Set a valid environment variable: " + name)
return value
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
base_url = os.environ.get("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").rstrip("/")
path = "/quotes"
url = base_url + path
headers = {
"Authorization": "Bearer " + required_env("RESELLER_API_TOKEN"),
"Accept": "application/json",
"Idempotency-Key": required_env("IDEMPOTENCY_KEY"),
"Content-Type": "application/json",
}
body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"renew\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}".encode("utf-8")
request = Request(url, data=body, headers=headers, method="POST")
opener = build_opener(NoRedirect())
try:
response = opener.open(request, timeout=20)
except HTTPError as error:
response = error # HTTP failure still has a response body and headers.
# Network/timeout exceptions propagate: a write may already have been accepted.
with response:
status = response.status
request_id = response.headers.get("X-Request-ID")
retry_after = response.headers.get("Retry-After")
raw = response.read()
data = None
if raw and status not in (204, 205):
try:
data = json.loads(raw)
except (ValueError, UnicodeDecodeError):
if 200 <= status < 300:
raise RuntimeError("Unexpected non-JSON success response.")
if not 200 <= status < 300:
# Inspect data['error']; schedule 429 with retry_after, keeping the same persisted key.
raise RuntimeError(f"HTTP {status}; request_id={request_id}; retry_after={retry_after}")
# data contains the response. Do not log secrets/PII; 202 is not completion.
print("HTTP", status)go
// Go 1.22+; standard library only. Save as main.go and run: go run main.go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func requiredEnv(name string) string {
value := os.Getenv(name)
if value == "" || strings.ContainsAny(value, "\r\n") { panic("Set a valid environment variable: " + name) }
return value
}
func main() {
baseURL := os.Getenv("API_BASE_URL")
if baseURL == "" { baseURL = "https://api.b.websoft.kz/api/reseller/v1" }
path := "/quotes"
endpoint := strings.TrimRight(baseURL, "/") + path
request, err := http.NewRequest("POST", endpoint, strings.NewReader("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"renew\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
request.Header.Set("Accept", "application/json")
request.Header.Set("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: 20 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
}
response, err := client.Do(request)
if err != nil { panic("Transport failure; operation result may be unknown") }
defer response.Body.Close()
raw, err := io.ReadAll(response.Body)
if err != nil { panic("Response read failed; operation result may be unknown") }
requestID := response.Header.Get("X-Request-ID")
retryAfter := response.Header.Get("Retry-After")
var data any
if len(raw) > 0 && response.StatusCode != 204 && response.StatusCode != 205 {
decoder := json.NewDecoder(strings.NewReader(string(raw)))
decoder.UseNumber() // Preserve integer money/identifiers without float rounding.
if err := decoder.Decode(&data); err != nil && response.StatusCode >= 200 && response.StatusCode < 300 { panic("Unexpected non-JSON success response") }
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
// Inspect data; schedule 429 using retryAfter. Never create a new key blindly.
panic(fmt.Sprintf("HTTP %d; request_id=%s; retry_after=%s", response.StatusCode, requestID, retryAfter))
}
// data holds the JSON value; 202 means accepted, not completed. Do not log secrets.
fmt.Println("HTTP", response.StatusCode)
}java
// Java 17+; standard library only. Save as RequestExample.java.
// Run: java RequestExample.java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class RequestExample {
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty() || value.contains("\r") || value.contains("\n")) {
throw new IllegalStateException("Set a valid environment variable: " + name);
}
return value;
}
private static String envOr(String name, String fallback) {
String value = System.getenv(name);
return value == null || value.isEmpty() ? fallback : value;
}
public static void main(String[] args) throws Exception {
String baseUrl = envOr("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").replaceAll("/+$", "");
String path = "/quotes";
URI uri = URI.create(baseUrl + path);
String body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"renew\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}";
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(20))
.header("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
.header("Accept", "application/json")
.header("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
// Reuse HttpClient in production. A timeout may mean an unknown write result.
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
int status = response.statusCode();
String requestId = response.headers().firstValue("X-Request-ID").orElse("");
String retryAfter = response.headers().firstValue("Retry-After").orElse("");
String responseBody = (status == 204 || status == 205) ? "" : response.body();
if (status < 200 || status >= 300) {
// Decode responseBody with your JSON library; schedule 429 using retryAfter.
throw new IllegalStateException("HTTP " + status + "; request_id=" + requestId + "; retry_after=" + retryAfter);
}
// Java SE has no JSON object mapper: pass responseBody to your project's JSON parser.
// Do not parse an empty 204 body; 202 means accepted, not completed.
System.out.println("HTTP " + status);
}
}csharp
// C# / .NET 8+ console app; standard library only. Save as Program.cs.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
static string RequiredEnv(string name)
{
string? value = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(value) || value.Contains('\r') || value.Contains('\n'))
throw new InvalidOperationException("Set a valid environment variable: " + name);
return value;
}
string baseUrl = (Environment.GetEnvironmentVariable("API_BASE_URL") ?? "https://api.b.websoft.kz/api/reseller/v1").TrimEnd('/');
string path = "/quotes";
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(20) };
// Reuse HttpClient (or IHttpClientFactory) in production, not one client per request.
using var request = new HttpRequestMessage(new HttpMethod("POST"), baseUrl + path);
request.Headers.Add("Authorization", "Bearer " + RequiredEnv("RESELLER_API_TOKEN"));
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Idempotency-Key", RequiredEnv("IDEMPOTENCY_KEY"));
request.Content = new StringContent("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"renew\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}", Encoding.UTF8, "application/json");
// Transport exceptions may have an unknown result; do not issue a new mutation automatically.
using var response = await client.SendAsync(request);
int status = (int)response.StatusCode;
string requestId = response.Headers.TryGetValues("X-Request-ID", out var ids) ? string.Join(",", ids) : "";
string retryAfter = response.Headers.TryGetValues("Retry-After", out var delays) ? string.Join(",", delays) : "";
string raw = await response.Content.ReadAsStringAsync();
JsonElement? data = null;
if (raw.Length > 0 && status != 204 && status != 205)
{
try { using var document = JsonDocument.Parse(raw); data = document.RootElement.Clone(); }
catch (JsonException) { if (response.IsSuccessStatusCode) throw; }
}
if (!response.IsSuccessStatusCode)
{
// Inspect data; schedule 429 using retryAfter and preserve the same key/body.
throw new HttpRequestException($"HTTP {status}; request_id={requestId}; retry_after={retryAfter}");
}
// data contains JSON, or null for 204. HTTP 202 is not completion.
Console.WriteLine($"HTTP {status}");Восстановление
json
{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "restore",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}bash
API_BASE_URL='https://api.b.websoft.kz/api/reseller/v1'
: "${RESELLER_API_TOKEN:?Set RESELLER_API_TOKEN in your environment}"
: "${IDEMPOTENCY_KEY:?Set one persisted unique key per logical request}"
curl --request POST "$API_BASE_URL/quotes" \
--connect-timeout 5 --max-time 20 --fail-with-body \
--header "Authorization: Bearer $RESELLER_API_TOKEN" \
--header 'Accept: application/json' \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "restore",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}'php
<?php
declare(strict_types=1);
// PHP 8.0+; extensions: curl, json. Persist the key/body before any write.
function requiredEnv(string $name): string
{
$value = getenv($name);
if ($value === false || $value === '' || str_contains($value, "\r") || str_contains($value, "\n")) {
throw new RuntimeException('Set a valid environment variable: ' . $name);
}
return $value;
}
$baseUrl = rtrim(getenv('API_BASE_URL') ?: 'https://api.b.websoft.kz/api/reseller/v1', '/');
$path = '/quotes';
$url = $baseUrl . $path;
$headers = [
'Authorization: Bearer ' . requiredEnv('RESELLER_API_TOKEN'),
'Accept: application/json',
'Idempotency-Key: ' . requiredEnv('IDEMPOTENCY_KEY'),
'Content-Type: application/json',
];
$body = '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "restore",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}';
$responseHeaders = [];
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException('Cannot initialize cURL.');
}
try {
if (!curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 20,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HEADERFUNCTION => static function (CurlHandle $curl, string $line) use (&$responseHeaders): int {
if (str_starts_with($line, 'HTTP/')) {
$responseHeaders = [];
} elseif (str_contains($line, ':')) {
[$name, $value] = explode(':', $line, 2);
$responseHeaders[strtolower(trim($name))] = trim($value);
}
return strlen($line);
},
])) {
throw new RuntimeException('Cannot configure cURL.');
}
$raw = curl_exec($handle);
if ($raw === false) {
throw new RuntimeException('Transport failure; result may be unknown. cURL code: ' . curl_errno($handle));
}
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
} finally {
unset($handle);
}
$requestId = $responseHeaders['x-request-id'] ?? null;
$retryAfter = $responseHeaders['retry-after'] ?? null;
$data = null;
if ($raw !== '' && !in_array($status, [204, 205], true)) {
try {
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $error) {
if ($status >= 200 && $status < 300) {
throw new RuntimeException('Unexpected non-JSON success response.', 0, $error);
}
}
}
if ($status < 200 || $status >= 300) {
// Inspect $data['error']; queue 429 using $retryAfter, never create a new write key blindly.
throw new RuntimeException('HTTP ' . $status . '; request_id=' . ($requestId ?? '-') . '; retry_after=' . ($retryAfter ?? '-'));
}
// $data contains the decoded response; 202 means accepted, not completed.
echo 'HTTP ' . $status . PHP_EOL;js
// Node.js 22+; save as request.mjs. No third-party dependencies.
function requiredEnv(name) {
const value = process.env[name];
if (!value || /[\r\n]/u.test(value)) throw new Error('Set a valid environment variable: ' + name);
return value;
}
const baseUrl = (process.env.API_BASE_URL || "https://api.b.websoft.kz/api/reseller/v1").replace(/\/$/u, '');
let path = "/quotes";
const url = baseUrl + path;
const body = `{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "domain_zone",
"resource_uuid": "019a0000-0000-7000-8000-000000000006",
"operation": "restore",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_service_uuid": "019a0000-0000-7000-8000-000000000041"
}
}
]
}`;
const response = await fetch(url, {
method: "POST",
redirect: 'manual',
signal: AbortSignal.timeout(20_000),
headers: {
Authorization: 'Bearer ' + requiredEnv('RESELLER_API_TOKEN'),
Accept: 'application/json',
'Idempotency-Key': requiredEnv('IDEMPOTENCY_KEY'),
"Content-Type": "application/json",
},
body,
});
const requestId = response.headers.get('x-request-id');
const retryAfter = response.headers.get('retry-after');
const raw = await response.text();
let data = null;
if (raw && ![204, 205].includes(response.status)) {
try { data = JSON.parse(raw); }
catch (cause) { if (response.ok) throw new Error('Unexpected non-JSON success response.', { cause }); }
}
if (!response.ok) {
// Inspect data?.error; schedule 429 using retryAfter. Do not generate another write key.
throw new Error('HTTP ' + response.status + '; request_id=' + requestId + '; retry_after=' + retryAfter);
}
// A timeout may have an unknown result. No automatic mutation retry in this example.
// data contains the response; 202 means accepted, not completed.
console.log('HTTP', response.status);python
# Python 3.10+; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import HTTPRedirectHandler, Request, build_opener
def required_env(name):
value = os.environ.get(name, "")
if not value or "\r" in value or "\n" in value:
raise RuntimeError("Set a valid environment variable: " + name)
return value
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
base_url = os.environ.get("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").rstrip("/")
path = "/quotes"
url = base_url + path
headers = {
"Authorization": "Bearer " + required_env("RESELLER_API_TOKEN"),
"Accept": "application/json",
"Idempotency-Key": required_env("IDEMPOTENCY_KEY"),
"Content-Type": "application/json",
}
body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"restore\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}".encode("utf-8")
request = Request(url, data=body, headers=headers, method="POST")
opener = build_opener(NoRedirect())
try:
response = opener.open(request, timeout=20)
except HTTPError as error:
response = error # HTTP failure still has a response body and headers.
# Network/timeout exceptions propagate: a write may already have been accepted.
with response:
status = response.status
request_id = response.headers.get("X-Request-ID")
retry_after = response.headers.get("Retry-After")
raw = response.read()
data = None
if raw and status not in (204, 205):
try:
data = json.loads(raw)
except (ValueError, UnicodeDecodeError):
if 200 <= status < 300:
raise RuntimeError("Unexpected non-JSON success response.")
if not 200 <= status < 300:
# Inspect data['error']; schedule 429 with retry_after, keeping the same persisted key.
raise RuntimeError(f"HTTP {status}; request_id={request_id}; retry_after={retry_after}")
# data contains the response. Do not log secrets/PII; 202 is not completion.
print("HTTP", status)go
// Go 1.22+; standard library only. Save as main.go and run: go run main.go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func requiredEnv(name string) string {
value := os.Getenv(name)
if value == "" || strings.ContainsAny(value, "\r\n") { panic("Set a valid environment variable: " + name) }
return value
}
func main() {
baseURL := os.Getenv("API_BASE_URL")
if baseURL == "" { baseURL = "https://api.b.websoft.kz/api/reseller/v1" }
path := "/quotes"
endpoint := strings.TrimRight(baseURL, "/") + path
request, err := http.NewRequest("POST", endpoint, strings.NewReader("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"restore\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
request.Header.Set("Accept", "application/json")
request.Header.Set("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: 20 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
}
response, err := client.Do(request)
if err != nil { panic("Transport failure; operation result may be unknown") }
defer response.Body.Close()
raw, err := io.ReadAll(response.Body)
if err != nil { panic("Response read failed; operation result may be unknown") }
requestID := response.Header.Get("X-Request-ID")
retryAfter := response.Header.Get("Retry-After")
var data any
if len(raw) > 0 && response.StatusCode != 204 && response.StatusCode != 205 {
decoder := json.NewDecoder(strings.NewReader(string(raw)))
decoder.UseNumber() // Preserve integer money/identifiers without float rounding.
if err := decoder.Decode(&data); err != nil && response.StatusCode >= 200 && response.StatusCode < 300 { panic("Unexpected non-JSON success response") }
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
// Inspect data; schedule 429 using retryAfter. Never create a new key blindly.
panic(fmt.Sprintf("HTTP %d; request_id=%s; retry_after=%s", response.StatusCode, requestID, retryAfter))
}
// data holds the JSON value; 202 means accepted, not completed. Do not log secrets.
fmt.Println("HTTP", response.StatusCode)
}java
// Java 17+; standard library only. Save as RequestExample.java.
// Run: java RequestExample.java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class RequestExample {
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty() || value.contains("\r") || value.contains("\n")) {
throw new IllegalStateException("Set a valid environment variable: " + name);
}
return value;
}
private static String envOr(String name, String fallback) {
String value = System.getenv(name);
return value == null || value.isEmpty() ? fallback : value;
}
public static void main(String[] args) throws Exception {
String baseUrl = envOr("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").replaceAll("/+$", "");
String path = "/quotes";
URI uri = URI.create(baseUrl + path);
String body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"restore\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}";
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(20))
.header("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
.header("Accept", "application/json")
.header("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
// Reuse HttpClient in production. A timeout may mean an unknown write result.
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
int status = response.statusCode();
String requestId = response.headers().firstValue("X-Request-ID").orElse("");
String retryAfter = response.headers().firstValue("Retry-After").orElse("");
String responseBody = (status == 204 || status == 205) ? "" : response.body();
if (status < 200 || status >= 300) {
// Decode responseBody with your JSON library; schedule 429 using retryAfter.
throw new IllegalStateException("HTTP " + status + "; request_id=" + requestId + "; retry_after=" + retryAfter);
}
// Java SE has no JSON object mapper: pass responseBody to your project's JSON parser.
// Do not parse an empty 204 body; 202 means accepted, not completed.
System.out.println("HTTP " + status);
}
}csharp
// C# / .NET 8+ console app; standard library only. Save as Program.cs.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
static string RequiredEnv(string name)
{
string? value = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(value) || value.Contains('\r') || value.Contains('\n'))
throw new InvalidOperationException("Set a valid environment variable: " + name);
return value;
}
string baseUrl = (Environment.GetEnvironmentVariable("API_BASE_URL") ?? "https://api.b.websoft.kz/api/reseller/v1").TrimEnd('/');
string path = "/quotes";
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(20) };
// Reuse HttpClient (or IHttpClientFactory) in production, not one client per request.
using var request = new HttpRequestMessage(new HttpMethod("POST"), baseUrl + path);
request.Headers.Add("Authorization", "Bearer " + RequiredEnv("RESELLER_API_TOKEN"));
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Idempotency-Key", RequiredEnv("IDEMPOTENCY_KEY"));
request.Content = new StringContent("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"domain_zone\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000006\",\n \"operation\": \"restore\",\n \"period_unit\": \"year\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_service_uuid\": \"019a0000-0000-7000-8000-000000000041\"\n }\n }\n ]\n}", Encoding.UTF8, "application/json");
// Transport exceptions may have an unknown result; do not issue a new mutation automatically.
using var response = await client.SendAsync(request);
int status = (int)response.StatusCode;
string requestId = response.Headers.TryGetValues("X-Request-ID", out var ids) ? string.Join(",", ids) : "";
string retryAfter = response.Headers.TryGetValues("Retry-After", out var delays) ? string.Join(",", delays) : "";
string raw = await response.Content.ReadAsStringAsync();
JsonElement? data = null;
if (raw.Length > 0 && status != 204 && status != 205)
{
try { using var document = JsonDocument.Parse(raw); data = document.RootElement.Clone(); }
catch (JsonException) { if (response.IsSuccessStatusCode) throw; }
}
if (!response.IsSuccessStatusCode)
{
// Inspect data; schedule 429 using retryAfter and preserve the same key/body.
throw new HttpRequestException($"HTTP {status}; request_id={requestId}; retry_after={retryAfter}");
}
// data contains JSON, or null for 204. HTTP 202 is not completion.
Console.WriteLine($"HTTP {status}");Заказ хостинга (только live при исполнении)
json
{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "product",
"resource_uuid": "019a0000-0000-7000-8000-000000000004",
"operation": "order",
"period_unit": "month",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "www.example.com",
"external_id": "hosting-3043",
"notes": "Основной сайт"
}
}
]
}bash
API_BASE_URL='https://api.b.websoft.kz/api/reseller/v1'
: "${RESELLER_API_TOKEN:?Set RESELLER_API_TOKEN in your environment}"
: "${IDEMPOTENCY_KEY:?Set one persisted unique key per logical request}"
curl --request POST "$API_BASE_URL/quotes" \
--connect-timeout 5 --max-time 20 --fail-with-body \
--header "Authorization: Bearer $RESELLER_API_TOKEN" \
--header 'Accept: application/json' \
--header "Idempotency-Key: $IDEMPOTENCY_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "product",
"resource_uuid": "019a0000-0000-7000-8000-000000000004",
"operation": "order",
"period_unit": "month",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "www.example.com",
"external_id": "hosting-3043",
"notes": "Основной сайт"
}
}
]
}'php
<?php
declare(strict_types=1);
// PHP 8.0+; extensions: curl, json. Persist the key/body before any write.
function requiredEnv(string $name): string
{
$value = getenv($name);
if ($value === false || $value === '' || str_contains($value, "\r") || str_contains($value, "\n")) {
throw new RuntimeException('Set a valid environment variable: ' . $name);
}
return $value;
}
$baseUrl = rtrim(getenv('API_BASE_URL') ?: 'https://api.b.websoft.kz/api/reseller/v1', '/');
$path = '/quotes';
$url = $baseUrl . $path;
$headers = [
'Authorization: Bearer ' . requiredEnv('RESELLER_API_TOKEN'),
'Accept: application/json',
'Idempotency-Key: ' . requiredEnv('IDEMPOTENCY_KEY'),
'Content-Type: application/json',
];
$body = '{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "product",
"resource_uuid": "019a0000-0000-7000-8000-000000000004",
"operation": "order",
"period_unit": "month",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "www.example.com",
"external_id": "hosting-3043",
"notes": "Основной сайт"
}
}
]
}';
$responseHeaders = [];
$handle = curl_init($url);
if ($handle === false) {
throw new RuntimeException('Cannot initialize cURL.');
}
try {
if (!curl_setopt_array($handle, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 20,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HEADERFUNCTION => static function (CurlHandle $curl, string $line) use (&$responseHeaders): int {
if (str_starts_with($line, 'HTTP/')) {
$responseHeaders = [];
} elseif (str_contains($line, ':')) {
[$name, $value] = explode(':', $line, 2);
$responseHeaders[strtolower(trim($name))] = trim($value);
}
return strlen($line);
},
])) {
throw new RuntimeException('Cannot configure cURL.');
}
$raw = curl_exec($handle);
if ($raw === false) {
throw new RuntimeException('Transport failure; result may be unknown. cURL code: ' . curl_errno($handle));
}
$status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
} finally {
unset($handle);
}
$requestId = $responseHeaders['x-request-id'] ?? null;
$retryAfter = $responseHeaders['retry-after'] ?? null;
$data = null;
if ($raw !== '' && !in_array($status, [204, 205], true)) {
try {
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $error) {
if ($status >= 200 && $status < 300) {
throw new RuntimeException('Unexpected non-JSON success response.', 0, $error);
}
}
}
if ($status < 200 || $status >= 300) {
// Inspect $data['error']; queue 429 using $retryAfter, never create a new write key blindly.
throw new RuntimeException('HTTP ' . $status . '; request_id=' . ($requestId ?? '-') . '; retry_after=' . ($retryAfter ?? '-'));
}
// $data contains the decoded response; 202 means accepted, not completed.
echo 'HTTP ' . $status . PHP_EOL;js
// Node.js 22+; save as request.mjs. No third-party dependencies.
function requiredEnv(name) {
const value = process.env[name];
if (!value || /[\r\n]/u.test(value)) throw new Error('Set a valid environment variable: ' + name);
return value;
}
const baseUrl = (process.env.API_BASE_URL || "https://api.b.websoft.kz/api/reseller/v1").replace(/\/$/u, '');
let path = "/quotes";
const url = baseUrl + path;
const body = `{
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"items": [
{
"resource_type": "product",
"resource_uuid": "019a0000-0000-7000-8000-000000000004",
"operation": "order",
"period_unit": "month",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"payload": {
"domain_name": "www.example.com",
"external_id": "hosting-3043",
"notes": "Основной сайт"
}
}
]
}`;
const response = await fetch(url, {
method: "POST",
redirect: 'manual',
signal: AbortSignal.timeout(20_000),
headers: {
Authorization: 'Bearer ' + requiredEnv('RESELLER_API_TOKEN'),
Accept: 'application/json',
'Idempotency-Key': requiredEnv('IDEMPOTENCY_KEY'),
"Content-Type": "application/json",
},
body,
});
const requestId = response.headers.get('x-request-id');
const retryAfter = response.headers.get('retry-after');
const raw = await response.text();
let data = null;
if (raw && ![204, 205].includes(response.status)) {
try { data = JSON.parse(raw); }
catch (cause) { if (response.ok) throw new Error('Unexpected non-JSON success response.', { cause }); }
}
if (!response.ok) {
// Inspect data?.error; schedule 429 using retryAfter. Do not generate another write key.
throw new Error('HTTP ' + response.status + '; request_id=' + requestId + '; retry_after=' + retryAfter);
}
// A timeout may have an unknown result. No automatic mutation retry in this example.
// data contains the response; 202 means accepted, not completed.
console.log('HTTP', response.status);python
# Python 3.10+; standard library only.
import json
import os
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import HTTPRedirectHandler, Request, build_opener
def required_env(name):
value = os.environ.get(name, "")
if not value or "\r" in value or "\n" in value:
raise RuntimeError("Set a valid environment variable: " + name)
return value
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
base_url = os.environ.get("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").rstrip("/")
path = "/quotes"
url = base_url + path
headers = {
"Authorization": "Bearer " + required_env("RESELLER_API_TOKEN"),
"Accept": "application/json",
"Idempotency-Key": required_env("IDEMPOTENCY_KEY"),
"Content-Type": "application/json",
}
body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"product\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000004\",\n \"operation\": \"order\",\n \"period_unit\": \"month\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"www.example.com\",\n \"external_id\": \"hosting-3043\",\n \"notes\": \"Основной сайт\"\n }\n }\n ]\n}".encode("utf-8")
request = Request(url, data=body, headers=headers, method="POST")
opener = build_opener(NoRedirect())
try:
response = opener.open(request, timeout=20)
except HTTPError as error:
response = error # HTTP failure still has a response body and headers.
# Network/timeout exceptions propagate: a write may already have been accepted.
with response:
status = response.status
request_id = response.headers.get("X-Request-ID")
retry_after = response.headers.get("Retry-After")
raw = response.read()
data = None
if raw and status not in (204, 205):
try:
data = json.loads(raw)
except (ValueError, UnicodeDecodeError):
if 200 <= status < 300:
raise RuntimeError("Unexpected non-JSON success response.")
if not 200 <= status < 300:
# Inspect data['error']; schedule 429 with retry_after, keeping the same persisted key.
raise RuntimeError(f"HTTP {status}; request_id={request_id}; retry_after={retry_after}")
# data contains the response. Do not log secrets/PII; 202 is not completion.
print("HTTP", status)go
// Go 1.22+; standard library only. Save as main.go and run: go run main.go
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
)
func requiredEnv(name string) string {
value := os.Getenv(name)
if value == "" || strings.ContainsAny(value, "\r\n") { panic("Set a valid environment variable: " + name) }
return value
}
func main() {
baseURL := os.Getenv("API_BASE_URL")
if baseURL == "" { baseURL = "https://api.b.websoft.kz/api/reseller/v1" }
path := "/quotes"
endpoint := strings.TrimRight(baseURL, "/") + path
request, err := http.NewRequest("POST", endpoint, strings.NewReader("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"product\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000004\",\n \"operation\": \"order\",\n \"period_unit\": \"month\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"www.example.com\",\n \"external_id\": \"hosting-3043\",\n \"notes\": \"Основной сайт\"\n }\n }\n ]\n}"))
if err != nil { panic(err) }
request.Header.Set("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
request.Header.Set("Accept", "application/json")
request.Header.Set("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
request.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: 20 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },
}
response, err := client.Do(request)
if err != nil { panic("Transport failure; operation result may be unknown") }
defer response.Body.Close()
raw, err := io.ReadAll(response.Body)
if err != nil { panic("Response read failed; operation result may be unknown") }
requestID := response.Header.Get("X-Request-ID")
retryAfter := response.Header.Get("Retry-After")
var data any
if len(raw) > 0 && response.StatusCode != 204 && response.StatusCode != 205 {
decoder := json.NewDecoder(strings.NewReader(string(raw)))
decoder.UseNumber() // Preserve integer money/identifiers without float rounding.
if err := decoder.Decode(&data); err != nil && response.StatusCode >= 200 && response.StatusCode < 300 { panic("Unexpected non-JSON success response") }
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
// Inspect data; schedule 429 using retryAfter. Never create a new key blindly.
panic(fmt.Sprintf("HTTP %d; request_id=%s; retry_after=%s", response.StatusCode, requestID, retryAfter))
}
// data holds the JSON value; 202 means accepted, not completed. Do not log secrets.
fmt.Println("HTTP", response.StatusCode)
}java
// Java 17+; standard library only. Save as RequestExample.java.
// Run: java RequestExample.java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
public class RequestExample {
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isEmpty() || value.contains("\r") || value.contains("\n")) {
throw new IllegalStateException("Set a valid environment variable: " + name);
}
return value;
}
private static String envOr(String name, String fallback) {
String value = System.getenv(name);
return value == null || value.isEmpty() ? fallback : value;
}
public static void main(String[] args) throws Exception {
String baseUrl = envOr("API_BASE_URL", "https://api.b.websoft.kz/api/reseller/v1").replaceAll("/+$", "");
String path = "/quotes";
URI uri = URI.create(baseUrl + path);
String body = "{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"product\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000004\",\n \"operation\": \"order\",\n \"period_unit\": \"month\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"www.example.com\",\n \"external_id\": \"hosting-3043\",\n \"notes\": \"Основной сайт\"\n }\n }\n ]\n}";
HttpRequest request = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(20))
.header("Authorization", "Bearer " + requiredEnv("RESELLER_API_TOKEN"))
.header("Accept", "application/json")
.header("Idempotency-Key", requiredEnv("IDEMPOTENCY_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
// Reuse HttpClient in production. A timeout may mean an unknown write result.
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
int status = response.statusCode();
String requestId = response.headers().firstValue("X-Request-ID").orElse("");
String retryAfter = response.headers().firstValue("Retry-After").orElse("");
String responseBody = (status == 204 || status == 205) ? "" : response.body();
if (status < 200 || status >= 300) {
// Decode responseBody with your JSON library; schedule 429 using retryAfter.
throw new IllegalStateException("HTTP " + status + "; request_id=" + requestId + "; retry_after=" + retryAfter);
}
// Java SE has no JSON object mapper: pass responseBody to your project's JSON parser.
// Do not parse an empty 204 body; 202 means accepted, not completed.
System.out.println("HTTP " + status);
}
}csharp
// C# / .NET 8+ console app; standard library only. Save as Program.cs.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
static string RequiredEnv(string name)
{
string? value = Environment.GetEnvironmentVariable(name);
if (string.IsNullOrEmpty(value) || value.Contains('\r') || value.Contains('\n'))
throw new InvalidOperationException("Set a valid environment variable: " + name);
return value;
}
string baseUrl = (Environment.GetEnvironmentVariable("API_BASE_URL") ?? "https://api.b.websoft.kz/api/reseller/v1").TrimEnd('/');
string path = "/quotes";
using var handler = new HttpClientHandler { AllowAutoRedirect = false };
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(20) };
// Reuse HttpClient (or IHttpClientFactory) in production, not one client per request.
using var request = new HttpRequestMessage(new HttpMethod("POST"), baseUrl + path);
request.Headers.Add("Authorization", "Bearer " + RequiredEnv("RESELLER_API_TOKEN"));
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Idempotency-Key", RequiredEnv("IDEMPOTENCY_KEY"));
request.Content = new StringContent("{\n \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n \"items\": [\n {\n \"resource_type\": \"product\",\n \"resource_uuid\": \"019a0000-0000-7000-8000-000000000004\",\n \"operation\": \"order\",\n \"period_unit\": \"month\",\n \"period_count\": 1,\n \"quantity\": 1,\n \"currency\": \"KZT\",\n \"payload\": {\n \"domain_name\": \"www.example.com\",\n \"external_id\": \"hosting-3043\",\n \"notes\": \"Основной сайт\"\n }\n }\n ]\n}", Encoding.UTF8, "application/json");
// Transport exceptions may have an unknown result; do not issue a new mutation automatically.
using var response = await client.SendAsync(request);
int status = (int)response.StatusCode;
string requestId = response.Headers.TryGetValues("X-Request-ID", out var ids) ? string.Join(",", ids) : "";
string retryAfter = response.Headers.TryGetValues("Retry-After", out var delays) ? string.Join(",", delays) : "";
string raw = await response.Content.ReadAsStringAsync();
JsonElement? data = null;
if (raw.Length > 0 && status != 204 && status != 205)
{
try { using var document = JsonDocument.Parse(raw); data = document.RootElement.Clone(); }
catch (JsonException) { if (response.IsSuccessStatusCode) throw; }
}
if (!response.IsSuccessStatusCode)
{
// Inspect data; schedule 429 using retryAfter and preserve the same key/body.
throw new HttpRequestException($"HTTP {status}; request_id={requestId}; retry_after={retryAfter}");
}
// data contains JSON, or null for 204. HTTP 202 is not completion.
Console.WriteLine($"HTTP {status}");Ответы
Условия ошибок и восстановление
| HTTP | Код | Когда возникает | Что делать |
|---|---|---|---|
| 401 | unauthenticated | Bearer-ключ отсутствует, недействителен, отозван или истёк. | Проверьте ключ и его срок, не повторяйте с теми же неверными credentials. |
| 403 | forbidden | Scope, IP allowlist, режим или состояние реселлера не разрешает запрос. | Проверьте scopes, среду, allowlist и настройки реселлера через администратора. |
| 404 | resource_not_found | Модуль pricing отключён либо ресурс/домен восстановления недоступен. | Проверьте UUID, среду и принадлежность; при отключённой функции обратитесь к оператору. |
| 409 | conflict | Idempotency-Key уже занят; либо домен восстановления использует неактивную интеграцию или имеет migration_hold. | Для того же запроса дождитесь завершения и повторите неизменённый запрос с тем же ключом; изменённый запрос требует нового ключа после сверки результата. |
| 422 | validation_failed | Нет активного тарифного назначения, опубликованного предложения, цены нужного периода/операции/валюты; ограничения тарифа; неверный customer_uuid; домен вне периода восстановления. PriceUnavailableException оборачивается как validation_failed, а не отдельный error.code=price_unavailable. | Исправьте поля из error.details/условия. Не повторяйте неизменённый невалидный запрос. |
| 429 | rate_limit_exceeded | Лимит текущего API-ключа исчерпан. | Учитывайте Retry-After при наличии, используйте backoff с jitter. Запись повторяйте с прежним Idempotency-Key и неизменённым телом. |
| 500 | internal_error | Необработанная ошибка выполнения; это не подтверждение отсутствия побочного эффекта. | Сохраните request_id; сначала проверьте состояние объекта, затем повторяйте запись с тем же ключом. Для неоднозначного результата обращайтесь в поддержку. |
HTTP 201
Успешный ответ.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Заголовок X-RateLimit-Limit: Лимит API-ключа на 60 секунд. После прохождения ограничителя, включая replay. Отсутствует в собственной ветке 429 и при раннем отказе.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | integer | Да | minimum: 1 |
text
X-RateLimit-Limit: 120Заголовок X-RateLimit-Remaining: Остаток после запроса. Снимок, не гарантия при конкуренции. Отсутствует в собственной ветке 429 и при раннем отказе.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | integer | Да | minimum: 0 |
text
X-RateLimit-Remaining: 119Заголовок Idempotency-Replayed: Только при возврате сохранённого HTTP-ответа: true, не false. Отсутствие не доказывает, что доменная операция ранее не выполнялась.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | const: "true" |
Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["data"] | Успешный ответ. Схема: CoreQuoteEnvelope |
$.data | object | Да | required: ["uuid","customer_uuid","status","environment","currency","issued_at","expires_at","items","total_minor"] | Выданная котировка. Нет GET /quotes/{uuid}; сохраняйте этот ответ. Схема: CoreQuote |
$.data.uuid | string | Да | format: "uuid" | UUID объекта. |
$.data.customer_uuid | string | Да | format: "uuid" | Покупатель. |
$.data.status | string | Да | При создании active. Позже котировка может стать consumed/expired; отдельного GET нет. | |
$.data.environment | string | Да | enum: "test", "live" | Среда. |
$.data.currency | string | Да | Валюта. | |
$.data.issued_at | string | Да | format: "date-time" | Выдана. |
$.data.expires_at | string | Да | format: "date-time" | Истекает. |
$.data.items | array | Да | Строки. | |
$.data.items[] | object | Да | required: ["uuid","resource_type","operation","period_unit","period_count","quantity","currency","unit_price_minor","subtotal_minor","discount_minor","tax_minor","total_minor"] | Розничные суммы строки. Закупочная цена, себестоимость и resource_uuid не возвращаются. Схема: CoreQuoteItem |
$.data.items[].uuid | string | Да | format: "uuid" | UUID строки котировки. |
$.data.items[].resource_type | string | Да | Тип ресурса. | |
$.data.items[].operation | string | Да | Операция. | |
$.data.items[].period_unit | string | Да | Единица периода. | |
$.data.items[].period_count | integer | Да | format: "int64" | Количество периодов. |
$.data.items[].quantity | integer | Да | format: "int64" | Количество. |
$.data.items[].currency | string | Да | Валюта. | |
$.data.items[].unit_price_minor | integer | Да | format: "int64" | Цена единицы. В минимальных единицах валюты, без десятичных дробей. |
$.data.items[].subtotal_minor | integer | Да | format: "int64" | Подытог. В минимальных единицах валюты, без десятичных дробей. |
$.data.items[].discount_minor | integer | Да | format: "int64" | Скидка. В минимальных единицах валюты, без десятичных дробей. |
$.data.items[].tax_minor | integer | Да | format: "int64" | Налог. В минимальных единицах валюты, без десятичных дробей. |
$.data.items[].total_minor | integer | Да | format: "int64" | Итого строки. В минимальных единицах валюты, без десятичных дробей. |
$.data.total_minor | integer | Да | format: "int64" | Сумма total_minor всех строк. В минимальных единицах валюты, без десятичных дробей. |
Регистрация на год (201)
json
{
"data": {
"uuid": "019a0000-0000-7000-8000-000000000020",
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"status": "active",
"environment": "live",
"currency": "KZT",
"issued_at": "2026-10-05T09:00:00+00:00",
"expires_at": "2026-10-05T09:15:00+00:00",
"items": [
{
"uuid": "019a0000-0000-7000-8000-000000000021",
"resource_type": "domain_zone",
"operation": "register",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"unit_price_minor": 700000,
"subtotal_minor": 700000,
"discount_minor": 0,
"tax_minor": 0,
"total_minor": 700000
}
],
"total_minor": 700000
}
}Продление на год (201)
json
{
"data": {
"uuid": "019a0000-0000-7000-8000-000000000020",
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"status": "active",
"environment": "live",
"currency": "KZT",
"issued_at": "2026-10-05T09:00:00+00:00",
"expires_at": "2026-10-05T09:15:00+00:00",
"items": [
{
"uuid": "019a0000-0000-7000-8000-000000000021",
"resource_type": "domain_zone",
"operation": "renew",
"period_unit": "year",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"unit_price_minor": 700000,
"subtotal_minor": 700000,
"discount_minor": 0,
"tax_minor": 0,
"total_minor": 700000
}
],
"total_minor": 700000
}
}Хостинг на месяц (201)
json
{
"data": {
"uuid": "019a0000-0000-7000-8000-000000000020",
"customer_uuid": "019a0000-0000-7000-8000-000000000010",
"status": "active",
"environment": "live",
"currency": "KZT",
"issued_at": "2026-10-05T09:00:00+00:00",
"expires_at": "2026-10-05T09:15:00+00:00",
"items": [
{
"uuid": "019a0000-0000-7000-8000-000000000021",
"resource_type": "product",
"operation": "order",
"period_unit": "month",
"period_count": 1,
"quantity": 1,
"currency": "KZT",
"unit_price_minor": 700000,
"subtotal_minor": 700000,
"discount_minor": 0,
"tax_minor": 0,
"total_minor": 700000
}
],
"total_minor": 700000
}
}HTTP 401
Bearer-ключ отсутствует, недействителен, отозван или истёк.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["error"] | Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope |
$.error | object | Да | required: ["code","message","details","request_id"] | |
$.error.code | string | Да | Машинный код; не извлекайте причину из message. | |
$.error.message | string | Да | Человекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки. | |
$.error.details | oneOf | Да | Без подробностей возвращается [], не null и не {}. Валидация: {поле:[сообщения]}. Квота: {retry_after:секунды}. | |
$.error.details (oneOf 1) | array | Да | maxItems: 0 | |
$.error.details (oneOf 2) | object | Да | ||
$.error.details (oneOf 2).* | array | Нет | ||
$.error.details (oneOf 2).*[] | string | Да | ||
$.error.details (oneOf 3) | object | Да | required: ["retry_after"]; Дополнительные поля запрещены | |
$.error.details (oneOf 3).retry_after | integer | Да | minimum: 0 | |
$.error.request_id | string / null | Да | format: "uuid" | UUID для диагностики или null без контекста. При HTTP replay тело может содержать исходный ID, заголовок X-Request-ID относится к текущей попытке. |
default (401)
json
{
"error": {
"code": "unauthenticated",
"message": "API credential is missing or invalid.",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}HTTP 403
Scope, IP allowlist, режим или состояние реселлера не разрешает запрос.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["error"] | Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope |
$.error | object | Да | required: ["code","message","details","request_id"] | |
$.error.code | string | Да | Машинный код; не извлекайте причину из message. | |
$.error.message | string | Да | Человекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки. | |
$.error.details | oneOf | Да | Без подробностей возвращается [], не null и не {}. Валидация: {поле:[сообщения]}. Квота: {retry_after:секунды}. | |
$.error.details (oneOf 1) | array | Да | maxItems: 0 | |
$.error.details (oneOf 2) | object | Да | ||
$.error.details (oneOf 2).* | array | Нет | ||
$.error.details (oneOf 2).*[] | string | Да | ||
$.error.details (oneOf 3) | object | Да | required: ["retry_after"]; Дополнительные поля запрещены | |
$.error.details (oneOf 3).retry_after | integer | Да | minimum: 0 | |
$.error.request_id | string / null | Да | format: "uuid" | UUID для диагностики или null без контекста. При HTTP replay тело может содержать исходный ID, заголовок X-Request-ID относится к текущей попытке. |
default (403)
json
{
"error": {
"code": "forbidden",
"message": "Source IP address is not allowed.",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}supportTest (403)
json
{
"error": {
"code": "forbidden",
"message": "Support is available only to live credentials.",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}HTTP 404
Модуль pricing отключён либо ресурс/домен восстановления недоступен.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["error"] | Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope |
$.error | object | Да | required: ["code","message","details","request_id"] | |
$.error.code | string | Да | Машинный код; не извлекайте причину из message. | |
$.error.message | string | Да | Человекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки. | |
$.error.details | oneOf | Да | Без подробностей возвращается [], не null и не {}. Валидация: {поле:[сообщения]}. Квота: {retry_after:секунды}. | |
$.error.details (oneOf 1) | array | Да | maxItems: 0 | |
$.error.details (oneOf 2) | object | Да | ||
$.error.details (oneOf 2).* | array | Нет | ||
$.error.details (oneOf 2).*[] | string | Да | ||
$.error.details (oneOf 3) | object | Да | required: ["retry_after"]; Дополнительные поля запрещены | |
$.error.details (oneOf 3).retry_after | integer | Да | minimum: 0 | |
$.error.request_id | string / null | Да | format: "uuid" | UUID для диагностики или null без контекста. При HTTP replay тело может содержать исходный ID, заголовок X-Request-ID относится к текущей попытке. |
default (404)
json
{
"error": {
"code": "resource_not_found",
"message": "Not Found",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}HTTP 409
Конфликт Idempotency-Key; для restore также неактивная интеграция домена или блокировка migration_hold.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["error"] | Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope |
$.error | object | Да | required: ["code","message","details","request_id"] | |
$.error.code | string | Да | Машинный код; не извлекайте причину из message. | |
$.error.message | string | Да | Человекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки. | |
$.error.details | oneOf | Да | Без подробностей возвращается [], не null и не {}. Валидация: {поле:[сообщения]}. Квота: {retry_after:секунды}. | |
$.error.details (oneOf 1) | array | Да | maxItems: 0 | |
$.error.details (oneOf 2) | object | Да | ||
$.error.details (oneOf 2).* | array | Нет | ||
$.error.details (oneOf 2).*[] | string | Да | ||
$.error.details (oneOf 3) | object | Да | required: ["retry_after"]; Дополнительные поля запрещены | |
$.error.details (oneOf 3).retry_after | integer | Да | minimum: 0 | |
$.error.request_id | string / null | Да | format: "uuid" | UUID для диагностики или null без контекста. При HTTP replay тело может содержать исходный ID, заголовок X-Request-ID относится к текущей попытке. |
default (409)
json
{
"error": {
"code": "conflict",
"message": "A request with this Idempotency-Key is already processing.",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}changedRequest (409)
json
{
"error": {
"code": "conflict",
"message": "Idempotency-Key was already used with a different request.",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}HTTP 422
Нет активного тарифного назначения, опубликованного предложения, цены нужного периода/операции/валюты; ограничения тарифа; неверный customer_uuid; домен вне периода восстановления. PriceUnavailableException оборачивается как validation_failed, а не отдельный error.code=price_unavailable.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["error"] | Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope |
$.error | object | Да | required: ["code","message","details","request_id"] | |
$.error.code | string | Да | Машинный код; не извлекайте причину из message. | |
$.error.message | string | Да | Человекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки. | |
$.error.details | oneOf | Да | Без подробностей возвращается [], не null и не {}. Валидация: {поле:[сообщения]}. Квота: {retry_after:секунды}. | |
$.error.details (oneOf 1) | array | Да | maxItems: 0 | |
$.error.details (oneOf 2) | object | Да | ||
$.error.details (oneOf 2).* | array | Нет | ||
$.error.details (oneOf 2).*[] | string | Да | ||
$.error.details (oneOf 3) | object | Да | required: ["retry_after"]; Дополнительные поля запрещены | |
$.error.details (oneOf 3).retry_after | integer | Да | minimum: 0 | |
$.error.request_id | string / null | Да | format: "uuid" | UUID для диагностики или null без контекста. При HTTP replay тело может содержать исходный ID, заголовок X-Request-ID относится к текущей попытке. |
default (422)
json
{
"error": {
"code": "validation_failed",
"message": "A valid Idempotency-Key header is required.",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}fields (422)
json
{
"error": {
"code": "validation_failed",
"message": "The name field is required.",
"details": {
"name": [
"The name field is required."
]
},
"request_id": "11111111-1111-4111-8111-111111111111"
}
}HTTP 429
Превышен общий лимит текущего API-ключа по всем методам (в том числе replay). Квота отдельных EPP-команд и фоновых задач не равна этому HTTP-лимиту.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Заголовок Retry-After: На 429 API: целые секунды до сброса окна, не дата/Unix timestamp. При 0 добавьте jitter. Proxy может вернуть HTTP-date.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | pattern: "^[0-9]+$" |
text
Retry-After: 42Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["error"] | Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope |
$.error | object | Да | required: ["code","message","details","request_id"] | |
$.error.code | string | Да | Машинный код; не извлекайте причину из message. | |
$.error.message | string | Да | Человекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки. | |
$.error.details | oneOf | Да | Без подробностей возвращается [], не null и не {}. Валидация: {поле:[сообщения]}. Квота: {retry_after:секунды}. | |
$.error.details (oneOf 1) | array | Да | maxItems: 0 | |
$.error.details (oneOf 2) | object | Да | ||
$.error.details (oneOf 2).* | array | Нет | ||
$.error.details (oneOf 2).*[] | string | Да | ||
$.error.details (oneOf 3) | object | Да | required: ["retry_after"]; Дополнительные поля запрещены | |
$.error.details (oneOf 3).retry_after | integer | Да | minimum: 0 | |
$.error.request_id | string / null | Да | format: "uuid" | UUID для диагностики или null без контекста. При HTTP replay тело может содержать исходный ID, заголовок X-Request-ID относится к текущей попытке. |
default (429)
json
{
"error": {
"code": "rate_limit_exceeded",
"message": "API rate limit exceeded.",
"details": {
"retry_after": 42
},
"request_id": "11111111-1111-4111-8111-111111111111"
}
}HTTP 500
Необработанная ошибка выполнения; это не подтверждение отсутствия побочного эффекта.
Заголовок X-Request-ID: UUID текущего запроса. Корректный входной X-Request-ID принимается в нижнем регистре, иначе создаётся новый. Не гарантирован для proxy/неизвестного маршрута.
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | string | Да | format: "uuid" |
Content-Type: application/json
| Поле | Тип | Обязательно в родителе | Ограничения | Описание |
|---|---|---|---|---|
$ | object | Да | required: ["error"] | Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope |
$.error | object | Да | required: ["code","message","details","request_id"] | |
$.error.code | string | Да | Машинный код; не извлекайте причину из message. | |
$.error.message | string | Да | Человекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки. | |
$.error.details | oneOf | Да | Без подробностей возвращается [], не null и не {}. Валидация: {поле:[сообщения]}. Квота: {retry_after:секунды}. | |
$.error.details (oneOf 1) | array | Да | maxItems: 0 | |
$.error.details (oneOf 2) | object | Да | ||
$.error.details (oneOf 2).* | array | Нет | ||
$.error.details (oneOf 2).*[] | string | Да | ||
$.error.details (oneOf 3) | object | Да | required: ["retry_after"]; Дополнительные поля запрещены | |
$.error.details (oneOf 3).retry_after | integer | Да | minimum: 0 | |
$.error.request_id | string / null | Да | format: "uuid" | UUID для диагностики или null без контекста. При HTTP replay тело может содержать исходный ID, заголовок X-Request-ID относится к текущей попытке. |
default (500)
json
{
"error": {
"code": "internal_error",
"message": "The request could not be completed.",
"details": [],
"request_id": "11111111-1111-4111-8111-111111111111"
}
}