Loading...
Loading...
Build with Claude Messages API using structured outputs for guaranteed JSON schema validation. Covers prompt caching (90% savings), streaming SSE, tool use, and model deprecations. Prevents 16 documented errors. Use when: building chatbots/agents, troubleshooting rate_limit_error, prompt caching issues, streaming SSE parsing errors, MCP timeout issues, or structured output hallucinations.
npx skill4agent add jezweb/claude-skills claude-apioutput_formatimport Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Extract contact info: John Doe, john@example.com, 555-1234' }],
betas: ['structured-outputs-2025-11-13'],
output_format: {
type: 'json_schema',
json_schema: {
name: 'Contact',
strict: true,
schema: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' },
phone: { type: 'string' }
},
required: ['name', 'email', 'phone'],
additionalProperties: false
}
}
}
});
// Guaranteed valid JSON matching schema
const contact = JSON.parse(message.content[0].text);
console.log(contact.name); // "John Doe"strict: trueconst message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Get weather for San Francisco' }],
betas: ['structured-outputs-2025-11-13'],
tools: [{
name: 'get_weather',
description: 'Get current weather',
input_schema: {
type: 'object',
properties: {
location: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location'],
additionalProperties: false
},
strict: true // ← Guarantees schema compliance
}]
});structured-outputs-2025-11-13betasminimummaximum// Pre-compile schemas during server startup
const warmupMessage = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 10,
messages: [{ role: 'user', content: 'warmup' }],
betas: ['structured-outputs-2025-11-13'],
output_format: {
type: 'json_schema',
json_schema: YOUR_CRITICAL_SCHEMA
}
});
// Later requests use cached grammarconst message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
messages: [{ role: 'user', content: 'Extract contact: John Doe' }],
betas: ['structured-outputs-2025-11-13'],
output_format: {
type: 'json_schema',
json_schema: contactSchema
}
});
const contact = JSON.parse(message.content[0].text);
// ✅ Format is guaranteed valid
// ❌ Content may be hallucinated
// ALWAYS validate semantic correctness
if (!isValidEmail(contact.email)) {
throw new Error('Hallucinated email detected');
}
if (contact.age < 0 || contact.age > 120) {
throw new Error('Implausible age value');
}.parsed| Model | ID | Context | Best For | Cost (per MTok) |
|---|---|---|---|---|
| Claude Opus 4.5 | claude-opus-4-5-20251101 | 200k | Flagship - best reasoning, coding, agents | $5/$25 (in/out) |
| Claude Sonnet 4.5 | claude-sonnet-4-5-20250929 | 200k | Balanced performance | $3/$15 (in/out) |
| Claude Opus 4 | claude-opus-4-20250514 | 200k | High capability | $15/$75 |
| Claude Haiku 4.5 | claude-haiku-4-5-20250929 | 200k | Near-frontier, fast | $1/$5 |
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 4096,
messages: [{ role: 'user', content: 'Solve complex problem' }],
betas: ['clear_thinking_20251015']
});
// Thinking blocks automatically managedconst message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Analyze this spreadsheet' }],
betas: ['skills-2025-10-02'],
// Requires code execution tool enabled
});const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }],
});
stream
.on('error', (error) => {
// Error can occur AFTER stream starts
console.error('Stream error:', error);
// Implement fallback or retry logic
})
.on('abort', (error) => {
console.warn('Stream aborted:', error);
});cache_controlconst message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
system: [
{
type: 'text',
text: 'System instructions...',
},
{
type: 'text',
text: LARGE_CODEBASE, // 50k tokens
cache_control: { type: 'ephemeral' }, // ← MUST be on LAST block
},
],
messages: [{ role: 'user', content: 'Explain auth module' }],
});
// Monitor cache usage
console.log('Cache reads:', message.usage.cache_read_input_tokens);
console.log('Cache writes:', message.usage.cache_creation_input_tokens);const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
betas: ['structured-outputs-2025-11-13'],
tools: [{
name: 'get_weather',
description: 'Get weather data',
input_schema: {
type: 'object',
properties: {
location: { type: 'string' },
unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }
},
required: ['location'],
additionalProperties: false
},
strict: true // ← Guarantees schema compliance
}],
messages: [{ role: 'user', content: 'Weather in NYC?' }]
});tool_use_idconst toolResults = [];
for (const block of response.content) {
if (block.type === 'tool_use') {
const result = await executeToolFunction(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id, // ← MUST match tool_use block id
content: JSON.stringify(result),
});
}
}
messages.push({
role: 'user',
content: toolResults,
});try {
const result = await executeToolFunction(block.name, block.input);
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result),
});
} catch (error) {
// Return error to Claude for handling
toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
is_error: true,
content: `Tool execution failed: ${error.message}`,
});
}// U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) cause JSON parse failures
function sanitizeToolResult(content: string): string {
return content
.replace(/\u2028/g, '\n') // LINE SEPARATOR → newline
.replace(/\u2029/g, '\n'); // PARAGRAPH SEPARATOR → newline
}
const toolResult = {
type: 'tool_result',
tool_use_id: block.id,
content: sanitizeToolResult(result) // Sanitize before sending
};const validFormats = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
if (!validFormats.includes(mimeType)) {
throw new Error(`Unsupported format: ${mimeType}`);
}max_tokensretry-afterasync function makeRequestWithRetry(
requestFn: () => Promise<any>,
maxRetries = 3,
baseDelay = 1000
): Promise<any> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await requestFn();
} catch (error) {
if (error.status === 429) {
// CRITICAL: Use retry-after header if present
const retryAfter = error.response?.headers?.['retry-after'];
const delay = retryAfter
? parseInt(retryAfter) * 1000
: baseDelay * Math.pow(2, attempt);
console.warn(`Rate limited. Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw new Error('Max retries exceeded');
}anthropic-ratelimit-requests-limitanthropic-ratelimit-requests-remaininganthropic-ratelimit-requests-reset| Status | Error Type | Cause | Solution |
|---|---|---|---|
| 400 | invalid_request_error | Bad parameters | Validate request body |
| 401 | authentication_error | Invalid API key | Check env variable |
| 403 | permission_error | No access to feature | Check account tier |
| 404 | not_found_error | Invalid endpoint | Check API version |
| 429 | rate_limit_error | Too many requests | Implement retry logic |
| 500 | api_error | Internal error | Retry with backoff |
| 529 | overloaded_error | System overloaded | Retry later |
retry-after429 Too Many Requests: Number of request tokens has exceeded your per-minute rate limitretry-aftercache_controlcache_controlinvalid_request_error: tools[0].input_schema is invalidinvalid_request_error: image source must be base64 or urlinvalid_request_error: messages: too many tokensinvalid_request_error: unknown parameter: batchesanthropic-betaanthropic-beta: message-batches-2024-09-24messages.stream().withResponse()try {
const stream = await anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }]
}).withResponse();
} catch (error) {
// Now properly catchable in v0.71.2+
console.error('Stream error:', error);
}const stream = anthropic.messages.stream({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello' }]
});
stream.on('error', (error) => {
console.error('Stream error:', error);
});Connection error499 Client disconnected// Don't use MCP for long requests
const message = await anthropic.beta.messages.toolRunner({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 4096,
messages: [{ role: 'user', content: 'Long task >2 min' }],
tools: [customTools] // Direct tool definitions, not MCP
});const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
messages: [{ role: 'user', content: 'Extract contact: John Doe' }],
betas: ['structured-outputs-2025-11-13'],
output_format: {
type: 'json_schema',
json_schema: contactSchema
}
});
const contact = JSON.parse(message.content[0].text);
// ✅ Format is guaranteed valid
// ❌ Content may be hallucinated
// CRITICAL: Validate semantic correctness
if (!isValidEmail(contact.email)) {
throw new Error('Hallucinated email detected');
}
if (contact.age < 0 || contact.age > 120) {
throw new Error('Implausible age value');
}function sanitizeToolResult(content: string): string {
return content
.replace(/\u2028/g, '\n') // LINE SEPARATOR → newline
.replace(/\u2029/g, '\n'); // PARAGRAPH SEPARATOR → newline
}
const toolResult = {
type: 'tool_result',
tool_use_id: block.id,
content: sanitizeToolResult(result)
};{
"dependencies": {
"@anthropic-ai/sdk": "^0.71.2"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.3.0",
"zod": "^3.23.0"
}
}