Batch API partial failures are easy to miss because the outer HTTP request can succeed while individual operations fail. A production design needs per-item outcomes, stable identifiers and a retry boundary that does not repeat work already accepted.
Treat the batch envelope and each item as separate results
A batch endpoint usually returns one transport response for many application operations. Those two layers answer different questions. The envelope status tells the client whether the server could parse and process the batch request. Each item status tells the client what happened to that operation.
Microsoft Graph makes the distinction explicit. Its JSON batching endpoint can return HTTP 200 for a valid batch even when one or more responses inside the body carry errors. The API also allows up to 20 requests per batch, assigns an ID to every request, and may return item responses in a different order. A client that checks only the outer status, or assumes response order matches request order, can quietly mark failed work as complete.
Design the batch response as a collection of independent result records:
- the stable request ID supplied by the client
- the operation type and target
- the final item status
- a response payload or a structured error
- a retry hint, including any server-provided delay
- an attempt number and correlation ID for tracing
Persist that record before the worker acknowledges the batch. It becomes the evidence used for retries, user support and reconciliation.
Define an item state machine before writing retry code
A Boolean success field is too small for real failure handling. Each item needs a state that separates terminal results from temporary ones. A useful starting point is accepted, completed, retryable failure, permanent failure and blocked by dependency.
The mapping must be specific to the operation. A 429 response is normally retryable after the server’s stated delay. A validation error caused by a missing required field is permanent until the input changes. An ambiguous timeout is more dangerous because the server may have committed the operation before the client lost the response.
Store the raw provider status with your internal classification. The original status helps operators verify the mapping later, while the internal state gives application code one consistent contract. If your API talks to several downstream systems, do not let each worker invent its own interpretation of retryable.
This is the same boundary problem that appears in API pagination consistency: a client needs a stable contract for what the server observed, not an optimistic guess based on transport success.
Retry the failed set, not the original batch
When three items fail in a batch of twenty, the retry input should contain those three items. Replaying all twenty repeats side effects for the seventeen that may already be complete.
Microsoft’s throttling guidance is concrete here. Batched requests are evaluated individually, so a batch can return 200 while individual items return 429. The client should use the longest Retry-After value among the failed items, then send a new batch containing only those throttled operations. Microsoft’s SDK retry handlers do not automatically retry throttled requests inside a batch.
Your retry planner should therefore read the saved result ledger, select only retryable items whose delay has elapsed, and issue a new attempt that points back to the original request IDs. Keep a maximum attempt count and a time limit. After either limit, move the item to manual review or a dead-letter path with the evidence needed to decide what happened.
Make repeated writes safe
HTTP defines PUT, DELETE and safe methods as idempotent, which means multiple identical requests have the same intended effect as one. POST is not idempotent by definition. Product APIs often use POST for actions such as creating a payment, sending a notification or provisioning an account, so the transport method alone does not make a retry safe.
For side-effecting operations, accept an idempotency key or another stable business key at the item level. Save the key beside the operation result and return the first committed result when the same key arrives again. Scope the key narrowly enough to avoid collisions, but long enough to cover the full retry window.
The store that checks the key and commits the side effect needs an atomic relationship. A separate “seen” cache written before or after the business record can fail between the two writes and create either missing work or duplicates. If a single transaction is impossible, use a durable inbox or outbox and make the recovery behavior explicit.
This resembles feature flag rollback boundaries: a switch can stop new execution, but it cannot reverse an external side effect that already happened.
Model dependencies instead of relying on array order
Some batch items cannot run independently. A file upload may require a parent record, or a permission assignment may require a newly created user. Represent that relationship in the request and in the result ledger.
Microsoft Graph supports a dependsOn field for sequential execution. When a dependency fails, a dependent request can receive 424. That status is different from a failure in the dependent operation itself. Retrying the child before the parent succeeds only adds noise.
Build a small dependency graph and reject cycles before execution. When a parent fails permanently, mark its children blocked with the parent request ID. When the parent later succeeds, release only the children whose prerequisites are now complete. Avoid hidden dependencies based on list position because batching systems may reorder or parallelize work.
Expose partial completion to people and operators
A progress screen should not collapse a mixed batch into a red or green banner. Show counts for completed, retrying, blocked and permanently failed items, then let the operator inspect the failed set. If a retry can charge money, send a message or change access, name that effect before offering a manual retry.
Operational metrics should use items as the denominator. Track item success rate, first-attempt success, retry recovery, permanent failures, queue age and duplicate suppression. Batch-level success can look healthy while users are missing a small but important share of outcomes.
Logs need the batch ID, original item ID, attempt ID, classification, downstream request ID and final state. Do not put sensitive payloads into the log merely to make debugging easier. Record references and approved diagnostic fields instead. For evidence design, what an audit log can prove gives a useful limit: a log proves only what the instrumented system observed and preserved.
Test the recovery path with mixed outcomes
A happy-path batch test proves little about the retry boundary. Add cases in which one item is invalid, one is throttled, one times out after commit, one is blocked by a failed dependency and the remaining items succeed. Then verify that the next attempt contains only the eligible failures.
Also test a process crash after the downstream side effect but before the local result is saved. The expected outcome should be written down: deduplicate the second attempt, reconcile from the downstream system, or stop for manual review. An undefined answer is where duplicate work usually enters.
Finally, test old retries after a deployment changes validation rules or payload shape. Store enough version information to interpret the original request, and decide whether pending items use the old contract, migrate to the new one or fail for review.
Reporting & Sources
This guide was checked on October 23, 2026 against Microsoft Graph JSON batching, Microsoft Graph throttling guidance and RFC 9110 HTTP semantics. The Microsoft limits and retry behavior are specific to Graph; use the equivalent contract for another provider.
Questions
Can a batch request return HTTP 200 when some items failed?
Yes. A 200 response can mean the batch envelope was valid while individual item responses still contain errors.
Should a client retry the whole batch after one item is throttled?
No. Retry only the failed, retryable items after the applicable server delay, while preserving their original request identities.
How do idempotency keys prevent duplicate work?
They let the server recognize repeated attempts for the same logical operation and return the committed result instead of applying the side effect again.
What should happen when one batch item depends on another?
Represent the dependency explicitly, block the child when its prerequisite fails, and release it only after every required parent has completed.
Which metrics reveal batch API partial failures?
Use item-level success, first-attempt success, retry recovery, permanent failures, queue age and duplicate suppression rather than batch-level status alone.





