Payments are experiencing issues due to temporary restrictions in Russia. If your payment does not go through, please submit a support request.Our support team is available 24/7 — we are always here to help with hosting and server issues.We are now accepting requests for dedicated server rental and colocation services in our data center.Reminder: we recommend enabling backups for additional data protection.A new VPS/VDS lineup with NVMe storage and improved performance is now available.Maintenance work on some servers has been completed. All services are operating normally.
Article9 min readViews1

Batch API Returned 200: How Not to Lose Errors for Individual Items

Check every element of the batch response: detect missing results, avoid resubmitting successful SKUs, and safely resume only temporarily failed operations.

Comments 0

A sorting line employee inspects one of four packages marked with a yellow tag
In this article

A batch request may return HTTP 200 even if some items were rejected, one result was lost, and another requires retrying. Therefore, the top-level status code only confirms that the package was received and parsed according to the rules of the specific API. Readiness for exchange is verified by the result of each element: all sent identifiers must appear in the response exactly once, and successes, permanent failures, and temporary errors must be routed to separate branches.

Below is a reproducible model without a network, files, or a live store. It shows two edge cases where a successful response quickly turns into accounting issues: a missing result blocks acceptance of the entire batch, and retrying a temporarily failed item uses the original operation_id and does not create a second change.

Why HTTP 200 Is Not Enough

RFC 9110 defines 200 as a successful execution of the HTTP request itself; the meaning of the content depends on the method. A specific batch contract may use a successful upper response as a container within which individual operations complete differently.

Microsoft Graph provides a documented example: parsing a JSON batch typically returns 200, but each nested response has its own id and status; the order of nested responses may differ from the order of requests. This is an example of a specific API, not a universal scheme for 1C-Bitrix or any provider.

Status code 207 Multi-Status is also not a magic replacement: RFC 4918 defines it for WebDAV and still requires reading the body with results for individual resources. Use 200, 207, or another top-level status exactly as the method contract describes; the client still needs a verifiable result for each operation.

Minimal result contract

Item status

What is confirmed

Client action

applied

Change applied or retry recognized

Do not resend without a new business command

rejected

Request understood, but data or permissions do not match

Fix the cause; automatically retrying the same payload will not help

retryable

Temporary failure per API rules

Retry only this item after an acceptable pause

no result

The fate of the element is unknown

Do not consider the package complete; perform a check by operation_id

The state names here are illustrative. In a production API, these may be HTTP codes from nested responses, custom codes, or separate error objects. The invariant is key: one sent operation_id yields one explicitly classified result.

Test environment and initial conditions

  • CPython 3.12, standard library only; check performed on October 30, 2026.

  • The isolated model operates only in memory: no network, files, database, or real directory.

  • The package contains four product variants with SKU codes SKU-RED-S, SKU-RED-M, SKU-BLUE-S, and SKU-BLUE-M; only the numeric stock remainder changes.

  • operation_id op-101…op-104 is created once for each command and does not change during network retries.

  • The sample defines the applied, rejected, and retryable states; they are not tied to a specific 1C-Bitrix version.

Save the sample in a new temporary directory as batch_result_lab.py. It performs no write or delete operations outside the user-created file.

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)}" )

Launch command:

python3 batch_result_lab.py

Output received:

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

The process completed with code 0. The assert checks further confirm that the two calls with op-103 produce a single unique inventory change: the first applies the value 4 for SKU-BLUE-S, and the second confirms the already accepted operation.

What the run confirmed

Check

Expected result

Observation

No op-104 in response

Package cannot be accepted as complete

Validator stopped with missing results

Two elements applied

Do not repeat successful items

Only op-103 entered the retry queue

op-102 rejected

Permanent rejection is separated from temporary failure

The reason stock_must_be_non_negative is stored separately

op-103 is retryable

Retry with the original operation_id

The first retry set the product stock

The response to the retry was lost

Another repetition does not change the data the second time

duplicate_ack; two calls, one unique change

How to accept a package in a store integration

  1. Preserve the contents of the sent package: operation_id, SKU, or another stable identifier for the product variant, the field, its value, and the contract version.

  2. After the response, compare the sets of identifiers. An unknown, repeated, or missing result is a separate protocol error, not a reason to guess the state.

  3. Mark applied operations as completed and exclude them from further technical retries.

  4. For rejected items, retain the machine code and safe details: an invalid SKU, an unacceptable value, missing permissions, or a version conflict require different fixes.

  5. Retry only retryable operations according to the provider's rules: with an acceptable pause, a limited budget, and the same operation_id.

  6. After an ambiguous timeout, first request the operation status if the API allows it. A new operation_id can turn a retry into a new command.

It is useful to display package counters separately: 4 sent, 2 applied, 1 rejected, 1 awaiting retry. The phrasing "4 out of 4 successful" is acceptable only after verifying the business result of all four operations. Array size, HTTP 200, and the absence of an exception on the client side do not prove this.

Where the boundary with 1C-Bitrix lies

This example does not verify standard exchange with 1C and does not promise the response format for any edition of 1C-Bitrix: Site Management. Before implementation, you must open the documentation for the specific module and version, clarify the exchange direction, product and product variant identification method, price and stock fields, user permissions, maximum package size, and retry rules.

For the catalog, separately verify that the SKU unambiguously points to the required product variant, not just the parent product. A permission error for price changes must not be masked as temporary unavailability. Setting stock to zero is a meaningful value, not an empty result. If the method does not support an idempotency key or operation search, a safe retry scheme must be designed on the integration side.

Edge cases that should be added to the staging environment

  • nested results arrived in a different order;

  • one operation_id appears twice, or an unknown identifier appeared in the response;

  • some items were rejected due to permissions, others due to an incorrect SKU;

  • the response was lost after the change was actually applied;

  • the price or stock changed between reading and writing, and the API supports versioning or the If-Match condition;

  • the package was accepted, but subsequent asynchronous processing is not yet complete.

Simple readiness criterion: for each submitted item, exactly one verifiable outcome is known; successful items are not repeated, permanent failures go to correction, temporary failures are retried with the same identifier, and an unknown fate does not turn into a fabricated success. An upper HTTP 200 remains a useful transport fact—simply no longer attempts to represent the entire report.

To configure and diagnose the exchange on a project copy, it is useful to prepare in advance a consistent set of SKUs and expected values. If you need to analyze a specific 1C-Bitrix and external system route, the next step is to match this set with the job logs and the actual state of the catalog.

Discussion 0

Share your experience and ask questions. Comments without links appear after editorial review.

No comments yet. Start the discussion.