API управления
Соглашения
Постраничность курсором, конверт ошибки, коды ответов, ограничение частоты и его заголовки, выбор песочницы и подтверждения необратимых операций.
На этой странице · 9
Правила, общие для всех маршрутов. Дальше в справочнике они не повторяются.
Тело запроса
Management API принимает только JSON. Заголовок Content-Type: application/json
обязателен для запросов с телом.
- Пустое тело при этом заголовке считается за
{}— так удобнее вызывать маршруты вродеPOST /keys/{id}/rotate, где тела нет, аcurlиfetchзаголовок всё равно ставят. - Тело, ушедшее формой (
curl -d '…'безContent-Type), распознаётся отдельно, иначе ответ звучал бы как «поле title обязательно» на запрос, гдеtitleкак раз прислан.
curl -s -X POST "$STEND/api/v1/sandboxes" -H "Authorization: Bearer $STEND_KEY" -d '{"name":"x"}'
{
"error": "VALIDATION",
"message": "Тело запроса должно быть JSON с заголовком «Content-Type: application/json». Похоже, JSON ушёл как поле формы"
}
Битый JSON при верном заголовке отвечает иначе:
{ "error": "INVALID_JSON", "message": "Тело запроса не разбирается как JSON" }
Постраничность
Списки отдают { items, nextCursor }. Курсор — идентификатор последней отданной
записи; смещением (offset) листать нельзя: журнал пополняется во время листания,
и страница 2 повторила бы часть страницы 1.
Параметры списков
curl -s "$STEND/api/v1/logs?limit=2" -H "Authorization: Bearer $STEND_KEY"
{
"items": [
{ "id": "cmtty0oo8001h7am3cac6d6jj", "publicId": "req_890aa03867", "serviceCode": "bitrix24", "endpoint": "/crm.deal.list", "statusCode": 404 },
{ "id": "cmtty0oo8001g7am3to5x7fvq", "publicId": "f0c9ea91aae8abf9", "serviceCode": "ozon", "endpoint": "/v3/posting/fbs/list", "statusCode": 200 }
],
"nextCursor": "cmtty0oo8001g7am3to5x7fvq"
}
Негодный курсор — 400, а не пустая страница. Молчаливая пустота читалась бы как «данных больше нет» и обманывала бы скрипт, сохранивший курсор между запусками:
{
"error": "VALIDATION",
"message": "Курсор «zzz» недействителен: такой записи больше нет или она не подходит под фильтры",
"issues": ["cursor: запросите первую страницу без курсора"]
}
Курсор действителен только при том же наборе фильтров: с другими фильтрами запись в выборку не попадёт и придёт та же 400.
Конверт ошибки
Один и тот же во всём API:
{
"error": "VALIDATION",
"message": "Некорректные данные запроса",
"issues": [
"body.path: Путь мока начинается с /custom/ — по этому адресу его вызывает песочница",
"body.title: Название не короче 2 символов"
]
}
error — машинный код, message — объяснение по-русски, issues — разбор
ошибок валидации по полям (только там, где он есть).
Коды ответов
HTTP-коды
Чужой объект отвечает 404, а не 403: по разнице кодов чужие идентификаторы перебирались бы на существование.
Машинные коды из GET /api/v1/meta: UNAUTHORIZED, SANDBOX_KEY_NOT_ALLOWED,
FORBIDDEN, SANDBOX_NOT_FOUND, NOT_FOUND, VALIDATION, INVALID_JSON,
CONFIRM_MISMATCH, CONFLICT, UNPROCESSABLE, RATE_LIMITED, INTERNAL.
Отдельные маршруты добавляют свои: TARGET_NOT_ALLOWED, EVENT_SERVICE_MISMATCH,
UNKNOWN_EVENT, NO_WEBHOOK, NO_WEBHOOK_FOR_EVENT, WEBHOOK_PAUSED,
NO_AGENT, TOO_MANY_BURSTS, NO_KEY.
422, а не 429, у потолков продукта
Потолок одновременных серий событий отвечает 422, хотя в кабинете это 429.
В Management API код 429 занят ограничением частоты и приходит с Retry-After;
клиент с обычной логикой «429 → подождать и повторить» на потолке продукта
уходил бы в бесконечный повтор.
Ограничение частоты
240 запросов в минуту на субъект — на ключ, а для браузера на сессию.
Значение задаётся переменной MGMT_RATE_LIMIT и приходит в GET /api/v1/meta.
Ёмкость равна минутной норме, поэтому короткая пачка запросов проходит целиком,
а ровный поток упирается в норму.
Заголовки есть на каждом ответе:
HTTP/1.1 200 OK
x-ratelimit-limit: 240
x-ratelimit-remaining: 239
x-ratelimit-reset: 0
При превышении:
HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 240
x-ratelimit-remaining: 0
x-ratelimit-reset: 1
retry-after: 1
{
"error": "RATE_LIMITED",
"message": "Слишком много запросов к Management API: не больше 240 в минуту. Повторите через 1 с."
}
X-RateLimit-Reset — секунды до освобождения хотя бы одного запроса, а не время
полного восстановления ведра.
У выгрузки журнала своё, куда более строгое ведро — шесть выгрузок в минуту:
каждая читает десятки тысяч записей в памяти того же процесса, что обслуживает
мок-шлюз. Считается оно отдельно, поэтому обычные запросы от этого не страдают —
X-RateLimit-Remaining в таком ответе показывает остаток общей нормы:
HTTP/1.1 429 Too Many Requests
x-ratelimit-remaining: 231
retry-after: 10
{
"error": "RATE_LIMITED",
"message": "Выгрузок журнала не больше 6 в минуту. Повторите через 10 с или сузьте период полями from и to"
}
Лимит считается в памяти процесса
При нескольких инстансах APIStend норма станет кратно мягче — это осознанный размен: поход в общее хранилище на каждый запрос стоил бы дороже самого запроса.
Идемпотентности нет
Заголовка Idempotency-Key в Management API нет, и повторный POST создаёт
второй объект. Безопасность повтора обеспечивают сами операции:
- уникальность имени песочницы и пары «метод + путь» у мока — повтор даёт 409;
- повторный импорт того же файла спецификации кладёт уже существующие пути
в
skipped, а не перезаписывает; POST /mocks/{id}/enableи/disableпри повторе ничего не меняют и не считаются ошибкой (changed: false);POST /alerts/{id}/readне переписывает время первого прочтения (alreadyRead: true).
Для остальных операций повтор после сетевой ошибки нужно проверять чтением: запросить список и убедиться, что объект не создан дважды.
Выбор песочницы
Почти всё в API живёт внутри песочницы. Она ищется по порядку:
- 1
:sandboxIdв путиТолько у маршрутов
/sandboxes/{sandboxId}…. - 2
?sandboxId=в строке запросаРаботает у всех маршрутов, привязанных к песочнице.
- 3
Песочница ключа
Серверный ключ выпущен внутри песочницы, и она считается текущей.
- 4
Первая песочница аккаунта
Так работает cookie-сессия кабинета. У кого одна песочница — тот про неё вообще не вспоминает.
Если песочницы нет вовсе, приходит 404 SANDBOX_NOT_FOUND с подсказкой создать
её через POST /api/v1/sandboxes.
Подтверждения необратимых операций
Удаление ключа, песочницы, приложения и аккаунта требует повторить имя объекта
в теле запроса — confirmName (у аккаунта — login). Несовпадение даёт 400
с отдельным кодом:
{
"error": "CONFIRM_MISMATCH",
"message": "Удаление необратимо. Повторите имя песочницы в поле confirmName — ожидается «ci-doc»"
}
Очистка журнала подтверждается флагом confirm: true.
Даты и время
Все даты — ISO 8601 в UTC (2026-09-09T10:15:33.470Z). Фильтры from и to
в журнале принимают и смещение: 2026-09-09T00:00:00+03:00 разбирается верно,
пересчитывать в UTC руками не нужно.