Сеть ненадёжна: запрос может завершиться таймаутом, хотя на сервере документ уже создан. Наивный повтор породит дубль. В JSON API 1.2 МойСклад нет заголовка Idempotency-Key, зато есть рабочие механизмы идемпотентности — поля syncId и externalCode, которые делают повторные запросы безопасными.
Почему повторные запросы опасны
Классический сценарий рассинхрона: интеграция отправляет POST /entity/customerorder, сервер создаёт заказ и отвечает, но ответ теряется по дороге — клиент видит таймаут. Не зная, прошла операция или нет, интеграция повторяет POST — и в МойСклад появляется два одинаковых заказа.
Операции по-разному чувствительны к повторам:
| Метод | Идемпотентность |
|---|---|
GET | Безопасен — повтор не меняет данные |
PUT по id | Идемпотентен — повтор перезапишет те же поля |
POST (создание) | Опасен — каждый повтор создаёт новый объект |
DELETE | Условно безопасен — повтор вернёт 404 |
https://api.moysklad.ru/api/remap/1.2. Главная зона риска — создание сущностей через POST, именно её нужно защищать от дублей.syncId — уникальный ключ синхронизации
У большинства сущностей МойСклад есть поле syncId — UUID, который задаёт внешняя система и который уникален в пределах аккаунта. Это ближайший аналог идемпотентного ключа.
При создании передайте свой заранее сгенерированный UUID:
POST /api/remap/1.2/entity/customerorder{ "organization": {...}, "agent": {...}, "syncId": "a1b2c3d4-0000-0000-0000-000000000001" }
Если такой syncId уже существует, сервис не создаст дубль, а вернёт ошибку уникальности. Логика ретрая становится простой: сгенерировали syncId один раз на бизнес-операцию, и сколько бы раз ни повторили запрос — заказ будет один.
externalCode и upsert по внешнему ключу
Второй механизм — externalCode, внешний код сущности. Он тоже задаётся вашей системой и удобен, когда объекту соответствует запись во внешней базе (товар из 1С, заказ с сайта).
Паттерн безопасного создания через externalCode — проверка перед вставкой:
- перед POST запросите
GET /entity/customerorder?filter=externalCode={ключ}; - если объект найден — сделайте PUT (обновление);
- если не найден — сделайте POST (создание).
Такой upsert идемпотентен: повтор всей операции либо обновит уже созданный объект, либо создаст недостающий, но дубля не породит. Для массового обмена это надёжнее, чем полагаться только на коды ответов.
Стратегия ретраев
Повторять запросы нужно осмысленно — не каждый сбой требует ретрая. Ориентируйтесь на код ответа:
5xxи сетевые таймауты — повторять можно, операция могла не дойти или не завершиться;429 Too Many Requests— повторять после паузы, соблюдая заголовки лимитов;4xx(кроме 429) — не повторять, это ошибка данных, повтор не поможет.
Для повторов используйте экспоненциальную задержку с джиттером, ограничьте число попыток и всегда сопровождайте POST полем syncId или проверкой externalCode. Тогда даже несколько ретраев подряд не создадут дублей.
429 ориентируйтесь на заголовки лимитов (X-RateLimit-Remaining, X-Lognex-Retry-After) и выдерживайте паузу, а не долбите сервис повторами.Что делать при таймауте создания
Самая коварная ситуация — таймаут на POST, когда неизвестно, создан объект или нет. Алгоритм разрешения неопределённости:
- после таймаута не повторяйте POST вслепую;
- сделайте GET с фильтром по
syncIdилиexternalCode, который вы отправляли; - если объект нашёлся — операция прошла, ретрай не нужен;
- если не нашёлся — безопасно повторить POST с тем же ключом.
Этот приём превращает неидемпотентный POST в безопасный: наличие уникального ключа позволяет достоверно узнать состояние на сервере, а не гадать по потерянному ответу.
Идемпотентность в bulk-операциях
В массовых запросах правила те же, но масштабируются. При bulk POST /entity/{type} с массивом объектов:
- задавайте каждому объекту свой
syncIdилиexternalCode; - если у объекта есть
metaс id — он обновляется, если нет — создаётся; - для полного upsert сопоставляйте объекты по внешнему ключу перед отправкой пачки.
При повторе bulk-пачки объекты с уже существующими ключами не задвоятся, а обновятся. Это делает регулярную синхронизацию каталога устойчивой к обрывам: прерванную выгрузку можно перезапустить целиком без риска дублей.
Итог
Идемпотентность в JSON API 1.2 достигается не заголовком, а полями syncId и externalCode. GET и PUT по id безопасны сами по себе, а рискованный POST защищают уникальным ключом, генерируемым один раз на бизнес-операцию. Ретраи делают только для 5xx, таймаутов и 429, а неопределённость после таймаута снимают проверочным GET по ключу.
Когда обмен между МойСклад, сайтом на 1С-Битрикс и CRM должен переживать обрывы сети без дублей заказов и товаров, мы проектируем такие интеграции с идемпотентностью, очередями и логированием на уровне архитектуры. Построим обмен, которому не страшны таймауты и повторы.
Частые вопросы
Есть ли в JSON API МойСклад заголовок Idempotency-Key?
Нет. Идемпотентность обеспечивается полями syncId и externalCode, которые задаёт внешняя система и которые уникальны в пределах аккаунта.
Что такое syncId и как он защищает от дублей?
Это UUID, задаваемый вашей системой при создании сущности. Если объект с таким syncId уже есть, сервис не создаст дубль, а вернёт ошибку уникальности.
Когда syncId генерировать?
Один раз на бизнес-операцию, до первого запроса, и сохранять вместе с ней. Если генерировать новый UUID на каждый ретрай, защита от дублей теряется.
Как сделать upsert по externalCode?
Перед POST запросите объект фильтром по externalCode. Если найден — обновите через PUT, если нет — создайте через POST. Повтор такой операции не породит дубль.
Какие ответы стоит повторять?
Ошибки 5xx, сетевые таймауты и 429 после паузы. Ответы 4xx кроме 429 повторять бессмысленно — это ошибки данных, которые ретрай не исправит.
Что делать при таймауте на создание объекта?
Не повторять POST вслепую. Сначала сделайте GET с фильтром по syncId или externalCode: если объект нашёлся — операция прошла, если нет — можно безопасно повторить.
Идемпотентны ли PUT и GET?
Да. GET не меняет данные, а PUT по id перезаписывает те же поля, поэтому их повтор безопасен. Опасно только создание через POST без уникального ключа.
Как обеспечить идемпотентность в bulk-запросах?
Задавайте каждому объекту syncId или externalCode и сопоставляйте по внешнему ключу перед отправкой. Тогда повтор пачки обновит существующие объекты, а не задвоит их.
Поможем с настройкой и поддержкой Для разработчиков
Поможем с настройкой, доработкой и поддержкой Для разработчиков.