CertSafari
    Anthropic Claude Certified Architect – Foundations (CCAR-F)· Lessons

    Domain 4 · Lesson 23/30

    Batch Failure Recovery with custom_id and Sample-First Prompt Refinement

    Design efficient batch processing strategies

    8 min read
    3.33% of exam
    4 sources
    Published 4 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Use custom_id to match out-of-order batch results back to their source documents
    • Tell the four batch result types apart and decide which failed requests need fixing before they are resent
    • Resubmit only the failed requests in a new batch, including splitting documents that exceeded the context window
    • Refine prompts on a small sample before a large batch run to raise first-pass success

    1.custom_id: the only link between a request and its result

    Each entry in a batch has two parts: a custom_id you choose and a params object holding normal Messages API parameters. The custom_id must be unique within the batch, 1 to 64 characters long, and use only letters, digits, hyphens, and underscores. It matters because results don't come back in the order you submitted them. The results file is JSONL, one line per request, and the custom_id is the only reliable way to match a line to the document it came from.

    Checkpoint 1 of 5· Fill the gap

    Which field gives each batched request the identifier you will use to match its result?

    message_batch = client.messages.batches.create(
        requests=[
            Request(
                 ? ="my-first-request",
                params=MessageCreateParamsNonStreaming(
                    model="claude-opus-5-5",
                    max_tokens=1024,
                    messages=[
                        {
                            "role": "user",
                            "content": "Hello, world",
                        }
                    ],
                ),
            ),

    Pick custom_id values that point straight back to your own records, such as an invoice number or document key. Generic values like request-1 tell you nothing on their own. The docs specifically recommend meaningful custom_id values because the order of results isn't guaranteed. When a result fails, its custom_id tells you exactly which source document to resend, with no extra lookup table needed.

    Checkpoint 2 of 5· Check yourself

    A pipeline stores batch results by assuming line N of the results file matches the Nth submitted request. What goes wrong?

    Sources12

    2.Reading results: four outcomes, and only one is billed

    You poll the batch until its processing_status changes from in_progress to ended. At that point, each request has exactly one of four result types. Only succeeded requests are billed. The request_counts field on the batch shows how many requests landed in each state, so you can see the size of the cleanup job before reading a single line. Because the results file can be large, the docs recommend streaming it rather than downloading it all at once.

    Streaming results and branching on result type, keyed by custom_idpython
    for result in client.messages.batches.results(
        "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d",
    ):
        outcome = result.result
        match outcome.type:
            case "succeeded":
                print(f"Success! {result.custom_id}")
            case "errored":
                if outcome.error.error.type == "invalid_request_error":
                    # Request body must be fixed before re-sending request
                    print(f"Validation error {result.custom_id}")
                else:
                    # Request can be retried directly
                    print(f"Server error {result.custom_id}")
            case "expired":
                print(f"Request expired {result.custom_id}")

    Checkpoint 3 of 5· Match them up

    Match each batch result type to what it means

    Tap a term, then the definition that fits it.

    Sources1

    3.Resubmitting only what failed, with the right fix

    A request that fails doesn't affect any other request in the same batch. That makes recovery targeted. You don't rerun the whole batch and pay again for thousands of results you already have. Instead, collect the custom_ids of the failed requests and put only those into a new batch. A batch can't be edited after submission, so a new batch is the only way to resend anything.

    How you fix each failure depends on its type. An invalid_request_error means the request itself is wrong and has to be changed before you resend it. Server errors and expired requests can go back unchanged. The most common invalid-request case in document pipelines is an input that is too big. If a document on its own exceeds the model's context window, the API rejects it with a 400 invalid_request_error that says the prompt is too long. Resending it unchanged will fail again. You need to split that document into chunks that fit, each with its own custom_id derived from the parent's. You can use the token counting API to check sizes before resubmitting.

    Checkpoint 4 of 5· Check yourself

    A nightly batch of 20,000 contracts ends with 19,880 succeeded, 70 expired, and 50 errored with invalid_request_error because those contracts exceed the context window. What is the most efficient recovery?

    Sources13

    4.Refine on a sample before you batch the full volume

    Every failed or unusable result costs you another submission cycle, and on a batch that can mean up to a day of extra latency, which also eats into your SLA. The cheapest failure is one you catch before the large run starts. The batch docs suggest dry-running a single request shape against the synchronous Messages API to catch validation errors. That fast test confirms the request is well formed before you send thousands of copies of it.

    Output quality needs the same treatment. In Anthropic's account of building its multi-agent research system, the team says to start evaluating right away on small samples. Early prompt changes can have large effects, for example raising success from 30% to 80%, so even about 20 representative cases make those effects visible. For batch work this means running the prompt on a small, varied sample of real documents. Use synchronous calls so you can iterate quickly. Fix the prompt until the sample passes, and only then submit the full volume, when the first-pass success rate is already high.

    Checkpoint 5 of 5· Check yourself

    A team is about to batch-extract fields from 80,000 claims with a new prompt. Which step most reduces the cost of repeated resubmission?

    Sources14

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.Batch results come back in the order the requests were submitted, so you can match them by position.Why is that wrong?

      Order isn't guaranteed. Each result line carries the custom_id you assigned, and that is the only reliable join key.

      Covered in custom_id: the only link between a request and its result

    2. 2.When some requests in a batch fail, the safest fix is to resubmit the whole batch, or to edit the failed entries in place.Why is that wrong?

      Failures are isolated to individual requests, and a submitted batch can't be modified. Put only the failed custom_ids into a new batch, and change the ones that failed with invalid_request_error first.

      Covered in Resubmitting only what failed, with the right fix

    Practise it for real

    Run a two-request batch end to end and handle its results by custom_id

    1. 1.Create a batch with client.messages.batches.create, passing two Request entries with custom_ids my-first-request and my-second-request

      Why: Each entry needs a unique custom_id plus standard Messages params

      You should see: A message_batch object whose processing_status is in_progress

    2. 2.Poll with client.messages.batches.retrieve(batch_id) every 60 seconds until processing_status equals ended

      Why: Results are only available after processing ends

      You should see: request_counts showing how many requests succeeded, errored, were canceled, or expired

    3. 3.Stream client.messages.batches.results(batch_id) and branch on result.result.type, printing result.custom_id

      Why: Results may arrive in any order, so custom_id is the join key

      You should see: One line per request, possibly not in submission order

    4. 4.Collect any custom_ids that did not succeed into a list for a follow-up batch

      Why: Only failed requests need resubmitting, and invalid_request_error ones need their body fixed first

      You should see: An empty list on a clean run, or only the failed custom_ids otherwise

    Stuck? Get a nudge

    If a request errors with invalid_request_error, run that same params object once through client.messages.create to see the validation message before you resubmit.

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “Must be 1 to 64 characters and contain only alphanumeric characters, hyphens, and underscores”
      ↩︎ custom_id: the only link between a request and its result
      “Use meaningful custom_id values to easily match results with requests, since order is not guaranteed.”
      ↩︎ custom_id: the only link between a request and its result
      “It starts as in_progress, then updates to ended once all the requests in the batch have finished processing, and results are ready.”
      ↩︎ Reading results: four outcomes, and only one is billed
      “it's recommended to stream results back rather than download them all at once.”
      ↩︎ Reading results: four outcomes, and only one is billed
      “Note that the failure of one request in a batch does not affect the processing of other requests.”
      ↩︎ Resubmitting only what failed, with the right fix
      “No, once a batch has been submitted, it cannot be modified.”
      ↩︎ Resubmitting only what failed, with the right fix
      “Request body must be fixed before re-sending request”
      ↩︎ Resubmitting only what failed, with the right fix
      “Dry run a single request shape with the Messages API to avoid validation errors.”
      ↩︎ Refine on a sample before you batch the full volume
      “Note that the failure of one request in a batch does not affect the processing of other requests.”
      ↩︎ Exam trap 2
      “Request encountered an error and a message was not created. Possible errors include invalid requests and internal server errors.”
      ↩︎ Checkpoint
    2. 2.
      “Results are not guaranteed to be in the same order as requests. Use the custom_id field to match results to requests.”
      ↩︎ custom_id: the only link between a request and its result
      “Results are not guaranteed to be in the same order as requests. Use the custom_id field to match results to requests.”
      ↩︎ Exam trap 1
    3. 3.
      “If the input alone already exceeds the model's context window, the API returns a 400 invalid_request_error ("prompt is too long") on every model.”
      ↩︎ Resubmitting only what failed, with the right fix
      “use the token counting API to estimate token usage before sending messages to Claude.”
      ↩︎ Resubmitting only what failed, with the right fix
    4. 4.
      “Start evaluating immediately with small samples.”
      ↩︎ Refine on a sample before you batch the full volume
      “A prompt tweak might boost success rates from 30% to 80%.”
      ↩︎ Refine on a sample before you batch the full volume

    Ready to test yourself?

    Practise the 16 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.