ai / claude
My guidelines for the Claude API. I use Ruby or Go; the examples use raw HTTP/JSON.
Model selection
- Haiku (
claude-haiku-4-5): short summarization, scoring, classification. - Sonnet (
claude-sonnet-5): default for extraction and writing. - Opus (
claude-opus-5): research and high-stakes tasks. - Use model aliases, not dated snapshots.
- Pin server tool types as shared constants. They are dated, so re-check them when changing models.
Structured output
A call that needs structured data forces one output tool. The answer
arrives in the tool_use block's input.
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-haiku-4-5",
"max_tokens": 512,
"messages": [
{
"role": "user",
"content": "Write a YC-style company headline for an AI email triage app."
}
],
"tools": [
{
"name": "record_result",
"description": "Record the result in the structured fields.",
"strict": true,
"input_schema": {
"type": "object",
"properties": {
"headline": {
"type": "string",
"description": "A Y Combinator-style company headline, 80 characters or less."
}
},
"required": ["headline"],
"additionalProperties": false
}
}
],
"tool_choice": {
"type": "tool",
"name": "record_result",
"disable_parallel_tool_use": true
}
}'
output_config.format with a json_schema returns the object as text,
which can fail to close a string and lose later fields. A tool_use
input cannot fail that way.
disable_parallel_tool_use keeps the response to one block.
Strict schemas
strict: true forces the arguments to match the schema. It accepts a
subset of JSON Schema:
type, properties, required, additionalProperties, items, enum, const,
description, title, anyOf, $ref, $defs, definitions
Any other keyword (maxLength, pattern, format, minimum,
minItems, default) returns a 400. I put the constraint in the field
description and check schemas in a test.
Adaptive thinking
I enable adaptive thinking for Sonnet and Opus calls that return prose:
"thinking": {"type": "adaptive"}
Haiku does not support it. The API refuses to combine thinking with a
forced strict tool_choice, so a structured call runs without thinking.
My client rejects the combination.
Research in two passes
Pass 1 runs a web search and returns prose. Code execution filters retrieved data before it enters context.
{
"model": "claude-opus-5",
"thinking": { "type": "adaptive" },
"tools": [
{
"type": "web_search_20260209",
"name": "web_search",
"max_uses": 5,
"allowed_callers": ["direct"]
},
{ "type": "code_execution_20260521", "name": "code_execution" }
]
}
Pass 2 shapes that prose into a schema with a separate forced tool call.
When server tools and an output tool share one request, I use
tool_choice: {"type": "auto", "disable_parallel_tool_use": true}. A
forced output tool makes the model call it on turn one, before any
search, with placeholder fields. If the model returns no tool call, I
retry once: replay its text as an assistant turn, add a user turn that
asks for the tool, and force it. The input ends on a user turn because
the API does not combine a prefill with tool use.
Direct callers
I set allowed_callers: ["direct"] on web_search and web_fetch. An
unset caller makes the request eligible for programmatic tool calling,
which the API refuses to combine with disable_parallel_tool_use or a
strict: true tool.
Fetch limits
I give web_fetch a max_uses and a max_content_tokens:
{
"type": "web_fetch_20260209",
"name": "web_fetch",
"max_uses": 5,
"max_content_tokens": 20000,
"allowed_callers": ["direct"]
}
Without a content cap, the loop accumulates whole pages and exceeds the context window. I divide the input headroom after the prompt across the allowed fetches.
System prompt
I use the system prompt only to separate instructions from untrusted input (scraped pages, user text). Otherwise instructions go in the user prompt.
Hallucination guardrails
I apply Anthropic's guidance:
- Restrict answers to provided data.
- Allow explicit uncertainty (
"Insufficient data"). - Require inline citations (
[n]) for research claims. - Pass structured context as JSON.
Handle "Insufficient data" before database writes.
Context window budgeting
Maximum prompt size:
(context_window - max_output_tokens - buffer) * 4 chars/token
My defaults: 64k output for Haiku and Sonnet, 128k for Opus, and a 5k buffer. Sonnet supports a 1M window. I keep it at 200k.
Retries
I use two retry policies: one at the HTTP layer and one above it.
The HTTP layer retries on 502, 503, 504, 429, 529, and 500. Anthropic returns intermittent 500 errors under load, and a retry recovers most of them.
The layer above retries a successful response with no usable answer at most once. A response that hit the output budget retries with double the budget. A response with no tool call retries only when the request changes.
Timeouts differ by call type. A single generation takes seconds. A server-side search loop takes minutes: a 10-search Opus run took 250 seconds. I keep the client timeout inside the caller's deadline, so the client reports the failure with more detail.