Skip to main content
Back to all essays
System Design6 min read

Batch AI processing: reconcile every business record

A worked document-processing batch shows how to match unordered results, separate model success from usable output, and prevent stale updates.

Illustrative 120-record batch reconciliation: 112 provider successes and eight unresolved results become 102 ready records, seven needing review, three stale proposals and eight unresolved records.
Open full-size workflow diagram
Read the workflow steps
  1. Freeze a 120-record input manifest with unique IDs and observed source versions.
  2. Match provider results by custom_id rather than line order. The example has 112 successes and eight errored, expired or canceled requests.
  3. Validate the 112 outputs: 105 pass the application's content checks and seven need review.
  4. Refresh source records before release: three of the 105 proposals are stale, leaving 102 ready. Account for all 120 as 102 ready, seven review, three stale and eight unresolved.
Cover graphic for Batch AI processing: reconcile every business record, System Design

An overnight AI job reads a folder of supplier documents and returns a results file. The dashboard says the batch ended. A morning operator still needs to know which supplier records are ready, which documents failed and whether someone changed a record after the job started. Batch completion is a runtime status; the operating team needs an account of every input.

Anthropic's Message Batches API processes requests asynchronously at half the standard API price. Its documentation describes succeeded, errored, canceled and expired outcomes for individual requests. Results can arrive in a different order from the inputs and must be joined using custom_id. Anthropic: batch processing and result states

That makes a batch useful for work that can wait, such as preparing a document review queue or classifying an archive. It is a poor fit for a customer who needs an answer during a live call. Before building a batch worker, check whether the source system already imports structured fields reliably; an AI extraction step adds little when the data already has a dependable machine-readable format.

Freeze the input list before submitting work

For a hypothetical supplier-document job, I would create an input manifest with an internal item ID, source document version, destination record and prompt version. The manifest determines what the run owes the operator. Adding another file later creates another manifest entry or another run, so the completion count does not drift while results are arriving.

Keep provider request IDs separate from business identity. A custom_id can identify the request for a particular source version without including a customer name or personal information. The application retains the mapping locally. Store the provider batch ID after submission and record an uncertain submission outcome if the response is lost; guessing that no batch was created can duplicate paid processing.

Illustrative manifest entry, not a provider request schema
run_id: supplier-review-20261012
item_id: item_044
provider_custom_id: item_044_v7
source_document: doc_981
source_version: 7
destination_record: supplier_44
prompt_version: extract_3
provider_result: pending
application_result: unresolved

Join results by identity, then validate their meaning

A JSONL file's first line might correspond to the last input. Zipping result lines with the input array can silently put one supplier's fields on another supplier's record. Build an identity-based join that rejects unexpected IDs and detects duplicate results. An unmatched entry remains unresolved, even if the worker has reached the end of the file.

A provider success says the model request completed. The application still needs to check whether the response finished normally, can be parsed and contains supported values. Structured output constraints can help with schema shape; refusal or a token cutoff can still return an unusable output. Neither a matching schema nor a successful response proves that an extracted account number belongs to the document. Anthropic: structured outputs and invalid-output cases

For this job, I would require evidence locations for fields that the team intends to use. An absent field stays absent. A conflicting company name goes to review. Compare totals with the source using ordinary code where possible, and keep OCR failures distinct from interpretation mistakes. Those distinctions tell an operator whether to obtain a better scan, correct a value or ask the supplier.

Work through a 120-record example

The following numbers are a worked illustration, not measured results. The manifest contains 120 items. The provider returns 112 succeeded results, five errors, two expired requests and one canceled request. Content checks find 105 usable outputs among the successes; seven need operator review. A fresh read then finds that three destination records changed during the job. Their proposed updates are stale, leaving 102 ready records.

Illustrative reconciliation of all 120 inputs
Application statusCountNext owner or step
Ready for the permitted release process102Release worker or reviewer checks exact fields
Content needs review7Operator inspects missing or conflicting evidence
Source or destination changed3Refresh evidence and prepare a new proposal
Provider error, expiry or cancellation8Worker or operator resolves each outcome
Total manifest items120Every input appears once in the account

The queue must preserve those categories after a worker restart. A headline of 112 successes would overstate ready work. A headline of 102 completed records would also be wrong if those records have only been prepared. After release, record the actual write result separately. If the destination rejects an update, the item remains visible with its reason and owner.

Retain failures without repeating successful work

Anthropic documents a 24-hour expiry for requests that have not been processed and makes results available for 29 days from batch creation. Canceling a batch can leave partial results. Collect and preserve the per-item outcomes within the provider's availability period, using the retention and access rules appropriate to your data. Anthropic: expiry, partial results and retention window

Our proposed worker would select a retry candidate only after classifying its failure. An invalid request needs repair. An expired request needs a new attempt if the work still applies. A canceled item might reflect an operator's deliberate decision to stop. Preserve attempt history and associate a new request with the same business item; do not erase the earlier result to make the run appear clean.

Before retrying, recheck the source version and intended destination. A document corrected overnight should not be processed again as though its old version were current. Keep generation retries separate from destination-write retries. The latter need the destination API's own duplicate-protection and recovery contract. Read: recovering an uncertain external action

Prove the operator can close the run

Suggested acceptance tests for the application, not tests we ran
Injected conditionExpected visible result
Results returned in reverse orderCorrect fields remain attached to their original item IDs
One result is missingIts manifest entry stays unresolved
The same result is imported twiceNo second destination update occurs
Model output has a missing source valueOperator sees missing evidence, without an invented field
Destination record changes during the runOld proposal cannot overwrite the correction
Worker restarts during results importPreviously handled items and unresolved work survive

Ask the operating team to close a small test run using its normal screen. They should be able to identify each unresolved item, see the source and decide its next step. Track accepted outputs, correction time, stale proposals and actual writes separately. Then calculate cost per usable record, including retries and review time. The API discount applies to inference charges, so it cannot by itself establish a saving for the whole operation.

Start with one document queue that already has an owner and a defined next step. Keep destination writes disabled until the identity join and recovery tests pass. The first release should make the morning queue easier to resolve, with every outstanding record accounted for.

Putting this into practice?