CCCA-F Prep
D420% of exam

CCA-F Domain 4 Study Guide: Prompt Engineering & Structured Output

Choosing and enforcing reliable structured output on the Claude Messages API — from tool schemas and tool_choice to extraction design, feedback loops, and picking synchronous vs. batch processing.

12 min read 6 objectives Reviewed July 25, 2026
On this page

Domain 4 is worth 20% of the CCA-F exam, and it rewards a specific skill: given a scenario, pick the output method whose reliability matches how strictly the downstream system needs the schema honored — and then configure the API so the model actually complies. The exam is scenario-based, so you will rarely be asked to recite a parameter name. You will be asked which of several approaches survives contact with messy real-world documents and a brittle parser on the other end.

This guide walks the six D4 objectives in order. It grounds every recommendation in how the Anthropic Messages API actually behaves in early 2026: tool use with a JSON schema, strict (grammar-constrained) tool use, the output_config.format structured-output feature, tool_choice to force a tool, few-shot prompting, and the choice between the synchronous Messages API and the asynchronous Message Batches API. Where a specific number matters — the batch discount, the processing window — it is stated as Anthropic documents it, and where the API surface has shifted (assistant prefills), that is called out honestly rather than glossed over.

This is an independent, unofficial study resource. It is not affiliated with or endorsed by Anthropic, and all examples are original — no real exam items are reproduced. Trademarks are used descriptively.

OBJ 20

Choosing the most reliable structured-output method

There is no single "structured output" switch. There is a reliability ladder, and the exam wants you to select the rung that matches the strictness the downstream system demands. A dashboard that tolerates an occasional malformed row can live near the bottom of the ladder; a billing pipeline that inserts directly into a typed database column needs the top.

From strongest schema guarantee to weakest:

  1. Strict tool use ("strict": true on the tool definition) — Anthropic compiles your JSON schema into a grammar and constrains sampling to it, so the emitted tool_use.input is guaranteed to validate against the schema. This is the strongest compliance available and the right answer whenever a parser downstream will crash on anything unexpected.
  2. Structured outputs on the response (output_config.format with a json_schema) — constrains the model's text response to your JSON schema rather than a tool call. Use it when you want the answer itself to be schema-valid JSON, not a function argument.
  3. Tool use with a JSON schema, non-strict — the schema strongly steers the model and you still parse tool_use.input, but without grammar constraints an unusual value can occasionally slip through. Validate on receipt.
  4. Prompt-based "return JSON" — you describe the shape in the prompt and hope. Weakest: the model may add prose, a code fence, or a trailing comment. Only acceptable when a human reads the output or a lenient parser cleans it up.

The exam trap: equating any JSON output with guaranteed schema compliance. "We asked for JSON and usually get JSON" is prompt-based reliability. If the scenario says the result feeds a strict typed consumer, only strict tool use (or structured outputs) actually guarantees the contract.

OBJ 21

Designing schemas that admit missing and ambiguous data

A schema that requires every field forces the model to invent values when a field is genuinely absent from the document. The fix is to design the schema so "not present" and "unsure" are representable states, not error conditions the model has to route around by hallucinating.

  • Optional fields — leave truly-optional keys out of required so their absence is legal.
  • Nullable values — allow null (via "type": ["string", "null"] or an anyOf with a null branch) so the model has an explicit way to say "this field exists in the schema but not in this document."
  • Enums for closed sets — constrain fields with a fixed vocabulary (currency, document type, status) to an enum, and include an explicit "unknown" member so ambiguity has a home instead of becoming a fabricated guess.
json
{
  "type": "object",
  "properties": {
    "invoice_number": { "type": ["string", "null"] },
    "issue_date":     { "type": ["string", "null"], "format": "date" },
    "currency":       { "type": "string", "enum": ["USD", "EUR", "GBP", "unknown"] },
    "total_amount":   { "type": ["number", "null"] },
    "vendor_name":    { "type": ["string", "null"] },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": { "type": "string" },
          "amount":      { "type": ["number", "null"] }
        },
        "required": ["description", "amount"],
        "additionalProperties": false
      }
    }
  },
  "required": ["invoice_number", "currency", "total_amount", "line_items"],
  "additionalProperties": false
}
Invoice-extraction schema: nullable amounts, an enum with an explicit unknown, optional line items

Note a structured-outputs constraint to remember: objects need "additionalProperties": false, and each object lists its required keys. String/number range constraints (minLength, minimum) and recursive schemas are not enforced by the grammar — validate those client-side.

The exam trap: marking every field required and non-nullable "to make sure we get complete data." That does the opposite — it guarantees the model fills blanks with plausible fiction. Optionality is an accuracy feature, not a looseness.

OBJ 22

Enforcing tool use and guaranteeing invocation

Two independent levers appear in D4 scenarios and are easy to confuse. A JSON schema on the tool (input_schema, optionally "strict": true) governs the shape of the arguments when a tool is called. `tool_choice` governs whether a tool is called at all. A downstream failure like "the model replied with a friendly paragraph instead of calling record_extraction" is a tool_choice problem, not a schema problem.

tool_choiceBehavior
{"type": "auto"}Model decides whether to use a tool (default).
{"type": "any"}Model must call at least one of the provided tools.
{"type": "tool", "name": "..."}Model must call that specific tool — the way to guarantee schema-valid args instead of prose.
{"type": "none"}Model may not call any tool.
json
{
  "tools": [{
    "name": "record_invoice",
    "description": "Record the fields extracted from an invoice document.",
    "strict": true,
    "input_schema": {
      "type": "object",
      "properties": {
        "invoice_number": { "type": ["string", "null"] },
        "total_amount":   { "type": ["number", "null"] },
        "currency":       { "type": "string", "enum": ["USD", "EUR", "GBP", "unknown"] }
      },
      "required": ["invoice_number", "total_amount", "currency"],
      "additionalProperties": false
    }
  }],
  "tool_choice": { "type": "tool", "name": "record_invoice" }
}
Force a single extraction tool so the model cannot answer conversationally

With strict: true the arguments are grammar-constrained to the schema; with tool_choice naming the tool, the model must emit those arguments rather than a sentence. Together they turn "extract this invoice" into a call whose input is guaranteed to be valid, parseable, and present.

The exam trap: reaching for a better schema when the real problem is that the model sometimes doesn't call the tool. A schema can't fix a missing call — only tool_choice guarantees invocation.

OBJ 23

Extraction accuracy across messy document formats

Real documents are inconsistent: dates as 03/04/2026 or 4 March 2026, amounts as 1,234.50 or $1.234,50, vendor names in headers or footers. Three patterns, stacked, drive accuracy and consistency up while pushing hallucination down.

  1. Structured schema with optional/nullable fields (objective 21) — so the model reports absence instead of inventing it.
  2. Format-normalization instructions — tell the model the canonical output form: "Return all dates as ISO 8601 (YYYY-MM-DD). Return amounts as plain decimal numbers with a period decimal separator and no currency symbol or thousands separators." This collapses many input formats into one predictable output.
  3. Few-shot examples — one or two worked input→output pairs anchor edge cases (a missing field returning null, a European-format amount normalized) far more effectively than prose describing them.
json
[
  { "role": "user", "content": "Extract from: 'Rechnung 88-A, Betrag 1.234,50 EUR, Datum 04.03.2026'" },
  { "role": "assistant", "content": "{\"invoice_number\": \"88-A\", \"total_amount\": 1234.50, \"currency\": \"EUR\", \"issue_date\": \"2026-03-04\"}" },
  { "role": "user", "content": "Extract from: 'Invoice 90-B, total blank, dated March 5 2026'" },
  { "role": "assistant", "content": "{\"invoice_number\": \"90-B\", \"total_amount\": null, \"currency\": \"unknown\", \"issue_date\": \"2026-03-05\"}" }
]
A few-shot pair in the messages array — teaches normalization and null-on-absence by example

The exam trap: assuming a bigger, sterner prompt ("BE ACCURATE. DO NOT MAKE MISTAKES.") raises extraction quality. It doesn't. Concrete normalization rules plus one or two positive examples outperform emphatic adjectives every time.

OBJ 24

Feedback loops that turn errors into improvements

Extraction quality is not a one-time prompt-tuning event; it is a loop. The design objective is to capture structured metadata about each error so that patterns — not anecdotes — drive the next revision of the prompt, schema, or few-shot set.

Log, per failed or corrected extraction, a small record you can aggregate:

json
{
  "document_id": "inv-2026-0442",
  "field": "total_amount",
  "error_type": "fabricated",
  "model_value": 1250.00,
  "correct_value": null,
  "document_format": "scanned_pdf_eu",
  "root_cause_hypothesis": "amount absent; schema lacked null option"
}
An error record designed for aggregation, not just debugging
Dominant error patternWhere to intervene
Fabricated values for absent fieldsSchema — make the field nullable/optional; add a 'never guess' instruction
Wrong format (dates, decimals)Prompt — add/clarify a normalization rule for that format
Recurs on one document layoutFew-shot — add an example of that layout's edge case
Model answered in prose, no tool calltool_choice — force the tool (objective 22)

The loop is: collect structured error metadata → aggregate to find the dominant error_type/document_format cluster → change the one lever that pattern implicates → re-run against the same evaluation set → confirm the cluster shrank. Because each error carries field, error_type, and document_format, you fix categories rather than chasing individual documents.

The exam trap: treating logging as sufficient. Raw transcripts alone don't close the loop — you need the structured signal (error type, field, format) that tells you which of prompt, schema, or examples to change.

OBJ 25

Synchronous Messages API vs. asynchronous Message Batches

The final objective is a processing-mode decision driven by three questions: How latency-sensitive is the caller? Does the workflow block on the result? And is a longer, less predictable processing window acceptable?

  • Synchronous Messages API — you send a request and wait for the response inline. Interactive, low-latency, blocking. The right choice for chat, live extraction where a user is waiting, or any step whose result the very next line of code needs.
  • Asynchronous Message Batches API — you submit up to many thousands of requests at once, the batch processes in the background, and you poll its status (in_progressended) and collect results when done. Non-blocking and built for high-volume, latency-tolerant work.
FactorMessages API (sync)Message Batches API (async)
LatencyImmediate, interactiveBackground; up to a 24-hour window (many finish sooner)
BlockingCaller waits for the resultSubmit and move on; poll or collect later
VolumeOne request per callLarge batches of independent requests
CostStandard token pricing50% discount vs. standard pricing
Best forChat, live user-facing extractionOvernight bulk extraction, backfills, offline evals

Anthropic documents the Batches API as offering a 50% cost reduction on token usage, with batches processing within a window of up to 24 hours (most complete sooner) and results retrievable once the batch's status reaches ended. Results come back keyed by the custom_id you assigned each request — and in any order — so always match by id, never by position.

The exam trap: choosing Batches purely because it is cheaper, in a scenario where a user is waiting. The 50% discount is real, but batch turnaround is measured in the processing window, not milliseconds — using it behind an interactive request breaks the experience. Match the mode to the blocking behavior first, then enjoy the savings where they fit.

Key takeaways

  • 01Structured output is a reliability ladder: strict (grammar-constrained) tool use and output_config.format give the strongest schema guarantees; non-strict tool schemas are weaker; prompt-based JSON is weakest. Match the rung to how strict the downstream consumer is.
  • 02A JSON schema controls the shape of tool arguments; tool_choice controls whether the tool is called. Force the tool (tool_choice = that tool) when a conversational reply would break the pipeline.
  • 03Make fields optional, nullable, and enum-with-'unknown' so the model can represent missing or ambiguous data instead of fabricating it — and tell it, in the prompt, to use those options.
  • 04Combine structured schemas, explicit format-normalization instructions, and one or two few-shot examples to raise extraction accuracy across inconsistent document formats.
  • 05Build feedback loops on structured error metadata (field, error type, document format) so the dominant failure pattern points you to the right lever: schema, prompt, few-shot, or tool_choice.
  • 06Use the synchronous Messages API when a caller is blocked and waiting; use the asynchronous Message Batches API (50% cheaper, up to a 24-hour window) for high-volume, latency-tolerant work — and key results by custom_id.
  • 07Assistant-turn prefills are rejected (400) on current Claude models — reach for output_config.format or a tool schema instead of prefilling {.

Common mistakes

Believing any JSON response means guaranteed schema compliance.

Only strict tool use or structured outputs (output_config.format) guarantee the schema. Plain 'return JSON' prompting can add prose, fences, or invalid values — validate or upgrade the method when a strict consumer is downstream.

Marking every extraction field required and non-nullable to 'get complete data.'

That forces the model to invent values for absent fields. Make genuinely-optional fields optional/nullable and give enums an 'unknown' member so absence and ambiguity are representable.

Trying to fix 'the model replied conversationally' by improving the schema.

A schema shapes arguments only when a tool is called. Use tool_choice: {type: 'tool', name: '...'} to guarantee the call happens.

Relying on assistant prefill to force JSON output.

Last-assistant-turn prefills 400 on current Claude models. Use output_config.format or a tool schema; keep few-shot examples in earlier turns, not the final assistant turn.

Choosing the Batches API for a user-facing request because it is cheaper.

Batch processing runs in a window of up to 24 hours. Use the synchronous Messages API whenever a caller or downstream step is blocked and waiting; reserve batching for offline, high-volume work.

Frequently asked

What is the single most reliable way to get schema-valid output?

Strict tool use — set "strict": true on the tool definition. Anthropic compiles your JSON schema into a grammar and constrains sampling to it, so the tool_use.input is guaranteed to validate. The output_config.format structured-output feature gives the same grammar-backed guarantee for the response text itself. Non-strict tool schemas and prompt-based JSON are weaker and should be validated on receipt.

How do I stop the model from inventing values for fields that aren't in the document?

Design the schema so absence is a legal state: make the field optional (leave it out of required), allow null, and give closed-vocabulary fields an enum with an explicit 'unknown' value. Then add a short instruction telling the model to return null/unknown rather than guess. The schema grants permission; the instruction tells it to use that permission.

What is the difference between a tool's JSON schema and tool_choice?

The schema (input_schema, optionally strict) governs the shape of the arguments once a tool is called. tool_choice governs whether — and which — tool is called. If the model sometimes answers in prose instead of calling your extraction tool, that's a tool_choice problem: set it to {type: 'tool', name: '...'} to force the call.

When should I use the Message Batches API instead of the synchronous Messages API?

Use Batches for high-volume, latency-tolerant work — overnight bulk extraction, backfills, offline evals — where nothing is blocked waiting on a specific response. It processes in the background within a window of up to 24 hours (often sooner) at a 50% cost reduction, and you poll status until it reaches ended. Use the synchronous Messages API for anything interactive or blocking.

Are assistant prefills still a valid technique on the exam?

Understand the concept — seeding the assistant turn with { or [ to force JSON — but know that on current Claude models (Opus 4.6+, Sonnet 4.6+, and the Fable family) a last-assistant-turn prefill returns a 400 error. The modern equivalents are output_config.format and tool schemas. Few-shot assistant messages placed in earlier turns are still fine.

Independent, unofficial study material. Not affiliated with, endorsed by, or authorized by Anthropic. Every example is original and written to teach the public exam objectives — no real exam questions are reproduced. Technical details reflect Claude, the Anthropic API, Claude Code, and MCP as of July 25, 2026; always confirm specifics against current official documentation.

Test yourself

Turn what you just read into answers you can check.

Take the mock exam

Keep studying

All guides