Get structured (JSON) output from an LLM
Instead of asking for JSON and hoping, define the shape as a schema and get responses your code can use directly. Worked through with a customer-message classifier.
15 minAs of Oct 2026
By the end of this guide
- Explain what goes wrong when you ask for JSON in the prompt, and what structured outputs prevent.
- Define a schema as a Pydantic model and get validated results back with messages.parse.
- Catch the content errors and exceptional responses a schema can't prevent, using a test sample and stop_reason.
Who this is for
- Developers who need to pull values out of LLM answers into a database or a UI
- Anyone whose code keeps growing error handling for json.loads failures and missing fields
What you need
- Python with the anthropic SDK and the pydantic package
- Steps 1–3 of "Your first LLM API call" done
Step by step
Why "answer in JSON" isn't enough
Write "answer only in JSON" in a prompt and most of the time you'll get JSON. But across hundreds or thousands of requests, the shape occasionally slips, and that one response stops your program.
Structured outputs constrain generation so the response always follows the JSON schema you define. That largely removes the retries caused by broken JSON syntax, missing fields and wrong types.
What can happen when you only ask for JSON in the prompt (examples) Problem A response like In your code Extra text around it "Here is the classification:" followed by JSON json.loads fails Code fences JSON wrapped in ```json Parsing it as is fails Missing field category but no summary KeyError Wrong type urgent as the string "yes" instead of a boolean Conditions behave oddly Define the shape as a schema
This guide takes a customer message and gets back a category, whether it's urgent and a one-sentence summary. Start by fixing each field's type and allowed values.
In Python, a Pydantic model is the simplest way to define the schema. If you already have a schema file, you can use the JSON schema directly instead.
Fields for the customer-message classifier (example) Field Type Rule category String One of delivery, refund, payment, other urgent True/false true when the customer names a deadline and asks for speed summary String The message in one sentence Example prompt Write a Pydantic model for the result of classifying a customer message. category is one of delivery, refund, payment, other; urgent is true/false; summary is a string. All three are required.
Example AI reply Fields without a default are required, so nothing else needs marking. When the SDK converts the model to a JSON schema, it also adds additionalProperties: false so no undeclared fields can slip in.
Example codefrom typing import Literal from pydantic import BaseModel class Inquiry(BaseModel): category: Literal["delivery", "refund", "payment", "other"] urgent: bool summary: strLiteral["delivery", ...]- Allows only one of the listed values. In the JSON schema it becomes a list of allowed values (enum).
urgent: bool- A true/false value. It arrives as a real boolean, not the string "true".
summary: str- A string. For conditions such as a length limit, see step 5.
Request it with messages.parse and read the result
Pass the model class to messages.parse in the anthropic SDK. The SDK sends the schema as output_config.format, checks the response that comes back and returns an object, so you never parse strings yourself.
If you write the JSON schema by hand, pass output_config={"format": {"type": "json_schema", "schema": ...}} to messages.create and read the response's text block with json.loads.
Example prompt Write code that classifies the customer message below with the Inquiry model. Use messages.parse from the anthropic SDK, with the claude-opus-5-5 model. Message: It's been over a week since I ordered and it still says ‘preparing for shipment’. I need it for an event this Saturday, so it has to arrive by Friday.
Example AI reply Pass Inquiry as output_format and the validated result arrives in parsed_output as an Inquiry object.
Example output: delivery True Order stuck in ‘preparing for shipment’ for over a week; the customer needs it by Friday
Example codeimport anthropic # Put the Inquiry class from step 2 here client = anthropic.Anthropic() text = "It's been over a week since I ordered and it still says ‘preparing for shipment’. I need it for an event this Saturday, so it has to arrive by Friday." response = client.messages.parse( model="claude-opus-5-5", max_tokens=1024, messages=[{"role": "user", "content": f"Classify this customer message: {text}"}], output_format=Inquiry, ) inquiry = response.parsed_output print(inquiry.category, inquiry.urgent, inquiry.summary)client.messages.parse(...)- The SDK helper for structured outputs. It checks the response against the schema before returning it.
output_format=Inquiry- The shape you want back. The SDK turns it into a JSON schema and sends it as output_config.format.
response.parsed_output- The validated Inquiry object. Read it through attributes such as inquiry.category.
Check it
- Check that category is one of the four values and urgent is True or False. Change the message and run it several times: the shape should never change.
What a schema can't guarantee
Structured outputs protect the shape of a response, not whether its content is right. A category can differ from your team's rules, or a summary can drift from the message, so test first with a few messages whose right answers you know.
The shape can break in exceptional cases too. A response that stops with stop_reason refusal (declined for safety) or max_tokens (cut off at the cap) may not match the schema. Check stop_reason before using a result, and retry such responses or hand them to a person.
A test with five messages whose answers are known (example) Message (summary) Expected Got Match My coupon code didn't give me the discount payment payment ✓ I asked for a refund. When will it arrive? refund refund ✓ I typed the wrong address. Can I change it? delivery delivery ✓ My card was charged twice. Please cancel one payment refund ✗ Where do I close my account? other other ✓ 5 messages 4 match (80%) Example prompt In a test with five messages, it classified "My card was charged twice" as refund. By our team's rules that's a payment error, so it should be payment. Write short criteria for the four categories and for urgent that I can put in the system prompt. Say where borderline cases go.
Example AI reply Try putting these criteria in the system prompt.
delivery: problems getting the item to the customer, such as delays, address changes or damage
refund: return and refund requests, and questions about refund timing
payment: problems with paying, such as payment errors, double charges, coupons and discounts. Requests to cancel a double charge go here too.
other: anything that fits none of the above (account details, how to use the service and so on)
urgent: true when the customer names a date or deadline and asks for speed, otherwise false
Check it
- After adding the criteria, rerun the same five messages and check that every result matches.
- Before going live, widen the sample to a few dozen past messages and measure accuracy again.
Know these before you go live
To constrain the response, structured outputs compile your schema into a grammar. That brings a few things to know about speed, cost and which schema features are supported.
Things to know before you go live What Details What to do The first request is slow A new schema adds latency the first time while its grammar compiles. The grammar is then cached for 24 hours from last use Make one warm-up call right after you deploy Slightly more input tokens A system prompt describing the output format is added automatically Include it in cost estimates Unsupported constraints Numeric and length constraints such as minimum and maxLength aren't accepted by the API. SDK helpers move them into field descriptions and check the response against the original constraint State length limits in the prompt and check them in code Changing the schema breaks the cache Changing output_config.format invalidates the prompt cache for that conversation Don't change the schema often Check it
- If the first request with a new schema was unusually slow, grammar compilation is a likely cause. Send a second request with the same schema and see whether it's faster.
Common mistakes
- Trusting "answer only in JSON" in the prompt and skipping parse-error handling
- Assuming the content is right because the shape is right, and shipping without a test
- Skipping the stop_reason check and storing refused or cut-off responses
References
- Structured outputs — Claude Platform Docs (Anthropic)
- Creating your first schema — JSON Schema
- Models — Pydantic Docs