Request JSON Data

Su Suriya Ravichandran Updated 10 Nov 2026
8 min read ·Lesson 102 of 200

Requesting JSON with a Raw Schema

messages.parse() with a Pydantic model is the most convenient way to get structured output in Python (Lessons 101 and 104). Sometimes, though, your schema is not a Python class:

  • it lives in a JSON file shared with other services or languages,
  • it comes from an OpenAPI specification,
  • it is built at runtime, for example from a form builder or database configuration.

In these cases, send the schema directly with messages.create() and the output_config parameter:

response = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=2048,
    messages=[{"role": "user", "content": "..."}],
    output_config={
        "format": {
            "type": "json_schema",
            "schema": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "email": {"type": "string"},
                },
                "required": ["name", "email"],
                "additionalProperties": False,
            },
        }
    },
)
PartMeaning
output_configOutput settings for the request (it also holds effort, Lesson 47)
"format"The output format to enforce
"type": "json_schema"The format is defined by a JSON Schema
"schema"The JSON Schema itself

Note: an older form of this feature used a top-level output_format parameter with a beta header. According to the documentation, output_format is deprecated in favor of output_config.format, and the Python SDK (v1.0 and later) rejects output_format={...} on client.beta.messages.create() with a TypeError. Use output_config. (messages.parse() still accepts output_format= as a convenience for passing a Pydantic model, and translates it internally.)


Reading the JSON

The JSON arrives in the response's text block. Find the text block by type, because a thinking block may come first (Lesson 16), and parse it:

import json

text = next(block.text for block in response.content if block.type == "text")
data = json.loads(text)

With the raw-schema approach, you get a plain Python dict. Unlike messages.parse(), the SDK does not validate the result against extra constraints for you. Lesson 103 covers that.


Writing Schemas for JSON Outputs

Objects, Arrays, and Nesting

{
  "type": "object",
  "properties": {
    "title": {"type": "string"},
    "topics": {"type": "array", "items": {"type": "string"}},
    "instructor": {
      "type": "object",
      "properties": {
        "name": {"type": "string"},
        "experience_years": {"type": "integer"}
      },
      "required": ["name", "experience_years"],
      "additionalProperties": false
    }
  },
  "required": ["title", "topics", "instructor"],
  "additionalProperties": false
}

Add "additionalProperties": false to every object, so no unexpected fields appear.

Fixed Values with enum

"level": {"type": "string", "enum": ["beginner", "intermediate", "advanced"]}

Values That May Be Missing: null vs. Optional

Information is often missing from the input. There are two ways to represent that:

ApproachSchemaOutput when the value is missing
Nullable required field"type": ["string", "null"] and listed in required"prerequisites": null
Optional fieldNot listed in requiredThe field is absent

Nullable required fields make the output's shape predictable: every key is always present. However, type arrays such as ["string", "null"] count as union types, which the documentation limits to 16 per request across all strict schemas, because they are expensive to compile. Optional fields count toward a separate limit of 24 optional parameters per request (Lesson 108).

Property Order

According to the documentation, output properties keep the order of your schema, with one caveat: required properties come first, then optional properties. If order matters to your application, for example when writing CSV columns, make all properties required, or reorder them in code.


Unsupported Constraints and transform_schema()

JSON outputs support standard JSON Schema with some limitations. For example, numeric and length constraints such as minimum, maximum, minLength, and maxLength are not enforced by the API. Sending a schema with unsupported features can fail.

The SDK provides transform_schema(), which prepares a schema for the API, following the same steps the SDK's helpers use:

  1. remove unsupported constraints, such as minimum and maxLength,
  2. add each removed constraint to the field's description, so Claude still sees the rule as guidance,
  3. add additionalProperties: false to all objects, and
  4. filter string formats to the supported list.
from anthropic import transform_schema

sent_schema = transform_schema(original_schema)   # Also accepts a Pydantic model

transform_schema() returns the transformed schema instead of sending it, so you can inspect it, and edit it if needed. Keep the original schema for validation in your code (Lesson 103), because the transformed version no longer contains the removed constraints.


Practical Example: A Schema from a File

This example extracts structured course data from a free-text course description, using a JSON Schema stored in a file, the form in which another team or service might share it.

course_schema.json

{
  "type": "object",
  "properties": {
    "title": {"type": "string", "description": "Course title"},
    "level": {"type": "string", "enum": ["beginner", "intermediate", "advanced"]},
    "duration_weeks": {"type": "integer", "minimum": 1, "maximum": 52,
                       "description": "Course length in weeks"},
    "topics": {"type": "array", "items": {"type": "string"},
               "description": "Main topics covered, 3 to 6 short items"},
    "prerequisites": {"type": ["string", "null"],
                      "description": "Required prior knowledge, or null if none is required"},
    "price_inr": {"type": "integer", "description": "Price in Indian rupees; omit if not mentioned"}
  },
  "required": ["title", "level", "duration_weeks", "topics", "prerequisites"],
  "additionalProperties": false
}

raw_schema.py

"""raw_schema.py: request JSON data with a raw JSON schema loaded from a file."""

import json
from pathlib import Path

from anthropic import Anthropic, transform_schema

client = Anthropic()
ORIGINAL_SCHEMA = json.loads(Path("course_schema.json").read_text(encoding="utf-8"))
SENT_SCHEMA = transform_schema(ORIGINAL_SCHEMA)

DESCRIPTION = """Build real web apps with Django! In this 8-week program you'll learn models,
views, templates, forms, and deploying to the cloud. You should already be comfortable with
basic Python (variables, loops, functions). Perfect for learners moving past the basics."""


def extract_course(description: str) -> dict | None:
    response = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=2048,
        messages=[{"role": "user", "content":
                   f"Extract the course details.\n<description>\n{description}\n</description>"}],
        output_config={"format": {"type": "json_schema", "schema": SENT_SCHEMA}},
    )
    if response.stop_reason != "end_turn":
        print(f"[no result: stop_reason={response.stop_reason}]")
        return None
    text = next(block.text for block in response.content if block.type == "text")
    return json.loads(text)


if __name__ == "__main__":
    print("duration_weeks as sent:", json.dumps(SENT_SCHEMA["properties"]["duration_weeks"]))
    course = extract_course(DESCRIPTION)
    print(json.dumps(course, indent=2, ensure_ascii=False))

Example output (the transformed description text and extracted values may differ slightly):

duration_weeks as sent: {"type": "integer", "description": "Course length in weeks {minimum: 1, maximum: 52}"}
{
  "title": "Build Real Web Apps with Django",
  "level": "intermediate",
  "duration_weeks": 8,
  "topics": [
    "Models",
    "Views",
    "Templates",
    "Forms",
    "Cloud deployment"
  ],
  "prerequisites": "Basic Python: variables, loops, and functions"
}

How It Works

  1. The schema is loaded from a file, so other services or languages can share the same definition. ORIGINAL_SCHEMA is kept unchanged for validation later.
  2. transform_schema() prepares the schema for the API: the unsupported minimum and maximum constraints on duration_weeks are removed from the schema and mentioned in the field's description instead, so Claude still sees them as guidance. The first printed line shows the transformed field.
  3. output_config={"format": {"type": "json_schema", "schema": SENT_SCHEMA}} asks for JSON that matches the schema.
  4. The stop reason is checked before parsing, because refusals and truncated outputs are not guaranteed to match the schema.
  5. next(...) finds the first text block, skipping any thinking block, and json.loads() turns it into a dictionary.
  6. Required fields come first: prerequisites is required, and nullable, so it is always present. price_inr is optional, and is absent because the description does not mention a price.

Common Mistakes

Using the Deprecated output_format Parameter

What happens: 400 errors without the old beta header, or a TypeError in the Python SDK.

Fix: use output_config={"format": {...}}.

Reading content[0]

What happens: AttributeError when the first block is a thinking block.

Fix: find the text block by type.

Forgetting additionalProperties: false

What happens: objects may contain unexpected extra keys.

Fix: add it to every object, or use transform_schema(), which adds it for you.

Assuming Removed Constraints Are Enforced

What happens: values outside minimum/maximum reach your code, because the API only sees them as description text.

Fix: validate against the original schema (Lesson 103).

Depending on Property Order

What happens: code that expects fields in schema order breaks when optional fields come after required ones.

Fix: access fields by name, or make all fields required.


Best Practices

  • Use raw schemas when the schema comes from outside Python code.
  • Send schemas through transform_schema(), and keep the original for validation.
  • Add additionalProperties: false to every object.
  • Choose nullable or optional fields deliberately, keeping complexity limits in mind.
  • Find the text block by type, and check stop_reason before parsing.
  • Describe fields clearly, including units and formats.

Exercise

  1. Add a nested instructor object (name, experience_years) to course_schema.json, and test it with a description that mentions an instructor.
  2. Change prerequisites from nullable-required to optional. How does the output change for a course without prerequisites?
  3. Print the full output of transform_schema(ORIGINAL_SCHEMA) and compare it with the original. List every difference.
  4. Build the schema in Python code from a list of field names and types, instead of a file.
Show answers for steps 2 and 4

Step 2: with prerequisites optional (removed from required, type "string"), Claude can omit it when there are no prerequisites. The key is then absent instead of null, so code must use course.get("prerequisites") instead of course["prerequisites"]. Optional fields also move after the required fields in the output.

Step 4:

FIELDS = [("title", "string", "Course title"),
          ("duration_weeks", "integer", "Course length in weeks"),
          ("is_free", "boolean", "Whether the course is free")]

schema = {
    "type": "object",
    "properties": {name: {"type": kind, "description": text} for name, kind, text in FIELDS},
    "required": [name for name, _, _ in FIELDS],
    "additionalProperties": False,
}

Building schemas in code lets an application define extraction fields from configuration, for example a form designer where users choose which fields to extract.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Request JSON Data and get answers drawn from it.

Signed-in readers only.