Anthropic Messages Structured Outputs with AI Gateway
Structured outputs constrain completed model responses to a JSON Schema. Check for refusals, errors, and output-token limits before parsing the result. This is useful when you need to extract structured data, build reliable pipelines, or integrate model responses directly into your application.
AI Gateway supports two approaches for structured outputs with Anthropic models:
- GA API (
output_config.format): The stable, generally available path - Beta API (
output_formatwith thestructured-outputs-2025-11-13beta header): The original beta path
For full details on structured outputs, see the Anthropic structured outputs documentation.
The GA API uses the output_config.format field to specify a JSON Schema. No beta header is required.
import Anthropic from '@anthropic-ai/sdk';
const apiKey = process.env.AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN;
const anthropic = new Anthropic({
apiKey,
baseURL: 'https://ai-gateway.vercel.sh',
});
const personSchema = {
type: 'object',
additionalProperties: false,
properties: {
name: { type: 'string' },
age: { type: 'number' },
email: { type: 'string' },
skills: {
type: 'array',
items: { type: 'string' },
},
},
required: ['name', 'age', 'email', 'skills'],
};
const message = await anthropic.messages.create({
model: 'anthropic/claude-sonnet-5',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Generate a profile for a software engineer in Austin, TX.',
},
],
output_config: {
format: {
type: 'json_schema',
schema: personSchema,
},
},
});
const textBlock = message.content.find((b) => b.type === 'text');
if (textBlock?.type === 'text') {
const person = JSON.parse(textBlock.text);
console.log(person.name, person.skills);
}import os
import json
import anthropic
api_key = os.getenv('AI_GATEWAY_API_KEY') or os.getenv('VERCEL_OIDC_TOKEN')
client = anthropic.Anthropic(
api_key=api_key,
base_url='https://ai-gateway.vercel.sh'
)
person_schema = {
'type': 'object',
'additionalProperties': False,
'properties': {
'name': {'type': 'string'},
'age': {'type': 'number'},
'email': {'type': 'string'},
'skills': {
'type': 'array',
'items': {'type': 'string'},
},
},
'required': ['name', 'age', 'email', 'skills'],
}
message = client.messages.create(
model='anthropic/claude-sonnet-5',
max_tokens=1024,
messages=[
{
'role': 'user',
'content': 'Generate a profile for a software engineer in Austin, TX.'
}
],
output_config={
'format': {
'type': 'json_schema',
'schema': person_schema,
},
},
)
text_block = next(b for b in message.content if b.type == 'text')
person = json.loads(text_block.text)
print(person['name'], person['skills'])curl -X POST "https://ai-gateway.vercel.sh/v1/messages" \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Generate a profile for a software engineer in Austin, TX."
}
],
"output_config": {
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"age": { "type": "number" },
"email": { "type": "string" },
"skills": { "type": "array", "items": { "type": "string" } }
},
"required": ["name", "age", "email", "skills"]
}
}
}
}'You can combine format with effort in the same output_config object:
output_config: {
effort: 'high',
format: {
type: 'json_schema',
schema: personSchema,
},
},The beta API uses the output_format field along with the structured-outputs-2025-11-13 beta header.
import Anthropic from '@anthropic-ai/sdk';
const apiKey = process.env.AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN;
const anthropic = new Anthropic({
apiKey,
baseURL: 'https://ai-gateway.vercel.sh',
});
const forecastSchema = {
type: 'object',
additionalProperties: false,
properties: {
location: { type: 'string' },
temperature: { type: 'number' },
conditions: { type: 'string' },
forecast: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
properties: {
day: { type: 'string' },
high: { type: 'number' },
low: { type: 'number' },
conditions: { type: 'string' },
},
required: ['day', 'high', 'low', 'conditions'],
},
},
},
required: ['location', 'temperature', 'conditions', 'forecast'],
};
const message = await anthropic.beta.messages.create({
model: 'anthropic/claude-sonnet-5',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Give me a weather forecast for San Francisco, CA.',
},
],
output_format: {
type: 'json_schema',
schema: forecastSchema,
},
betas: ['structured-outputs-2025-11-13'],
});
const textBlock = message.content.find((b) => b.type === 'text');
if (textBlock?.type === 'text') {
const forecast = JSON.parse(textBlock.text);
console.log(forecast.location, forecast.temperature);
}import os
import json
import anthropic
api_key = os.getenv('AI_GATEWAY_API_KEY') or os.getenv('VERCEL_OIDC_TOKEN')
client = anthropic.Anthropic(
api_key=api_key,
base_url='https://ai-gateway.vercel.sh'
)
forecast_schema = {
'type': 'object',
'additionalProperties': False,
'properties': {
'location': {'type': 'string'},
'temperature': {'type': 'number'},
'conditions': {'type': 'string'},
'forecast': {
'type': 'array',
'items': {
'type': 'object',
'additionalProperties': False,
'properties': {
'day': {'type': 'string'},
'high': {'type': 'number'},
'low': {'type': 'number'},
'conditions': {'type': 'string'},
},
'required': ['day', 'high', 'low', 'conditions'],
},
},
},
'required': ['location', 'temperature', 'conditions', 'forecast'],
}
message = client.messages.create(
model='anthropic/claude-sonnet-5',
max_tokens=1024,
messages=[
{
'role': 'user',
'content': 'Give me a weather forecast for San Francisco, CA.'
}
],
extra_body={
'output_format': {
'type': 'json_schema',
'schema': forecast_schema,
},
},
extra_headers={
'anthropic-beta': 'structured-outputs-2025-11-13',
},
)
text_block = next(b for b in message.content if b.type == 'text')
forecast = json.loads(text_block.text)
print(forecast['location'], forecast['temperature'])curl -X POST "https://ai-gateway.vercel.sh/v1/messages" \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "anthropic-beta: structured-outputs-2025-11-13" \
-d '{
"model": "anthropic/claude-sonnet-5",
"max_tokens": 1024,
"messages": [
{ "role": "user", "content": "Give me a weather forecast for San Francisco, CA." }
],
"output_format": {
"type": "json_schema",
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"location": { "type": "string" },
"temperature": { "type": "number" },
"conditions": { "type": "string" },
"forecast": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"day": { "type": "string" },
"high": { "type": "number" },
"low": { "type": "number" },
"conditions": { "type": "string" }
},
"required": ["day", "high", "low", "conditions"]
}
}
},
"required": ["location", "temperature", "conditions", "forecast"]
}
}
}'Structured outputs work with streaming. The model produces valid JSON incrementally, and each text_delta event contains a fragment of the JSON. Accumulate the fragments and parse the complete JSON when the stream ends.
import Anthropic from '@anthropic-ai/sdk';
const apiKey = process.env.AI_GATEWAY_API_KEY || process.env.VERCEL_OIDC_TOKEN;
const anthropic = new Anthropic({
apiKey,
baseURL: 'https://ai-gateway.vercel.sh',
});
const recipeSchema = {
type: 'object',
additionalProperties: false,
properties: {
name: { type: 'string' },
cuisine: { type: 'string' },
difficulty: { type: 'string', enum: ['easy', 'medium', 'hard'] },
servings: { type: 'number' },
ingredients: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
properties: {
name: { type: 'string' },
amount: { type: 'string' },
},
required: ['name', 'amount'],
},
},
steps: { type: 'array', items: { type: 'string' } },
},
required: ['name', 'cuisine', 'difficulty', 'servings', 'ingredients', 'steps'],
};
const stream = await anthropic.beta.messages.create({
model: 'anthropic/claude-sonnet-5',
max_tokens: 2048,
stream: true,
messages: [
{
role: 'user',
content: 'Give me a recipe for classic Italian lasagna.',
},
],
output_format: {
type: 'json_schema',
schema: recipeSchema,
},
betas: ['structured-outputs-2025-11-13'],
});
let fullJson = '';
for await (const event of stream) {
if (
event.type === 'content_block_delta' &&
event.delta.type === 'text_delta'
) {
fullJson += event.delta.text;
}
}
const recipe = JSON.parse(fullJson);
console.log(recipe.name, recipe.cuisine);import os
import json
import anthropic
api_key = os.getenv('AI_GATEWAY_API_KEY') or os.getenv('VERCEL_OIDC_TOKEN')
client = anthropic.Anthropic(
api_key=api_key,
base_url='https://ai-gateway.vercel.sh'
)
recipe_schema = {
'type': 'object',
'additionalProperties': False,
'properties': {
'name': {'type': 'string'},
'cuisine': {'type': 'string'},
'difficulty': {'type': 'string', 'enum': ['easy', 'medium', 'hard']},
'servings': {'type': 'number'},
'ingredients': {
'type': 'array',
'items': {
'type': 'object',
'additionalProperties': False,
'properties': {
'name': {'type': 'string'},
'amount': {'type': 'string'},
},
'required': ['name', 'amount'],
},
},
'steps': {'type': 'array', 'items': {'type': 'string'}},
},
'required': ['name', 'cuisine', 'difficulty', 'servings', 'ingredients', 'steps'],
}
full_json = ''
with client.messages.stream(
model='anthropic/claude-sonnet-5',
max_tokens=2048,
messages=[
{
'role': 'user',
'content': 'Give me a recipe for classic Italian lasagna.'
}
],
extra_body={
'output_format': {
'type': 'json_schema',
'schema': recipe_schema,
},
},
extra_headers={
'anthropic-beta': 'structured-outputs-2025-11-13',
},
) as stream:
for text in stream.text_stream:
full_json += text
recipe = json.loads(full_json)
print(recipe['name'], recipe['cuisine'])curl -X POST "https://ai-gateway.vercel.sh/v1/messages" \
-H "Authorization: Bearer $AI_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "anthropic-beta: structured-outputs-2025-11-13" \
-d '{
"model": "anthropic/claude-sonnet-5",
"max_tokens": 1024,
"stream": true,
"messages": [
{ "role": "user", "content": "Give me a weather forecast for San Francisco, CA." }
],
"output_format": {
"type": "json_schema",
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"location": { "type": "string" },
"temperature": { "type": "number" },
"conditions": { "type": "string" },
"forecast": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"day": { "type": "string" },
"high": { "type": "number" },
"low": { "type": "number" },
"conditions": { "type": "string" }
},
"required": ["day", "high", "low", "conditions"]
}
}
},
"required": ["location", "temperature", "conditions", "forecast"]
}
}
}'A completed structured response contains JSON in a text content block:
{
"id": "msg_123",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "{\"name\":\"Alex Chen\",\"age\":29,\"email\":\"alex@example.com\",\"skills\":[\"TypeScript\",\"React\",\"Node.js\"]}"
}
],
"model": "anthropic/claude-sonnet-5",
"stop_reason": "end_turn",
"usage": {
"input_tokens": 25,
"output_tokens": 80
}
}- Set
additionalProperties: falseon all object types in your schema - Include a
requiredarray listing all properties on each object - Supported types:
string,number,boolean,array,object, andenum
Was this helpful?