Skip to content

Создать контакт для домена ​

POST /contacts

operationId: createContact

Создаёт локальную карточку. Контакт реестра создаётся/связывается при доменных операциях, не этой командой. Для person обязательно отчество. residence_country_code и postal_code обязательны несмотря на необязательность в старых примерах. Правила документа зависят от страны резидентства, не от среды ключа.

Scopes: contacts.write

Параметры ​

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: ["type","email","phone","country_code","residence_country_code","city","address_line","postal_code"]POST и PATCH используют одинаковую полную валидацию: PATCH не частичный. Неуказанные необязательные текстовые поля обнуляются; external_id сохраняется при отсутствии. Документ при отсутствии обоих полей сохраняется. customer_uuid обязателен в managed. Схема: CoreUpsertContactRequest
$.customer_uuidstring / nullНетformat: "uuid"Обязателен в managed. При aggregate можно опустить для агрегированного покупателя.
$.external_idstring / nullНетmaxLength: 255Идентификатор во внешнем биллинге; уникален для реселлера+среды.
$.typestringДаenum: "person", "organization"Тип контакта.
$.first_namestring / nullНетmaxLength: 255Обязателен для person. Unicode-буквы/диакритика, одиночные пробелы, дефис или апостроф между словами.
$.last_namestring / nullНетmaxLength: 255Обязателен для person; те же правила имени.
$.middle_namestring / nullНетmaxLength: 255В текущем API отчество ОБЯЗАТЕЛЬНО для person, не только имя и фамилия.
$.organization_namestring / nullНетmaxLength: 500Обязательно для organization.
$.emailstringДаformat: "email"; maxLength: 255Email.
$.phonestringДаmaxLength: 50Телефон. Контроллер ограничивает длину, не проверяет E.164; используйте международный формат.
$.country_codestringДаminLength: 2; maxLength: 2Страна адреса, верхний регистр сохраняется сервером.
$.residence_country_codestringДаminLength: 2; maxLength: 2Обязательная страна резидентства; определяет правила документа.
$.citystringДаmaxLength: 255Город.
$.regionstring / nullНетmaxLength: 255Регион.
$.address_linestringДаmaxLength: 1000Адрес.
$.postal_codestringДаmaxLength: 30Обязательный индекс.
$.external_id_typestring / nullНетmaxLength: 40Обязателен с external_id_value. KZ: BIN/IIN (12 цифр и контрольная сумма); RU: INN (10/12 цифр); US: EIN (NN-NNNNNNN), SSN (NNN-NN-NNNN); CN: USCC (18 символов); DE: VAT (DE+9 цифр); UZ: STIR (9), PINFL (14); GB: CRN (8), VAT (GB+9); TR: VKN (10), TCKN (11); UA: EDRPOU (8); AE: TRN (100+12). Для остальных стран PASSPORT/TAX_ID (непустое значение). USCC допускает [0-9A-HJ-NP-RT-UW-Y]{18}. Форматы регистрозависимы для значения документа, тип/страна нормализуются к верхнему регистру.
$.external_id_valuestring / nullНетmaxLength: 100; Только записьОбязателен с external_id_type; хранится, но не возвращается. Для KZ проверяется контрольная сумма; тестовые исключения зависят от конфигурации, test-ключ не отключает проверку.
$ (allOf 1)Условная схемаДа
$ (allOf 1) (if)objectДа
$ (allOf 1) (if).typeУсловная схемаНетconst: "person"
$ (allOf 1) (then)objectДаrequired: ["first_name","last_name","middle_name"]
$ (allOf 1) (then).first_namestringДаminLength: 1
$ (allOf 1) (then).last_namestringДаminLength: 1
$ (allOf 1) (then).middle_namestringДаminLength: 1
$ (allOf 2)Условная схемаДа
$ (allOf 2) (if)objectДа
$ (allOf 2) (if).typeУсловная схемаНетconst: "organization"
$ (allOf 2) (then)objectДаrequired: ["organization_name"]
$ (allOf 2) (then).organization_namestringДаminLength: 1

Полный контакт физического лица ​

json
{
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "organization_name": null,
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1042",
  "customer_uuid": "019a0000-0000-7000-8000-000000000010"
}
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/contacts" \
  --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 '{
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "organization_name": null,
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1042",
  "customer_uuid": "019a0000-0000-7000-8000-000000000010"
}'
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 = '/contacts';

$url = $baseUrl . $path;
$headers = [
    'Authorization: Bearer ' . requiredEnv('RESELLER_API_TOKEN'),
    'Accept: application/json',
    'Idempotency-Key: ' . requiredEnv('IDEMPOTENCY_KEY'),
    'Content-Type: application/json',
];
$body = '{
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "organization_name": null,
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1042",
  "customer_uuid": "019a0000-0000-7000-8000-000000000010"
}';
$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 = "/contacts";

const url = baseUrl + path;
const body = `{
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "organization_name": null,
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1042",
  "customer_uuid": "019a0000-0000-7000-8000-000000000010"
}`;
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 = "/contacts"

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  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"organization_name\": null,\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1042\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\"\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 := "/contacts"

    endpoint := strings.TrimRight(baseURL, "/") + path
    request, err := http.NewRequest("POST", endpoint, strings.NewReader("{\n  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"organization_name\": null,\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1042\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\"\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 = "/contacts";

        URI uri = URI.create(baseUrl + path);
        String body = "{\n  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"organization_name\": null,\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1042\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\"\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 = "/contacts";

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  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"organization_name\": null,\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1042\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\"\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",
  "external_id": "contact-1044",
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id_type": "IIN",
  "external_id_value": "900101300116"
}
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/contacts" \
  --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",
  "external_id": "contact-1044",
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id_type": "IIN",
  "external_id_value": "900101300116"
}'
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 = '/contacts';

$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",
  "external_id": "contact-1044",
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id_type": "IIN",
  "external_id_value": "900101300116"
}';
$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 = "/contacts";

const url = baseUrl + path;
const body = `{
  "customer_uuid": "019a0000-0000-7000-8000-000000000010",
  "external_id": "contact-1044",
  "type": "person",
  "first_name": "Иван",
  "last_name": "Петров",
  "middle_name": "Иванович",
  "email": "ivan@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id_type": "IIN",
  "external_id_value": "900101300116"
}`;
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 = "/contacts"

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  \"external_id\": \"contact-1044\",\n  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id_type\": \"IIN\",\n  \"external_id_value\": \"900101300116\"\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 := "/contacts"

    endpoint := strings.TrimRight(baseURL, "/") + path
    request, err := http.NewRequest("POST", endpoint, strings.NewReader("{\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n  \"external_id\": \"contact-1044\",\n  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id_type\": \"IIN\",\n  \"external_id_value\": \"900101300116\"\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 = "/contacts";

        URI uri = URI.create(baseUrl + path);
        String body = "{\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000010\",\n  \"external_id\": \"contact-1044\",\n  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id_type\": \"IIN\",\n  \"external_id_value\": \"900101300116\"\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 = "/contacts";

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  \"external_id\": \"contact-1044\",\n  \"type\": \"person\",\n  \"first_name\": \"Иван\",\n  \"last_name\": \"Петров\",\n  \"middle_name\": \"Иванович\",\n  \"email\": \"ivan@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id_type\": \"IIN\",\n  \"external_id_value\": \"900101300116\"\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
{
  "type": "organization",
  "first_name": null,
  "last_name": null,
  "middle_name": null,
  "organization_name": "ТОО \"Example\"",
  "email": "admin@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1043",
  "customer_uuid": "019a0000-0000-7000-8000-000000000011"
}
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/contacts" \
  --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 '{
  "type": "organization",
  "first_name": null,
  "last_name": null,
  "middle_name": null,
  "organization_name": "ТОО \"Example\"",
  "email": "admin@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1043",
  "customer_uuid": "019a0000-0000-7000-8000-000000000011"
}'
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 = '/contacts';

$url = $baseUrl . $path;
$headers = [
    'Authorization: Bearer ' . requiredEnv('RESELLER_API_TOKEN'),
    'Accept: application/json',
    'Idempotency-Key: ' . requiredEnv('IDEMPOTENCY_KEY'),
    'Content-Type: application/json',
];
$body = '{
  "type": "organization",
  "first_name": null,
  "last_name": null,
  "middle_name": null,
  "organization_name": "ТОО \\"Example\\"",
  "email": "admin@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1043",
  "customer_uuid": "019a0000-0000-7000-8000-000000000011"
}';
$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 = "/contacts";

const url = baseUrl + path;
const body = `{
  "type": "organization",
  "first_name": null,
  "last_name": null,
  "middle_name": null,
  "organization_name": "ТОО \\"Example\\"",
  "email": "admin@example.com",
  "phone": "+77010000000",
  "country_code": "KZ",
  "residence_country_code": "KZ",
  "city": "Алматы",
  "region": "Алматы",
  "address_line": "ул. Примерная, 10",
  "postal_code": "050000",
  "external_id": "contact-1043",
  "customer_uuid": "019a0000-0000-7000-8000-000000000011"
}`;
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 = "/contacts"

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  \"type\": \"organization\",\n  \"first_name\": null,\n  \"last_name\": null,\n  \"middle_name\": null,\n  \"organization_name\": \"ТОО \\\"Example\\\"\",\n  \"email\": \"admin@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1043\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000011\"\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 := "/contacts"

    endpoint := strings.TrimRight(baseURL, "/") + path
    request, err := http.NewRequest("POST", endpoint, strings.NewReader("{\n  \"type\": \"organization\",\n  \"first_name\": null,\n  \"last_name\": null,\n  \"middle_name\": null,\n  \"organization_name\": \"ТОО \\\"Example\\\"\",\n  \"email\": \"admin@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1043\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000011\"\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 = "/contacts";

        URI uri = URI.create(baseUrl + path);
        String body = "{\n  \"type\": \"organization\",\n  \"first_name\": null,\n  \"last_name\": null,\n  \"middle_name\": null,\n  \"organization_name\": \"ТОО \\\"Example\\\"\",\n  \"email\": \"admin@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1043\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000011\"\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 = "/contacts";

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  \"type\": \"organization\",\n  \"first_name\": null,\n  \"last_name\": null,\n  \"middle_name\": null,\n  \"organization_name\": \"ТОО \\\"Example\\\"\",\n  \"email\": \"admin@example.com\",\n  \"phone\": \"+77010000000\",\n  \"country_code\": \"KZ\",\n  \"residence_country_code\": \"KZ\",\n  \"city\": \"Алматы\",\n  \"region\": \"Алматы\",\n  \"address_line\": \"ул. Примерная, 10\",\n  \"postal_code\": \"050000\",\n  \"external_id\": \"contact-1043\",\n  \"customer_uuid\": \"019a0000-0000-7000-8000-000000000011\"\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КодКогда возникаетЧто делать
401unauthenticatedBearer-ключ отсутствует, недействителен, отозван или истёк.Проверьте ключ и его срок, не повторяйте с теми же неверными credentials.
403forbiddenScope, IP allowlist, режим или состояние реселлера не разрешает запрос.Проверьте scopes, среду, allowlist и настройки реселлера через администратора.
404resource_not_foundМаршрут отключён конфигурацией либо объект недоступен в текущем контексте.Проверьте UUID, среду и принадлежность; при отключённой функции обратитесь к оператору.
409conflictIdempotency-Key уже обрабатывается либо использован для другого метода, URL/query или тела.Для того же запроса дождитесь завершения и повторите неизменённый запрос с тем же ключом; изменённый запрос требует нового ключа после сверки результата.
422validation_failedОтсутствуют обязательные поля (включая middle_name для person), неверный документ/контрольная сумма, customer_uuid не разрешён, external_id уже занят.Исправьте поля из error.details/условия. Не повторяйте неизменённый невалидный запрос.
429rate_limit_exceededЛимит текущего API-ключа исчерпан.Учитывайте Retry-After при наличии, используйте backoff с jitter. Запись повторяйте с прежним Idempotency-Key и неизменённым телом.
500internal_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"]Успешный ответ. Схема: CoreContactEnvelope
$.dataobjectДаrequired: ["uuid","type","status","first_name","last_name","middle_name","organization_name","email","phone","country_code","residence_country_code","city","region","address_line","postal_code","external_id_type","verification_status","is_locked_as_registrant","verified_at","external_id","customer_uuid"]Локальный доменный контакт. external_id_value никогда не возвращается. Изменение этой карточки само по себе не выполняет EPP contact update. Схема: CoreContact
$.data.uuidstringДаformat: "uuid"UUID объекта.
$.data.typestringДаenum: "person", "organization"Тип.
$.data.statusstringДаСтатус, при создании active.
$.data.first_namestring / nullДаИмя.
$.data.last_namestring / nullДаФамилия.
$.data.middle_namestring / nullДаОтчество.
$.data.organization_namestring / nullДаОрганизация.
$.data.emailstringДаformat: "email"Email.
$.data.phonestringДаТелефон.
$.data.country_codestringДаСтрана адреса.
$.data.residence_country_codestring / nullДаСтрана резидентства.
$.data.citystringДаГород.
$.data.regionstring / nullДаРегион.
$.data.address_linestringДаАдрес.
$.data.postal_codestring / nullДаПочтовый индекс.
$.data.external_id_typestring / nullДаТип документа, не идентификатор внешнего биллинга.
$.data.verification_statusstringДаСтатус верификации; создание обычно not_required.
$.data.is_locked_as_registrantbooleanДаПризнак блокировки контакта как владельца.
$.data.verified_atstring / nullДаformat: "date-time"Время подтверждения.
$.data.external_idstring / nullДаИдентификатор контакта во внешнем биллинге.
$.data.customer_uuidstring / nullДаformat: "uuid"UUID покупателя.

Физическое лицо без документа; для регистрации документ может понадобиться (201) ​

json
{
  "data": {
    "uuid": "019a0000-0000-7000-8000-000000000012",
    "type": "person",
    "status": "active",
    "first_name": "Иван",
    "last_name": "Петров",
    "middle_name": "Иванович",
    "organization_name": null,
    "email": "ivan@example.com",
    "phone": "+77010000000",
    "country_code": "KZ",
    "residence_country_code": "KZ",
    "city": "Алматы",
    "region": "Алматы",
    "address_line": "ул. Примерная, 10",
    "postal_code": "050000",
    "external_id_type": null,
    "verification_status": "not_required",
    "is_locked_as_registrant": false,
    "verified_at": null,
    "external_id": "contact-1042",
    "customer_uuid": "019a0000-0000-7000-8000-000000000010"
  }
}

Организация (201) ​

json
{
  "data": {
    "uuid": "019a0000-0000-7000-8000-000000000013",
    "type": "organization",
    "status": "active",
    "first_name": null,
    "last_name": null,
    "middle_name": null,
    "organization_name": "ТОО \"Example\"",
    "email": "admin@example.com",
    "phone": "+77010000000",
    "country_code": "KZ",
    "residence_country_code": "KZ",
    "city": "Алматы",
    "region": "Алматы",
    "address_line": "ул. Примерная, 10",
    "postal_code": "050000",
    "external_id_type": null,
    "verification_status": "not_required",
    "is_locked_as_registrant": false,
    "verified_at": null,
    "external_id": "contact-1043",
    "customer_uuid": "019a0000-0000-7000-8000-000000000011"
  }
}

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
$.errorobjectДаrequired: ["code","message","details","request_id"]
$.error.codestringДаМашинный код; не извлекайте причину из message.
$.error.messagestringДаЧеловекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки.
$.error.detailsoneOfДаБез подробностей возвращается [], не 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_afterintegerДаminimum: 0
$.error.request_idstring / 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
$.errorobjectДаrequired: ["code","message","details","request_id"]
$.error.codestringДаМашинный код; не извлекайте причину из message.
$.error.messagestringДаЧеловекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки.
$.error.detailsoneOfДаБез подробностей возвращается [], не 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_afterintegerДаminimum: 0
$.error.request_idstring / 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 ​

Маршрут отключён конфигурацией либо объект недоступен в текущем контексте.

Заголовок 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
$.errorobjectДаrequired: ["code","message","details","request_id"]
$.error.codestringДаМашинный код; не извлекайте причину из message.
$.error.messagestringДаЧеловекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки.
$.error.detailsoneOfДаБез подробностей возвращается [], не 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_afterintegerДаminimum: 0
$.error.request_idstring / 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 уже обрабатывается либо использован для другого метода, URL/query или тела.

Заголовок 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
$.errorobjectДаrequired: ["code","message","details","request_id"]
$.error.codestringДаМашинный код; не извлекайте причину из message.
$.error.messagestringДаЧеловекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки.
$.error.detailsoneOfДаБез подробностей возвращается [], не 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_afterintegerДаminimum: 0
$.error.request_idstring / 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 ​

Отсутствуют обязательные поля (включая middle_name для person), неверный документ/контрольная сумма, customer_uuid не разрешён, external_id уже занят.

Заголовок 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
$.errorobjectДаrequired: ["code","message","details","request_id"]
$.error.codestringДаМашинный код; не извлекайте причину из message.
$.error.messagestringДаЧеловекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки.
$.error.detailsoneOfДаБез подробностей возвращается [], не 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_afterintegerДаminimum: 0
$.error.request_idstring / 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: 42

Content-Type: application/json

ПолеТипОбязательно в родителеОграниченияОписание
$objectДаrequired: ["error"]Собственный JSON-конверт API, не RFC 9457. Структура и code не зависят от языка; message и details предназначены человеку. До выбора маршрута и на proxy этот конверт не гарантирован. Схема: ErrorEnvelope
$.errorobjectДаrequired: ["code","message","details","request_id"]
$.error.codestringДаМашинный код; не извлекайте причину из message.
$.error.messagestringДаЧеловекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки.
$.error.detailsoneOfДаБез подробностей возвращается [], не 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_afterintegerДаminimum: 0
$.error.request_idstring / 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
$.errorobjectДаrequired: ["code","message","details","request_id"]
$.error.codestringДаМашинный код; не извлекайте причину из message.
$.error.messagestringДаЧеловекочитаемое, частично локализуемое сообщение; не стабильный идентификатор ошибки.
$.error.detailsoneOfДаБез подробностей возвращается [], не 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_afterintegerДаminimum: 0
$.error.request_idstring / 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"
  }
}

Машиночитаемый контракт ​

OpenAPI JSON · OpenAPI YAML · Markdown этой операции