Back to Blog

Claude Batch API: Retry Failed Items, Not the Whole Job

Tutorials and Guides2853
Claude Batch API: Retry Failed Items, Not the Whole Job

You submit 500 product descriptions to Claude Sonnet for translation. The batch finishes overnight. In the morning, the results file contains translations, several errors, and a handful of items that expired.

Should you run the job again?

Only after you know which work is missing. Resubmitting all 500 descriptions mixes successful translations with failed attempts. It can create unnecessary charges, overwrite approved copy, and make it difficult to explain which version reached the storefront.

The useful recovery artifact is a manifest with one row per source item. It tells you what Claude returned, whether your application accepted it, and what action should happen next.

This guide shows how to build that manifest for Claude Message Batches and what to verify before purchasing access through an API gateway.

The batch status is not the product status

An ended batch tells you to inspect its results. It does not mean every individual request succeeded or that every generated translation is ready to publish.

Keep three levels separate:

LevelQuestion it answers
Batch lifecycleIs the service still processing this batch?
Individual API resultWhat happened to this particular request?
Application acceptanceDoes the returned translation meet our requirements?

A successful model response can still omit a product dimension or translate a trademark that must remain unchanged. That item needs editorial review, not the same retry treatment as a transient API error.

Likewise, an expired request has not produced an accepted translation. Your application must not mark it complete simply because the surrounding batch ended.

Anthropic documents the interface in its Message Batches guide. Check the current model catalog and the service you intend to use before choosing a Sonnet model identifier.

Give every request an identity before submission

Suppose your source record is product lamp-204, revision 7, and the destination language is German. A request identifier such as lamp-204-r7-de is easier to reconcile than an unexplained integer.

The exact naming convention is yours. Keep it within the API’s documented constraints, make it unique within the batch, and avoid embedding customer information.

Your local manifest should record more than that identifier:

json
{
  "custom_id": "lamp-204-r7-de",
  "source_id": "lamp-204",
  "source_revision": "7",
  "target_language": "de",
  "prompt_version": "catalog-translation-v3",
  "attempt": 1
}

A revision matters. If someone changes the source description while the batch is running, the returned translation belongs to revision 7. It should not overwrite a record now serving revision 8.

A prompt version matters for the same reason. Translating a description with an older terminology guide and translating it with a new guide are different jobs, even if they use the same model.

Keep the model’s job small enough to validate

For a product translation, ask Claude to translate the supplied description and preserve the SKU, measurements, and any terms your business marks as fixed.

Here is an illustrative request item. Replace ACCOUNT_MODEL_ID before submission:

json
{
  "custom_id": "lamp-204-r7-de",
  "params": {
    "model": "ACCOUNT_MODEL_ID",
    "max_tokens": 300,
    "messages": [
      {
        "role": "user",
        "content": "Translate into German for a product catalog. Preserve BX-204 and 18 cm exactly. Return only the translated description. Source: BX-204 is an adjustable desk lamp with an 18 cm base."
      }
    ]
  }
}

Use a few non-sensitive records to verify the route before sending a large catalog. Include an ordinary product, a product with measurements, and a deliberately difficult brand term.

The point of this small trial is to inspect the actual result shape and the quality of translated text. One successful synchronous Messages request does not establish that a gateway exposes the native asynchronous batch lifecycle.

Capture the result before you transform it

Once the batch has ended, retrieve its individual results according to the selected service’s documentation. Save the raw result file or stream before transforming it into application records. Associate it with the batch ID and original manifest.

Do not join results by line position. Match them by custom_id.

Why does this matter? A results file can look perfectly healthy if you only inspect its first few lines. If the application pairs output line 12 with input line 12, an otherwise correct translation can be attached to the wrong product. The generated text may not repeat the product name, so the error can survive a quick editorial check.

Check the identity mapping before inspecting quality:

An unknown or duplicated ID should enter an investigation queue. Do not silently discard it or assign it to the nearest source record.

Build a recovery report locally

The following script reads a local manifest and a JSONL results file. It makes no model calls and does not resubmit anything.

For this example, manifest.json is an array of records containing custom_id. Each result line contains custom_id and a nested result object.

python
import json
from pathlib import Path

manifest = json.loads(Path("manifest.json").read_text(encoding="utf-8"))
expected = {row["custom_id"]: row for row in manifest}

if len(expected) != len(manifest):
    raise ValueError("Duplicate custom_id in input manifest")

seen = {}
anomalies = []

with Path("results.jsonl").open(encoding="utf-8") as source:
    for line in source:
        if not line.strip():
            continue
        row = json.loads(line)
        key = row["custom_id"]
        if key not in expected:
            anomalies.append({"custom_id": key, "issue": "unknown_id"})
            continue
        seen.setdefault(key, []).append(row["result"])

report = []
for key, source in expected.items():
    matches = seen.get(key, [])
    if not matches:
        action = "investigate_missing_result"
        status = "missing"
    elif len(matches) != 1:
        action = "investigate_duplicate_result"
        status = "duplicate"
    else:
        result = matches[0]
        status = result.get("type", "unknown")
        action = {
            "succeeded": "validate_translation",
            "errored": "inspect_error_before_retry",
            "expired": "check_source_revision_before_retry",
            "canceled": "check_cancellation_intent",
        }.get(status, "investigate_unknown_status")

    report.append({
        "custom_id": key,
        "source_revision": source.get("source_revision"),
        "status": status,
        "next_action": action,
    })

Path("recovery-report.json").write_text(
    json.dumps({"items": report, "anomalies": anomalies}, indent=2),
    encoding="utf-8",
)

This deliberately stops short of automatic retries. An error category alone is not enough to decide whether another paid request is appropriate.

Turn each outcome into the right next action

Succeeded: validate the content. In the sample product, check that BX-204 and 18 cm survived, that the response contains a finished translation, and that the source revision remains current. The presence of both strings is useful evidence, but not a complete translation-quality assessment.

Errored: inspect the error. A malformed request needs a correction. Sending the identical payload again is unlikely to help. A temporary service failure may justify a bounded retry after you verify that no accepted output already exists.

Expired: check whether the item is still needed. If its source description changed, create work for the latest revision instead of retrying stale copy.

Canceled: determine who canceled the work and why. A deliberate cancellation is not an invitation to restart it automatically.

Missing: confirm that the result download was complete and belongs to the correct ended batch. A locally truncated JSONL file is a retrieval problem. Resubmitting model requests will not repair it.

These distinctions save more than tokens. They protect the relationship between source material, generated work, editorial approval, and publication.

Calculate whether batch access fits your workload

A batch route is interesting when work can be asynchronous. It is less suitable for a shopper waiting for an immediate answer.

Before comparing prices, define what counts as a completed business item. For this catalog, use a translation that passes validation and is accepted for the correct source revision.

Track:

text
API cost per accepted translation =
total API charges for all included attempts
÷ number of distinct accepted source-revision-language items

Keep editorial labor separate if you want to compare model charges independently. Add it separately when evaluating total production cost.

Do not divide charges only by the number of successful API responses. That hides translations rejected for content errors. Also avoid counting two successful retries of the same item as two delivered products.

Use actual account billing records and the current Claude pricing documentation for the relevant access method. Direct-provider pricing does not establish the cost of an intermediary service.

Ask the gateway a more useful question than “Do you have Claude?”

Ask whether the account route supports native Message Batches for the chosen Sonnet model, including batch creation, status retrieval, item identifiers, result retrieval, and individual error information.

An application-managed queue of synchronous calls can also process a catalog, but it has different pricing, lifecycle, and recovery behavior. Do not sell one design internally as the other.

Use the 4SAPI integration documentation to check the route. For billing reconciliation, see where to inspect request usage. If your other application is a live coding assistant rather than an offline catalog job, the 4SAPI guide to comparing coding-task cost and accuracy addresses a different purchasing decision.

A useful first account test is three items, not 500. Open the 4SAPI console, confirm native batch availability for your account, and submit a small supported workload. Keep the manifest, batch identifier, raw results, and recovery report. If those four artifacts reconcile, you have a reason to expand. If they do not, you have a precise integration question before spending on the full catalog.

Tags:Claude SonnetMessage BatchesBatch RecoveryAPI Gateway

Recommended reading

Explore more frontier insights and industry know-how.