Тема
Надёжность интеграции
Этот раздел описывает действующее поведение REST API, а не желаемый стандарт. Базовый путь всех примеров: /api/reseller/v1. Используйте выданный провайдером HTTPS origin. api.example в примерах является обозначением, не рабочим сервером. Значения по умолчанию приведены для текущей версии; фактические настройки вашего API-ключа и webhook имеют приоритет.
Авторизация и контекст
Передавайте Authorization: Bearer $RESELLER_API_TOKEN, Accept: application/json, а для JSON-тела Content-Type: application/json. Храните токен только на сервере интеграции. Не передавайте его браузеру, в query string, тикете, публичном репозитории или ИИ вместе с документацией.
Формат ключа: rsl_test_{publicKeyId}.{secret} или rsl_live_{publicKeyId}.{secret}. publicKeyId содержит 24 латинские буквы/цифры, secret содержит 64 символа base64url. Ключ личного кабинета не заменяет reseller-ключ. Среду нельзя переключить заголовком или полем запроса. Для test и live нужны разные ключи, данные и настройки подключения.
Начните с GET /context: он не требует отдельного scope и возвращает среду, доступные scopes и data.credential.rate_limit_per_minute. На остальные методы нужны scopes, указанные в справочнике; требование нескольких scopes означает логическое И. Наличие domains.read не даёт права покупать или продлевать домен. Учитываются также IP allowlist, активность ключа, срок его действия, режим API, тариф и статус реселлера. Ограниченный реселлер может выполнять безопасные HTTP-методы, но не POST, даже если POST логически используется для проверки.
Ресурсы ограничены реселлером и средой. Чужой или недоступный ресурс обычно даёт 404, а не сведения о его владельце. Передавайте UUID ровно из ответов API: не все методы одинаково обрабатывают произвольный не-UUID, возможен 500 вместо ожидаемого 422.
Контрпример: поддержка в test
Не считайте, что test-ключ автоматически предоставляет песочницу для всех методов. Все четыре обработчика поддержки доступны только с live-ключом, независимо от наличия tickets.read/tickets.write:
| Метод | Путь | Результат с корректным test-ключом и нужным scope |
|---|---|---|
| GET | /tickets | 403 |
| GET | /tickets/{ticketUuid} | 403 |
| POST | /tickets | 403 |
| POST | /tickets/{ticketUuid}/messages | 403 |
json
{"error":{"code":"forbidden","message":"Support is available only to live credentials.","details":[],"request_id":"11111111-1111-4111-8111-111111111111"}}Для записей это ограничение проверяется также перед возвратом старого HTTP replay. /balance и /ledger тоже не являются тестовыми финансовыми ресурсами: test-ключ получает 403. Для проверки поддержки используйте согласованный с провайдером live-сценарий, не переключайте финансовые тесты на live автоматически.
Лимиты запросов
| Параметр | Поведение |
|---|---|
| Базовый лимит нового ключа | 120 запросов за 60 секунд, может быть изменён провайдером |
| Настраиваемый диапазон ключа | 1..10000 запросов за минутное окно |
| Область счётчика | Одна запись API-ключа, все её методы и источники IP вместе |
| Окно | Фиксированное окно 60 секунд с первого учитываемого запроса; не календарная минута, не скользящий час и не token bucket |
| Сброс | По окончании окна; последующие запросы его не продлевают |
| Учёт | Запросы после успешной проверки ключа/реселлера, до проверки Idempotency-Key и scope; ошибки валидации, scope и HTTP replay тоже расходуют квоту |
| Отказы | Ранний отказ авторизации/реселлера не расходует этот счётчик. Собственный 429 не добавляет hit и не продлевает паузу |
| Ротация API-ключа | Меняет секрет существующего ключа, не обещает новый счётчик |
При конкурентных запросах проверка квоты и увеличение счётчика раздельны. Не используйте кратковременное превышение как разрешённую ёмкость; ограничивайте параллелизм и распределяйте запросы равномерно. Это лимит REST API, не квота реестра и не гарантия скорости выполнения доменной операции.
После прохождения ограничителя возвращаются:
http
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
X-Request-ID: 11111111-1111-4111-8111-111111111111На собственной ветке исчерпания квоты ответ имеет вид:
http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 42
X-Request-ID: 11111111-1111-4111-8111-111111111111json
{"error":{"code":"rate_limit_exceeded","message":"API rate limit exceeded.","details":{"retry_after":42},"request_id":"11111111-1111-4111-8111-111111111111"}}В этой ветке нет X-RateLimit-Limit и X-RateLimit-Remaining. На успешном ответе нет Retry-After. API самостоятельно не формирует X-RateLimit-Reset, RateLimit, RateLimit-Policy и RateLimit-Reset. Промежуточный proxy может добавить собственные заголовки или отдельный 429. Не требуйте отсутствующие заголовки в SDK. Сохранение чужих rate-limit заголовков при обработке ошибки не означает, что API их всегда выдаёт.
На 429 приостановите всю очередь данного ключа на Retry-After секунд плюс небольшой случайный запас. Если заголовка нет, используйте ограниченный exponential backoff с jitter. При Retry-After: 0 не запускайте busy loop. Для общего HTTP-клиента поддержите также HTTP-date от proxy; собственный ограничитель API возвращает целые секунды. X-RateLimit-Remaining является снимком, а не резервированием места для следующего запроса.
Идемпотентность
Idempotency-Key обязателен для всех POST, PUT, PATCH и DELETE, включая /domains/check, /quotes, /domains/{domainUuid}/sync, webhook test и ротацию секрета. GET/HEAD его не требуют. Значение обрезается по краям и должно содержать 1..160 символов. Рекомендуется UUID либо ваш уникальный идентификатор логической команды без персональных данных.
Сохраните до отправки: ключ, API-ключ/среду, HTTP-метод, путь, query string и исходные байты тела. SHA-256 отпечаток учитывает метод, путь с query string в его HTTP-нормализованном представлении и точные байты тела, а не семантически равный JSON. Не меняйте порядок полей, пробелы, число 1 на 1.0, пустое тело на {} и путь /orders на /domains при повторе. Заголовки языка и X-Request-ID в отпечаток не входят.
| Ситуация | Результат |
|---|---|
| Первый запрос с новым ключом | Обычная обработка |
| Тот же ключ и отпечаток, HTTP-ответ сохранён | Тот же HTTP status/JSON, Idempotency-Replayed: true |
| Тот же ключ, другой отпечаток | 409 conflict |
| Тот же ключ, первая попытка ещё processing | 409 conflict |
| Нет/невалидный ключ | 422 validation_failed, details: [] |
| Исключение или ответ 5xx | Сохранённый HTTP replay не гарантируется |
Обычный TTL HTTP replay: 24 часа с момента первого резервирования, настраивается провайдером и не продлевается повторами. Истёкшая запись может быть удалена при следующем обращении с этим ключом. Зависшая processing-запись не имеет отдельного короткого автоматического срока разблокировки: не обходите 409 новым ключом после таймаута. Уточните состояние у поддержки.
Доменная асинхронная операция имеет дополнительную устойчивую привязку ключа к задаче, которая не равна HTTP TTL. Однако у других методов такой защиты может не быть. Никогда не используйте старый ключ для нового бизнес-действия, в том числе спустя 24 часа. После истечения TTL сначала сверяйте ресурсы по UUID/external_id и операции, а не создавайте заказ заново.
Проверка действительности Bearer и общего доступа выполняется перед replay, но свежая проверка scope конкретного метода не гарантируется для уже сохранённого ответа. Отзыв ключа закрывает доступ; простое снятие scope не следует использовать как гарантию немедленного запрета чтения ранее сохранённых ответов. Это важно для чувствительных ответов, например секрета webhook.
HTTP replay повторяет статус и JSON, но не является побайтовым воспроизведением всех заголовков. Idempotency-Replayed отсутствует на обычном ответе, а не равен false. X-Request-ID может быть новым при старом error.request_id в сохранённом теле. При ротации/создании webhook replay может снова показать секрет: исключите эти ответы из логирования и кешей общего пользования. Срок TTL не является обещанием физического удаления секрета из хранилища в ту же секунду.
Ошибки и безопасные решения
API использует собственный JSON-конверт, не application/problem+json и не RFC 9457:
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"}}Пустые details выглядят как []; объект ошибок полей содержит массивы строк. На 429 объект содержит число retry_after. details: null для этого конверта не ожидается. request_id допускает null, если контекст не назначен. Само поле error в успешном JSON ресурса операции имеет другую семантику и может быть null; не смешивайте его с HTTP ErrorEnvelope.
| HTTP / code | Решение интегратора |
|---|---|
400 / request_failed, если конверт применён | Исправить запрос/JSON; не повторять неизменённым бесконечно |
401 / unauthenticated | Исправить токен, срок, отзыв; не создавать новый заказ в ответ на ошибку авторизации |
403 / forbidden | Проверить scopes, IP, режим/тариф/статус реселлера и live-only ограничения |
404 / resource_not_found | Проверить путь, UUID, среду и владельца; также возможно отключение API |
405 / request_failed, только если конверт применён | Выбрать документированный метод; маршрутизатор часто возвращает только message |
409 / conflict | Отличить конфликт ключа/обработку от состояния ресурса по локальному журналу и GET; не автоматический новый ключ |
422 / validation_failed | Показать ошибки полей, исправить входные данные; для изменённого запроса использовать новый ключ после сверки |
429 / rate_limit_exceeded | Общая пауза ключа по Retry-After, затем повтор той же команды |
500 и прочие 5xx / internal_error, если конверт применён | Возможен уже выполненный побочный эффект; сверка и ограниченный повтор с прежним ключом |
| Timeout, разрыв соединения, ответ не JSON | Результат неизвестен, не эквивалент failed; сохранить попытку и сверять |
Неподдерживаемый метод, неизвестный маршрут, неверный запрос до маршрутизации, сетевой proxy или WAF могут вернуть другой JSON, HTML или пустое тело без X-Request-ID. SDK должен сначала проверить HTTP status и Content-Type и безопасно сохранить ограниченный диагностический фрагмент. Не считать любую ошибку декодирования JSON признаком отказа бизнес-операции.
Язык выбирается из поддерживаемых ru, en, kk: в первую очередь X-Language, затем X-Locale, затем доступный контекст/Accept-Language и язык по умолчанию. Региональный суффикс явного заголовка, например ru-RU, нормализуется. Не все сообщения переведены: английская строка возможна при русском запросе. code, имена полей и структура не меняются от языка. Ветвление по message.includes(...) запрещено в интеграции: несколько разных причин сейчас используют один conflict или forbidden.
Пример неопределённой операции
HTTP 202 подтверждает приём, не результат реестра. После ответа сохраните UUID задачи и опрашивайте GET /operations/{operationUuid}; webhook используйте как ускоряющий сигнал, не единственное доказательство.
json
{"data":{"uuid":"66666666-6666-4666-8666-666666666666","domain_uuid":"55555555-5555-4555-8555-555555555555","action":"renew","status":"uncertain","result":[],"error":{"code":"registry_result_unknown","message":"Registry result is not confirmed. Do not repeat this operation; reconciliation is scheduled."},"created_at":"2026-10-05T09:00:00+00:00","completed_at":null,"next_check_at":"2026-10-05T09:05:00+00:00"}}Текущий обработчик REST-операций выставляет pending, processing, uncertain, completed и failed. Первые три не являются отказом. awaiting_registry учтён защитной проверкой занятости домена, но в текущем коде не найден процесс, который его выставляет; это не этап публичного жизненного цикла REST-операции. Если получите незнакомое состояние, сохраните его и сверяйте через GET/поддержку, не приравнивайте к failed.
Не освобождайте собственный резерв клиента и не создавайте вторую команду только из-за таймаута. Завершение completed и подтверждённый failed обрабатывайте по справочнику операции. Не все неопределённые результаты можно автоматически сверить: запросите ручную проверку, сохранив UUID задачи, домена и запроса. Регистрация создаётся через заказ; после неизвестного ответа ищите заказ/услугу по external_id и её состояние, не ожидайте, что у регистрации обязательно будет тот же публичный формат задачи управления доменом.
Алгоритм клиента
- До сетевого вызова сохраните неизменяемую команду и её Idempotency-Key в своей БД.
- Пропустите её через общую очередь лимита API-ключа. X-Request-ID задавайте отдельно на попытку.
- При 2xx сохраните ответ атомарно с локальной командой; 204 не декодируйте как JSON. При 202 запланируйте проверку UUID операции.
- При 429 выдержите паузу для всего ключа. При сетевой ошибке/5xx пометьте команду как unknown, сверяйте состояние и при допустимом повторе не меняйте ключ/байты.
- При 401/403/404/405/422 остановите автоматические повторы до исправления причины. При 409 сверяйте исходную команду и текущее состояние, не обходите блокировку.
- При uncertain продолжайте редкий polling, учитывая next_check_at и общую квоту. При длительном ожидании создайте обращение без секретов и повторной покупки.
- Не делайте вывод о регистрации, оплате или возврате средств только по отсутствию webhook.
Пагинация и журнал интегратора
Списки с cursor используют per_page=25 по умолчанию, диапазон 1..100. Передавайте next_cursor без изменений и с URL-кодированием. В webhook-списках ответ содержит только data и meta.next_cursor/per_page: поля total, page и last_page не обещаны. Некоторые другие ресурсы дополняют пагинацию links; проверяйте контракт конкретного метода. Отсутствующий курсор на первой странице и next_cursor: null на последней не одно и то же.
Храните у себя среду, метод/путь без секретов, время, HTTP status, код ошибки, X-Request-ID, Idempotency-Key, UUID заказа/домена/операции и результат сверки. Не пишите Authorization, signing_secret, auth-code, ЭЦП и персональные документы в общие логи. Ошибки авторизации до определения ключа могут не попасть в журнал запросов провайдера. Значение хранения журнала в конфигурации по умолчанию 90 дней не является подтверждённым SLA доступности/удаления логов и не заменяет ваш журнал.
Основания документации
Справочник описывает поля, ограничения и ответы, а руководство отдельно объясняет порядок интеграции: такое разделение соответствует Diataxis. Полнота описания публичных элементов и ясность формулировок взяты из Google AIP-192. Машинный контракт использует OpenAPI 3.1. Семантика HTTP и Retry-After опирается на RFC 9110, статус 429 на RFC 6585. Это принципы оформления, а не заявление о реализации всех Google AIP или Problem Details.