Из-за временных ограничений на территории РФ наблюдаются проблемы с оплатой. Если платёж не проходит, оставьте запрос в службу поддержки.Служба поддержки работает 24/7 — мы всегда на связи по вопросам хостинга и серверов.Открыт прием заявок на аренду выделенных серверов и размещение оборудования в дата-центре.Напоминаем: рекомендуем включить резервное копирование для дополнительной защиты данных.Доступна новая линейка VPS/VDS с NVMe-дисками и увеличенной производительностью.Технические работы на части серверов завершены. Все сервисы работают в штатном режиме.
Статья9 мин чтенияПросмотры1

Пакетный API вернул 200: как не потерять ошибки отдельных товаров

Проверяем каждый элемент пакетного ответа: обнаруживаем пропущенный результат, не повторяем успешные SKU и безопасно возобновляем только временно неудавшуюся операцию.

Комментарии 0

Сотрудница сортировочной линии проверяет одну из четырёх посылок, отмеченную жёлтой биркой
В этой статье

Пакетный запрос может получить HTTP 200, хотя часть товаров отклонена, один результат потерян, а ещё один нужно повторить. Поэтому верхний код ответа подтверждает только приём и разбор пакета по правилам конкретного API. Готовность обмена проверяют по результату каждого элемента: все отправленные идентификаторы должны встретиться в ответе ровно один раз, а успехи, постоянные отказы и временные сбои нужно разнести по разным веткам.

Ниже — воспроизводимая модель без сети, файлов и рабочего магазина. Она показывает два края, из-за которых красивый зелёный ответ быстро становится бухгалтерией: отсутствующий результат блокирует приёмку всего пакета, а повтор временно неудавшейся позиции использует прежний operation_id и не создаёт второе изменение.

Почему HTTP 200 недостаточно

RFC 9110 определяет 200 как успешное выполнение самого HTTP-запроса; смысл содержимого зависит от метода. Конкретный пакетный контракт может использовать успешный верхний ответ для контейнера, внутри которого отдельные операции завершились по-разному.

Microsoft Graph приводит документированный пример: разбор JSON-пакета обычно возвращает 200, но каждый вложенный ответ имеет собственный id и status; порядок вложенных ответов может отличаться от порядка запросов. Это пример конкретного API, а не универсальная схема для 1С-Битрикс или любого поставщика.

Код 207 Multi-Status тоже не является волшебной заменой: RFC 4918 определяет его для WebDAV и всё равно требует читать тело с результатами отдельных ресурсов. Использовать 200, 207 или другой верхний статус нужно ровно так, как описывает контракт метода; клиенту в любом случае нужен проверяемый результат каждой операции.

Минимальный контракт результата

Состояние элемента

Что подтверждено

Действие клиента

applied

Изменение применено либо повтор распознан

Не отправлять заново без новой бизнес-команды

rejected

Запрос понят, но данные или права не подходят

Исправить причину; автоматический повтор того же тела не поможет

retryable

Временный сбой по правилам API

Повторить только этот элемент после допустимой паузы

нет результата

Судьба элемента неизвестна

Не считать пакет завершённым; выполнить сверку по operation_id

Названия состояний здесь учебные. В рабочем API это могут быть HTTP-коды вложенных ответов, собственные коды или отдельные объекты ошибок. Важен инвариант: один отправленный operation_id — один явно классифицированный результат.

Стенд и начальные условия

  • CPython 3.12, только стандартная библиотека; проверка выполнена 30 октября 2026 года.

  • Изолированная модель работает только в памяти: нет сети, файлов, базы данных и настоящего каталога.

  • В пакете четыре торговые позиции с кодами SKU-RED-S, SKU-RED-M, SKU-BLUE-S и SKU-BLUE-M; изменяется только числовой остаток stock.

  • operation_id op-101…op-104 создаётся один раз для каждой команды и не меняется при сетевом повторе.

  • Учебные состояния applied, rejected и retryable заданы самим примером; они не приписываются конкретной версии 1С-Битрикс.

Сохраните пример в новом временном каталоге как batch_result_lab.py. Он не выполняет операций записи или удаления вне самого файла, созданного пользователем.

from __future__ import annotations from dataclasses import dataclass @dataclass(frozen=True) class Update: operation_id: str sku: str stock: int @dataclass(frozen=True) class Result: operation_id: str status: str error_code: str | None = None def validate_batch(requests: list[Update], results: list[Result]) -> dict[str, Result]: requested = {item.operation_id for item in requests} if len(requested) != len(requests): raise ValueError("duplicate operation_id in request") indexed: dict[str, Result] = {} for item in results: if item.operation_id not in requested: raise ValueError(f"unknown result: {item.operation_id}") if item.operation_id in indexed: raise ValueError(f"duplicate result: {item.operation_id}") if item.status not in {"applied", "rejected", "retryable"}: raise ValueError(f"unknown status: {item.status}") indexed[item.operation_id] = item missing = requested - indexed.keys() if missing: raise ValueError(f"missing results: {sorted(missing)}") return indexed class FakeCatalogApi: def __init__(self) -> None: self.stock: dict[str, int] = {} self.applied_operations: set[str] = set() self.calls = 0 def retry(self, update: Update) -> Result: self.calls += 1 if update.operation_id in self.applied_operations: return Result(update.operation_id, "applied", "duplicate_ack") self.stock[update.sku] = update.stock self.applied_operations.add(update.operation_id) return Result(update.operation_id, "applied") requests = [ Update("op-101", "SKU-RED-S", 7), Update("op-102", "SKU-RED-M", -1), Update("op-103", "SKU-BLUE-S", 4), Update("op-104", "SKU-BLUE-M", 0), ] incomplete = [ Result("op-101", "applied"), Result("op-102", "rejected", "stock_must_be_non_negative"), Result("op-103", "retryable", "supplier_temporarily_unavailable"), ] try: validate_batch(requests, incomplete) except ValueError as error: print(f"incomplete: {error}") complete = incomplete + [Result("op-104", "applied")] indexed = validate_batch(requests, complete) retry_ids = [key for key, value in indexed.items() if value.status == "retryable"] rejected_ids = [key for key, value in indexed.items() if value.status == "rejected"] print(f"accepted=2 rejected={rejected_ids} retry={retry_ids}") api = FakeCatalogApi() retry_request = next(item for item in requests if item.operation_id == retry_ids[0]) first = api.retry(retry_request) second = api.retry(retry_request) assert first.status == "applied" assert second.error_code == "duplicate_ack" assert api.stock == {"SKU-BLUE-S": 4} assert api.calls == 2 and len(api.applied_operations) == 1 print( "retry: " f"first={first.status} second={second.error_code} " f"calls={api.calls} unique_changes={len(api.applied_operations)}" )

Команда запуска:

python3 batch_result_lab.py

Полученный вывод:

incomplete: missing results: ['op-104'] accepted=2 rejected=['op-102'] retry=['op-103'] retry: first=applied second=duplicate_ack calls=2 unique_changes=1

Процесс завершился с кодом 0. Проверки assert дополнительно подтверждают, что два вызова с op-103 дают одно уникальное изменение остатка: первый применяет значение 4 для SKU-BLUE-S, второй получает подтверждение уже принятой операции.

Что подтвердил прогон

Проверка

Ожидаемый результат

Наблюдение

В ответе нет op-104

Пакет нельзя принять как полный

Валидатор остановился с missing results

Два элемента applied

Не повторять успешные позиции

В очередь повтора попал только op-103

op-102 rejected

Постоянный отказ отделён от временного

Причина stock_must_be_non_negative сохранена отдельно

op-103 retryable

Повторить с прежним operation_id

Первый повтор установил остаток товара

Ответ на повтор потерян

Ещё один повтор не меняет данные второй раз

duplicate_ack; два вызова, одно уникальное изменение

Как принимать пакет в интеграции магазина

  1. Сохранить состав отправленного пакета: operation_id, SKU или другой устойчивый идентификатор торговой позиции, поле, значение и версию контракта.

  2. После ответа сравнить множества идентификаторов. Неизвестный, повторяющийся или отсутствующий результат — отдельная ошибка протокола, а не повод угадать состояние.

  3. Зафиксировать applied как завершённые операции и больше не включать их в технический повтор.

  4. Для rejected сохранить машинный код и безопасные детали: неверный SKU, недопустимое значение, отсутствие права или конфликт версии требуют разных исправлений.

  5. Повторять только retryable по правилам поставщика: с допустимой паузой, ограниченным бюджетом и прежним operation_id.

  6. После неоднозначного тайм-аута сначала запросить состояние операции, если API это позволяет. Новый operation_id может превратить повтор в новую команду.

Счётчики пакета полезно показывать раздельно: отправлено 4, применено 2, отклонено 1, ожидает повтора 1. Формулировка «успешно 4 из 4» допустима только после проверки бизнес-результата всех четырёх операций. Размер массива, HTTP 200 и отсутствие исключения у клиента этого не доказывают.

Где проходит граница с 1С-Битрикс

Пример не проверяет штатный обмен с 1С и не обещает формат ответа какой-либо редакции 1С-Битрикс: Управление сайтом. Перед внедрением нужно открыть документацию конкретного модуля и версии, уточнить направление обмена, способ идентификации товара и торгового предложения, поля цены и остатка, права пользователя, максимальный размер пакета и правила повтора.

Для каталога отдельно проверьте, что SKU однозначно указывает нужную торговую позицию, а не только родительский товар. Ошибка права на изменение цены не должна маскироваться под временную недоступность. Изменение остатка на ноль — содержательное значение, а не пустой результат. Если метод не поддерживает ключ идемпотентности или поиск операции, безопасную схему повтора придётся проектировать на стороне интеграции.

Крайние случаи, которые стоит добавить на стенде

  • вложенные результаты пришли в другом порядке;

  • один operation_id встречается дважды или в ответе появился неизвестный идентификатор;

  • часть позиций получила отказ из-за прав, часть — из-за неверного SKU;

  • ответ потерян после фактического применения изменения;

  • цена или остаток изменились между чтением и записью, и API поддерживает версию либо условие If-Match;

  • пакет принят, но последующая асинхронная обработка ещё не завершена.

Критерий готовности простой: для каждого отправленного элемента известен ровно один проверяемый исход; успешные элементы не повторяются, постоянные отказы попадают на исправление, временные сбои повторяются с тем же идентификатором, а неизвестная судьба не превращается в выдуманный успех. Верхний HTTP 200 после этого остаётся полезным транспортным фактом — просто больше не пытается изображать весь отчёт.

Для настройки и диагностики обмена на копии проекта полезно заранее подготовить согласованный набор SKU и ожидаемых значений. Если нужен разбор конкретного маршрута 1С-Битрикс и внешней системы, следующий шаг — сопоставить этот набор с журналами задания и фактическим состоянием каталога.

Обсуждение 0

Делись опытом и задавай вопросы. Комментарии без ссылок появляются после проверки редактором.

Пока никто не написал. Начни обсуждение.