announced Structured Outputs for its top models in its API, a new feature designed to ensure that model-generated outputs exactly match the JSON Schemas provided by developers.
This solves a problem many developers face when a system or process consumes an LLM’s output for further processing. It’s essential for that system to “know” what to expect as its input so it can process it accordingly.
Similarly, when displaying model output to a user, you want this to be in the same format each time.
So far, it’s been a pain to ensure consistent output formats from Anthropic models. However, it appears that Anthropic has now solved this problem for its top models anyway. From their announcement (linked at the end of the article), they say,
The Claude Developer Platform now supports structured outputs for Claude Sonnet 4.5 and Opus 4.1. Available in public beta, this feature ensures API responses always match your specified JSON schemas or tool definitions.
Now, one thing to remember before we look at some example code, is that Anthropic guarantees that the model’s output will adhere to a specified format, not that any output will be 100% accurate. The models can and may still hallucinate occasionally.
So you might get perfectly formatted incorrect answers!
Setting up our dev environment
Before we look at some sample Python code, it’s best practice to create a separate development environment where you can install any necessary software and experiment with coding. Now, anything you do in this environment will be siloed and won’t impact any of your other projects.
I’ll be using Miniconda for this, but you can use whatever method you’re most familiar with.
If you want to go down the Miniconda route and don’t already have it, you must install it first. Get it using this link:
https://docs.anaconda.com/miniconda/
To follow along with my examples, you’ll also need an Anthropic API key and some credit on your account. For reference, I used 12 cents to run the code in this article. If you already have an Anthropic account, you can get an API key using the Anthropic console at https://console.anthropic.com/settings/keys.
1/ Create our new dev environment and install the required libraries
this on WSL2 Ubuntu for Windows.
(base) $ conda create -n anth_test python=3.13 -y
(base) $ conda activate anth_test
(anth_test) $ pip install anthropic beautifulsoup4 requests
(anth_test) $ pip install httpx jupyter 2/ Start Jupyter
Now type in ‘jupyter notebook’ into your command prompt. You should see a jupyter notebook open in your browser. If that doesn’t happen automatically, you’ll likely see a screenful of information after the command. Near the bottom, you’ll find a URL to copy and paste into your browser. It’ll look similar to this:
http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69
Code Examples
In our two coding examples, we will use the new output_format parameter available in the beta API. When specifying the structured output, we can use two different styles.
1. Raw JSON Schema.
As the name suggests, the structure is defined by a JSON schema block passed directly to the output format definition.
2. A Pydantic model class.
This is a regular Python class using Pydantic’s BaseModel that specifies the data we want the model to output. It’s a much more compact way to define a structure than a JSON schema.
Example code 1 — Text summarisation
This is useful if you have a bunch of different texts you want to summarise, but want the summaries to have the same structure. In this example, we’ll process the Wikipedia entries for some famous scientists and retrieve specific key facts about them in a highly organised way.
In our summary, we want to output the following structure for each scientist,
- The name of the Scientist
- When and where they were born
- Their main claim to fame
- The year they won the Nobel Prize
- When and where they died
Note: Most text in Wikipedia, excluding quotations, has been released under the Creative Commons Attribution-Sharealike 4.0 International License (CC-BY-SA) and the GNU Free Documentation License (GFDL) In short this means that you are free:
to Share — copy and redistribute the material in any medium or format
to Adapt — remix, transform, and build upon the material
for any purpose, even commercially.
Let’s break the code into manageable sections, each with an explanation.
First, we import the required third-party libraries and set up connections to Anthropic using our API Key.
import anthropic
import httpx
import requests
import json
import os
from bs4 import BeautifulSoup
http_client = httpx.Client()
api_key = 'YOUR_API_KEY'
client = anthropic.Anthropic(
api_key=api_key,
http_client=http_client
)This is the function that will scrape Wikipedia for us.
def get_article_content(url):
try:
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, "html.parser")
article = soup.find("div", class_="mw-body-content")
if article:
content = "\n".join(p.text for p in article.find_all("p"))
return content[:15000]
else:
return ""
except Exception as e:
print(f"Error scraping {url}: {e}")
return ""Next, we define our JSON schema, which specifies the exact format for the model’s output.
summary_schema = {
"type": "object",
"properties": {
"name": {"type": "string", "description": "The name of the Scientist"},
"born": {"type": "string", "description": "When and where the scientist was born"},
"fame": {"type": "string", "description": "A summary of what their main claim to fame is"},
"prize": {"type": "integer", "description": "The year they won the Nobel Prize. 0 if none."},
"death": {"type": "string", "description": "When and where they died. 'Still alive' if living."}
},
"required": ["name", "born", "fame", "prize", "death"],
"additionalProperties": False
}This function serves as the interface between our Python script and the Anthropic API. Its primary goal is to take unstructured text (an article) and force the AI to return a structured data object (JSON) containing specific fields, such as the scientist’s name, birth date, and Nobel Prize details.
The function calls client.messages.create to send a request to the model. It sets the temperature to 0.2, which lowers the model’s creativity to ensure the extracted data is factual and precise. The extra_headers parameter enables a specific beta feature that is not yet standard. By passing the anthropic-beta header with the value structured-outputs-2025-11-13, the code tells the API to activate the Structured Outputs logic for this specific request, forcing it to produce valid JSON that matches your defined structure.
Because the output_format parameter is used, the model returns a raw string that is guaranteed to be valid JSON. The line json.loads(response.content[0].text) parses this string into a native Python dictionary, making the data immediately ready for programmatic use.
def get_article_summary(text: str):
if not text: return None
try:
response = client.messages.create(
model="claude-sonnet-4-5", # Use the latest available model
max_tokens=1024,
temperature=0.2,
messages=[
{"role": "user", "content": f"Summarize this article:\n\n{text}"}
],
# Enable the beta feature
extra_headers={
"anthropic-beta": "structured-outputs-2025-11-13"
},
# Pass the new parameter here
extra_body={
"output_format": {
"type": "json_schema",
"schema": summary_schema
}
}
)
# The API returns the JSON directly in the text content
return json.loads(response.content[0].text)
except anthropic.BadRequestError as e:
print(f"API Error: {e}")
return None
except Exception as e:
print(f"Error: {e}")
return NoneThis is where we pull everything together. The various URLs we want to scrape are defined. Their contents are passed to the model for processing, before the end results are displayed.
urls = [
"https://en.wikipedia.org/wiki/Albert_Einstein",
"https://en.wikipedia.org/wiki/Richard_Feynman",
"https://en.wikipedia.org/wiki/James_Clerk_Maxwell",
"https://en.wikipedia.org/wiki/Alan_Guth"
]
print("Scraping and analyzing articles...")
for i, url in enumerate(urls):
print(f"\n--- Processing Article {i+1} ---")
content = get_article_content(url)
if content:
summary = get_article_summary(content)
if summary:
print(f"Scientist: {summary.get('name')}")
print(f"Born: {summary.get('born')}")
print(f"Fame: {summary.get('fame')}")
print(f"Nobel: {summary.get('prize')}")
print(f"Died: {summary.get('death')}")
else:
print("Failed to generate summary.")
else:
print("Skipping (No content)")
print("\nDone.")When I ran the above code, I got this output.
Scraping and analyzing articles...
--- Processing Article 1 ---
Scientist: Albert Einstein
Born: 14 March 1879 in Ulm, Kingdom of Württemberg, German Empire
Fame: Developing the theory of relativity and the mass-energy equivalence formula E = mc2, plus contributions to quantum theory including the photoelectric effect
Nobel: 1921
Died: 18 April 1955
--- Processing Article 2 ---
Scientist: Richard Phillips Feynman
Born: May 11, 1918, in New York City
Fame: Path integral formulation of quantum mechanics, quantum electrodynamics, Feynman diagrams, and contributions to particle physics including the parton model
Nobel: 1965
Died: February 15, 1988
--- Processing Article 3 ---
Scientist: James Clerk Maxwell
Born: 13 June 1831 in Edinburgh, Scotland
Fame: Developed the classical theory of electromagnetic radiation, unifying electricity, magnetism, and light through Maxwell's equations. Also key contributions to statistical mechanics, color theory, and numerous other fields of physics and mathematics.
Nobel: 0
Died: 5 November 1879
--- Processing Article 4 ---
Scientist: Alan Harvey Guth
Born: February 27, 1947 in New Brunswick, New Jersey
Fame: Pioneering the theory of cosmic inflation, which proposes that the early universe underwent a phase of exponential expansion driven by positive vacuum energy density
Nobel: 0
Died: Still alive
Done.Not too shabby! Alan Guth will be delighted that he’s still alive, but alas, he hasn’t yet won a Nobel Prize. Also, note that James Clerk Maxwell had died before the Nobel Prize was in operation.
Example code 2 — Automated Code Security & Refactoring Agent.
Here is a completely different use case and a very practical example for software engineering. Usually, when you ask an LLM to “fix code,” it gives you a conversational response mixed with code blocks. This makes it hard to integrate into a CI/CD pipeline or an IDE plugin.
By using Structured Outputs, we can force the model to return the clean code, a list of specific bugs found, and a security risk assessment in a single, machine-readable JSON object.
The Scenario
We will feed the model a Python function containing a dangerous SQL Injection vulnerability and poor coding practices. The model must identify the specific flaws and rewrite the code securely.
import anthropic
import httpx
import os
import json
from pydantic import BaseModel, Field, ConfigDict
from typing import List, Literal
# --- SETUP ---
http_client = httpx.Client()
api_key = 'YOUR_API_KEY'
client = anthropic.Anthropic(api_key=api_key, http_client=http_client)
# Intentionally bad code
bad_code_snippet = """
import sqlite3
def get_user(u):
conn = sqlite3.connect('app.db')
c = conn.cursor()
# DANGER: Direct string concatenation
query = "SELECT * FROM users WHERE username = '" + u + "'"
c.execute(query)
return c.fetchall()
"""
# --- DEFINE SCHEMA WITH STRICT CONFIG ---
# We add model_config = ConfigDict(extra="forbid") to ensure
# "additionalProperties": false is generated in the schema.
class BugReport(BaseModel):
model_config = ConfigDict(extra="forbid")
severity: Literal["Low", "Medium", "High", "Critical"]
line_number_approx: int = Field(description="The approximate line number where the issue exists.")
issue_type: str = Field(description="e.g., 'Security', 'Performance', 'Style'")
description: str = Field(description="Short explanation of the bug.")
class CodeReviewResult(BaseModel):
model_config = ConfigDict(extra="forbid")
is_safe_to_run: bool = Field(description="True only if no Critical/High security risks exist.")
detected_bugs: List[BugReport]
refactored_code: str = Field(description="The complete, fixed Python code string.")
explanation: str = Field(description="A brief summary of changes made.")
# --- API CALL ---
try:
print("Analyzing code for security vulnerabilities...\n")
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
temperature=0.0,
messages=[
{
"role": "user",
"content": f"Review and refactor this Python code:\n\n{bad_code_snippet}"
}
],
extra_headers={
"anthropic-beta": "structured-outputs-2025-11-13"
},
extra_body={
"output_format": {
"type": "json_schema",
"schema": CodeReviewResult.model_json_schema()
}
}
)
# Parse Result
result = json.loads(response.content[0].text)
# --- DISPLAY OUTPUT ---
print(f"Safe to Run: {result['is_safe_to_run']}")
print("-" * 40)
print("BUGS DETECTED:")
for bug in result['detected_bugs']:
# Color code the severity (Red for Critical)
prefix = "🔴" if bug['severity'] in ["Critical", "High"] else "🟡"
print(f"{prefix} [{bug['severity']}] Line {bug['line_number_approx']}: {bug['description']}")
print("-" * 40)
print("REFACTORED CODE:")
print(result['refactored_code'])
except anthropic.BadRequestError as e:
print(f"API Schema Error: {e}")
except Exception as e:
print(f"Error: {e}")This code acts as an automated security auditor. Instead of asking the AI to “chat” about code, it forces the AI to fill out a strict, digital form containing specific details about bugs and security risks.
Here is how it works in three simple steps.
- First, the code defines exactly what the answer must look like using Python classes in conjunction with Pydantic. It tells the AI: “Give me a JSON object containing a list of bugs, a severity rating (like ‘Critical’ or ‘Low’) for each, and the fixed code string.”
- When sending the vulnerable code to the API, it passes the Pydantic blueprint using the output_format parameter. This strictly constrains the model, preventing it from hallucinating or adding conversational filler. It must return valid data matching your blueprint.
- The script receives the AI’s response, which is guaranteed to be machine-readable JSON. It then automatically parses this data to display a clean report, flagging the SQL injection as a “Critical” issue for example and printing the secure, refactored version of the code.
Here is the output I received after running the code.
Analyzing code for security vulnerabilities...
Safe to Run: False
----------------------------------------
BUGS DETECTED:
🔴 [Critical] Line 7: SQL injection vulnerability due to direct string concatenation in query construction. Attacker can inject malicious SQL code through the username parameter.
🟡 [Medium] Line 4: Database connection and cursor are not properly closed, leading to potential resource leaks.
🟡 [Low] Line 1: Function parameter name 'u' is not descriptive. Should use meaningful variable names.
----------------------------------------
REFACTORED CODE:
import sqlite3
from contextlib import closing
def get_user(username):
"""
Retrieve user information from the database by username.
Args:
username (str): The username to search for
Returns:
list: List of tuples containing user data, or empty list if not found
"""
with sqlite3.connect('app.db') as conn:
with closing(conn.cursor()) as cursor:
# Use parameterized query to prevent SQL injection
query = "SELECT * FROM users WHERE username = ?"
cursor.execute(query, (username,))
return cursor.fetchall()Why is this powerful?
Integration-ready. You could run this script in a GitHub Action. If is_safe_to_run is False, you can automatically block a Pull Request.
Separation of concerns. You get the metadata (bugs, severity) separate from the content (the code). You don’t have to use Regex to strip out “Here is your fixed code” text from the response.
Strict typing. The severity field is constrained to specific Enum values (Critical, High, etc.), ensuring your downstream logic doesn’t break when the model returns “Severe” instead of “Critical” for example.
Summary
Anthropic’s release of native Structured Outputs is a game-changer for developers who need reliability, not just conversation. By enforcing strict JSON schemas, we can now treat Large Language Models less like chatbots and more like deterministic software components.
In this article, I demonstrated how to use this new beta feature to streamline data extraction and output, and build automated workflows that integrate seamlessly with Python code. If you’re a user of Anthropic’s API, the days of writing fragile Regex to parse AI responses are finally over.
For more information about this new beta feature, click the link below to visit Anthropics’ official documentation page.
https://platform.claude.com/docs/en/build-with-claude/structured-outputs


