ai / claude

My guidelines for the Claude API. I use Ruby or Go; the examples use raw HTTP/JSON.

Model selection

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:

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.

← All articles