TimoBy Amotion AI

Structured output with JSON schemas: CCAR-F task statement 4.3

CCAR-F · Prompt Engineering & Structured Output (20% of the exam)

Task statement 4.3 sits in Prompt Engineering & Structured Output, 20% of the CCAR-F exam. It tests how you get data back from Claude in a fixed JSON shape on every call: an extraction tool with a JSON schema, the right tool_choice setting, and a schema that gives Claude an honest way to say a value is missing.

What the official guide covers

The Claude Certified Architect Foundations exam guide (version 1.0, effective July 2026) lists this under task statement 4.3, "Enforce structured output using tool use and JSON schemas":

Knowledge ofSkills in
Tool use with a JSON schema is the most reliable way to get schema-compliant output, with no JSON syntax errorsDefining extraction tools with a JSON schema as the input and reading the data from the tool_use block
tool_choice "auto" (Claude may reply in text), "any" (Claude must call a tool but chooses which) and a forced tool (Claude must call the named tool)Setting "any" when there are several extraction schemas and the document type is unknown; forcing a named tool so one extraction runs before enrichment steps
Strict schemas remove syntax errors but not semantic errors, such as line items that do not add up to the total or values in the wrong fieldMaking fields optional (nullable) when the source may not contain the information, so Claude does not invent values
Schema design: required or optional fields, and enums with an "other" value plus a detail string for categories that growAdding enum values such as "unclear" and "other" plus a detail field; putting format rules in the prompt alongside a strict schema

How tool use produces structured output

You define a tool whose input_schema is the shape of the data you want, such as record_invoice, and never implement it. When Claude calls it, the input of the tool_use block is your extracted data, already parsed.

Add "strict": true to the tool definition and the API uses constrained sampling, so the tool input always follows your schema: correct types, required fields present, and no extra fields when additionalProperties is false. Without strict mode Claude usually follows the schema, but nothing guarantees it.

The API also offers JSON outputs: output_config.format with a json_schema constrains Claude's text reply to a schema, with no tool involved. The exam guide frames the topic as tool use.

Choosing tool_choice

SettingWhat Claude must doUse it when
{"type": "auto"} (default with tools)Decide whether to call a tool or reply in textConversational agents
{"type": "any"}Call one of the tools; Claude picks whichSeveral extraction tools, document type unknown
{"type": "tool", "name": "extract_metadata"}Call that toolOne extraction must run, for example before enrichment
{"type": "none"}Not call any toolTurns where tools must not be used

With any or a named tool, the API prefills the reply so Claude goes straight to the tool_use block, with no text before it.

Not every model or setting supports forced tool use. With manual extended thinking (thinking: {"type": "enabled"}), any and a named tool return an error, and some newer models reject forced tool use in every case. For those, Anthropic's tool docs recommend auto with strict tool use, or JSON outputs when you need a reply in a fixed JSON shape. Check the "Define tools" page for the model you use.

An extraction tool with strict mode (Python)

import anthropic

client = anthropic.Anthropic()
MODEL = "your-model-id"   # a model that supports forced tool use

RECORD_INVOICE = {
    "name": "record_invoice",
    "description": "Record the fields of one supplier invoice. "
                   "Use null for any field the invoice does not show.",
    "strict": True,
    "input_schema": {
        "type": "object",
        "properties": {
            "invoice_number": {"type": "string"},
            "invoice_date": {"type": ["string", "null"],
                             "description": "YYYY-MM-DD, or null if not shown"},
            "category": {"type": "string",
                         "enum": ["goods", "services", "subscription", "unclear", "other"]},
            "category_detail": {"type": ["string", "null"],
                                "description": "What the invoice says, when category is other"},
            "stated_total": {"type": ["number", "null"]},
        },
        "required": ["invoice_number", "invoice_date", "category",
                     "category_detail", "stated_total"],
        "additionalProperties": False,
    },
}

response = client.messages.create(
    model=MODEL,
    max_tokens=1024,
    system="Write dates as YYYY-MM-DD. Write amounts as plain numbers "
           "with no currency symbols or thousands separators.",
    tools=[RECORD_INVOICE],
    tool_choice={"type": "tool", "name": "record_invoice"},
    messages=[{"role": "user", "content": f"<invoice>\n{invoice_text}\n</invoice>"}],
)

invoice = next(b.input for b in response.content if b.type == "tool_use")

Three design choices in this schema match the guide:

  • Nullable fields. invoice_date and stated_total accept null, so Claude can say "not on this invoice" instead of inventing a value.
  • "unclear" and "other" with a detail field. Hard cases can be marked unclear, and a new category arrives as "other" with its text in category_detail.
  • Format rules in the prompt. The schema says invoice_date is a string; the system prompt says which format.

Check stop_reason as well. Anthropic's docs note that a reply cut off at max_tokens, or a refusal, may not match the schema.

What constrained output costs, and what it replaces

Strict tool use and JSON outputs are not free switches. Anthropic's structured outputs page lists the trade-offs:

  • First-call latency. The first request with a new schema is slower while the API compiles it into a grammar. A compiled grammar stays cached for 24 hours after its last use, so a stable schema pays once. A pipeline that builds a new schema for every request pays every time.
  • A few more input tokens. The API adds a short system prompt describing the expected format. Changing output_config.format also invalidates the prompt cache for that conversation.
  • Feature conflicts. JSON outputs cannot be combined with prefilling the assistant message, and combining them with citations returns an error. JSON outputs and strict tool use can be used together in one request.

Older material shows a prompt-level trick for clean JSON: start Claude's reply with an opening code fence and set a stop sequence on the closing one. That only shaped where the reply began and ended; it never checked the shape inside. Anthropic's prompting guide now says recent models reject a prefilled final assistant turn with a 400 error. If an exam option offers prefilling or "reply only in JSON" against a schema option, the schema is right.

What the schema cannot catch

A schema checks shape, not meaning. Strict mode guarantees that stated_total is a number, not that it matches the line items or that tax did not land in the discount field. Those semantic errors need validation code after extraction (task statement 4.4).

SituationChooseWhy
Output must parse and match a schema every timeStrict tool use, or JSON outputsConstrained output, no syntax errors
Several document types, each with its own extraction tool, type unknowntool_choice any, where the model supports itA tool is always called and Claude picks the match
One extraction must run before the next stepForce the named toolGuarantees that step runs
The model rejects forced tool_choiceauto with strict tool use, or JSON outputsThe fallback the tool docs recommend
A field may be missing from the sourceNullable typeRemoves the pressure to invent a value
Categories will grow over timeEnum with "other" plus a detail stringNew cases fit without a schema change

Rules that decide exam answers

  • A schema beats "reply in JSON" and prefilling. When an option asks for JSON in the prompt, or starts Claude's reply for it, and another defines a tool or output schema, the schema is right.
  • auto lets Claude answer in text. If every document must produce structured data, choose any or a forced tool, not auto with a prompt instruction.
  • Valid shape is not correct data. Strict mode removes syntax errors only. Sums, field placement and cross-field rules need validation code.
  • Nullable beats required for data that may be absent. A required non-null field pushes Claude to make a value up.
  • Use "other" plus detail for open categories. Use "unclear" when the source is ambiguous, rather than forcing a guess.

Where it appears in the exam

Prompt Engineering & Structured Output is a primary domain in two of the six exam scenarios: Claude Code for Continuous Integration and Structured Data Extraction. This task statement belongs mainly to the extraction scenario, which describes a system that validates extracted data with JSON schemas and feeds downstream systems.

Two sample questions

These are original Timo practice questions. They are not official exam questions.

Question 1

A pipeline receives invoices, purchase orders and delivery notes, and has one extraction tool for each. The document type is not known in advance. With tool_choice set to "auto", about one response in 25 is a text summary instead of a tool call, which breaks the downstream parser. What should the team change?

Answer: A. "any" guarantees a tool call and lets Claude choose the matching schema. B uses the wrong schema for two types, C is still probabilistic, and D loses the per-type structure and invites misplaced values.

Question 2

An extraction service uses strict tool use, and every output parses and matches the schema. Finance still finds invoices where stated_total does not equal the sum of the line items, and some where the tax amount sits in the discount field. The team lead says strict mode should have prevented this. What is correct?

Answer: D. Schema enforcement removes syntax and type errors only, so semantic checks belong in validation code. A misreads what strict mode does, B gives JSON outputs a check it does not have, and C does not apply to numeric fields such as tax and discount.

Build exercise

  1. Use the record_invoice tool above to extract five invoices, one with no date. Confirm the date comes back as null.
  2. Make invoice_date a required non-null string and run the same invoice again. Note what Claude puts in the field.
  3. Add a second tool for purchase orders, set tool_choice to "any" (on a model that supports it) and send a mix of documents. Check which tool each one triggers.
  4. Edit one invoice so its line items do not match the total. Confirm the output still passes the schema, then write a check that catches it.

Practise this topic

Sources