CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/API & SDK
API & SDK/2026-03-21Beginner

First Claude API Call Failing? Common Error FAQ

Claude API beginner's error resolution guide. Covers 401 authentication errors, 429 rate limiting, 400 bad requests, empty responses, streaming failures, tool_use errors, context window exceeded, tool_result submission, and Spanner temporarily unavailable issues.

Claude API115Anthropic SDK4errors2beginners3FAQ2

Making your first Claude API request? Hitting errors like authentication failures, rate limiting, or invalid requests? This guide covers the 9 most common issues beginners face and how to fix each one.

Q1: 401 Authentication Error or "API key not recognized"

Root Causes

API key is missing, invalid, incorrect format, or not set as environment variable.

Solutions

Step 1: Get your API key

  1. Visit Anthropic Console
  2. Log in to your account
  3. Generate a new API key (or copy existing one)
  4. Key starts with sk-ant-

Step 2: Set the API key in your code

Python:

import anthropic
 
# Method 1: Environment variable (Recommended)
# First: export ANTHROPIC_API_KEY="sk-ant-..."
client = anthropic.Anthropic()  # Reads ANTHROPIC_API_KEY automatically
 
# Method 2: Explicit parameter (for testing only)
client = anthropic.Anthropic(api_key="sk-ant-your-key-here")
 
# Verify (only shows first characters)
print(client.api_key[:10])

JavaScript/Node.js:

import Anthropic from "@anthropic-ai/sdk";
 
// Method 1: Environment variable (Recommended)
// First: export ANTHROPIC_API_KEY="sk-ant-..."
const client = new Anthropic();  // Reads ANTHROPIC_API_KEY automatically
 
// Method 2: Explicit parameter
const client = new Anthropic({
  apiKey: "sk-ant-your-key-here",
});

cURL:

# Include API key in header
curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: sk-ant-your-key-here" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{...}'

Step 3: Set environment variable

# For Bash/Zsh
export ANTHROPIC_API_KEY="sk-ant-your-key-here"
 
# Make it permanent (add to ~/.bashrc or ~/.zshrc)
echo 'export ANTHROPIC_API_KEY="sk-ant-your-key-here"' >> ~/.bashrc
source ~/.bashrc
 
# Verify it's set
echo $ANTHROPIC_API_KEY

Step 4: Test the key

# Test with curl
curl -s 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-opus-4-1",
    "messages":[{"role":"user","content":"test"}],
    "max_tokens":100
  }' | head -c 100
 
# Should return API response (not an error)

:::warning If your API key is leaked:

  1. Delete the compromised key in Anthropic Console
  2. Generate a new one
  3. Update all references in your code
  4. Check git history for accidentally committed keys

Contact Anthropic support if fraudulent usage is detected on your account. :::


Q2: 429 Error (Too Many Requests) - Rate limiting

Root Causes

Exceeded API usage limits, sending too many requests simultaneously, or hitting quota limits.

Solutions

Step 1: Check rate limit details

# View rate limit info in response headers
curl -i 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 '{...}'
 
# Response includes:
# x-ratelimit-limit-requests: 6000
# x-ratelimit-remaining-requests: 5999
# x-ratelimit-reset-requests: 2026-03-21T18:05:00Z

Step 2: Implement exponential backoff retry logic

Python:

import anthropic
import time
 
def call_claude_with_retry(client, max_retries=5, backoff_factor=2.0):
    """Retry API call with exponential backoff"""
 
    for attempt in range(max_retries):
        try:
            response = client.messages.create(
                model="claude-opus-4-1",
                max_tokens=1024,
                messages=[{"role": "user", "content": "Hello, Claude!"}]
            )
            return response.content[0].text
 
        except anthropic.RateLimitError:
            if attempt == max_retries - 1:
                raise  # Give up on last attempt
 
            # Exponential backoff: 1s, 2s, 4s, 8s, 16s
            wait_time = backoff_factor ** attempt
            print(f"Rate limited. Waiting {wait_time} seconds...")
            time.sleep(wait_time)
 
# Usage
client = anthropic.Anthropic()
result = call_claude_with_retry(client)
print(result)

Node.js:

import Anthropic from "@anthropic-ai/sdk";
 
async function callClaudeWithRetry(client, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await client.messages.create({
        model: "claude-opus-4-1",
        max_tokens: 1024,
        messages: [{ role: "user", content: "Hello, Claude!" }],
      });
 
      return response.content[0].text;
 
    } catch (error) {
      if (error.status === 429) {
        if (attempt === maxRetries - 1) throw error;
 
        // Exponential backoff in milliseconds
        const waitMs = Math.pow(2, attempt) * 1000;
        console.log(`Rate limited. Waiting ${waitMs}ms...`);
        await new Promise((resolve) => setTimeout(resolve, waitMs));
      } else {
        throw error;
      }
    }
  }
}
 
const client = new Anthropic();
const result = await callClaudeWithRetry(client);
console.log(result);

Step 3: Limit concurrent requests

# ❌ Bad: Too many concurrent requests
import asyncio
 
async def bad_example():
    tasks = [
        client.messages.create(
            model="claude-opus-4-1",
            max_tokens=100,
            messages=[{"role": "user", "content": f"Task {i}"}]
        )
        for i in range(100)  # All 100 at once!
    ]
    return await asyncio.gather(*tasks)
 
# ✅ Good: Limit concurrency to 5
from asyncio import Semaphore
 
async def good_example():
    semaphore = Semaphore(5)  # Max 5 concurrent
 
    async def limited_request(i):
        async with semaphore:
            return await client.messages.create(
                model="claude-opus-4-1",
                max_tokens=100,
                messages=[{"role": "user", "content": f"Task {i}"}]
            )
 
    tasks = [limited_request(i) for i in range(100)]
    return await asyncio.gather(*tasks)

:::tip Rate limits differ by plan:

  • Free tier: 6,000 requests/minute, ~2M tokens/day
  • Paid tier: Varies by plan

Upgrade to a paid plan if you exceed free tier limits. :::


Q3: 400 Error (Bad Request) - Invalid request format

Root Causes

Wrong model name, missing required fields, invalid parameter values, or malformed JSON.

Solutions

Step 1: Verify model name

# ❌ Wrong (missing version)
response = client.messages.create(
    model="claude-opus",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}]
)
 
# ✅ Correct model names
response = client.messages.create(
    model="claude-opus-4-1",  # Latest Opus
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}]
)

Available models:

- claude-opus-4-1 (highest capability)
- claude-sonnet-4-20250514 (balanced)
- claude-haiku-4-5-20251001 (fast, lightweight)

Step 2: Include all required fields

# ✅ Required fields
response = client.messages.create(
    model="claude-opus-4-1",         # Required
    max_tokens=1024,                 # Required (1-200000)
    messages=[                       # Required
        {"role": "user", "content": "Hello"}
    ]
)
 
# ❌ Missing required max_tokens
response = client.messages.create(
    model="claude-opus-4-1",
    messages=[{"role": "user", "content": "Hello"}]
    # Error: max_tokens is required
)

Step 3: Validate parameter values

# ❌ Invalid parameter values
response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=0,         # ❌ Must be ≥ 1
    temperature=-0.5,     # ❌ Must be 0.0-1.0
    top_p=1.5,            # ❌ Must be 0.0-1.0
    messages=[{"role": "user", "content": "Hello"}]
)
 
# ✅ Valid parameter values
response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=1024,      # 1-200000
    temperature=0.7,      # 0.0-1.0
    top_p=0.9,            # 0.0-1.0
    messages=[{"role": "user", "content": "Hello"}]
)

Step 4: Check message format

# ❌ Wrong format (string instead of dict)
messages = [
    "Hello, Claude!"  # ❌ Must be a dict
]
 
# ✅ Correct format
messages = [
    {
        "role": "user",  # "user" or "assistant"
        "content": "Hello, Claude!"
    }
]
 
# ✅ Complex content (text + image)
messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "text",
                "text": "What's in this image?"
            },
            {
                "type": "image",
                "source": {
                    "type": "base64",
                    "media_type": "image/jpeg",
                    "data": "base64-encoded-image"
                }
            }
        ]
    }
]

Step 5: Debug the error

import anthropic
 
try:
    response = client.messages.create(
        model="claude-opus-4-1",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello"}]
    )
except anthropic.BadRequestError as e:
    print(f"Error type: {e.error}")
    print(f"Error message: {e.message}")
    # Error message contains specific details about what was wrong

:::warning Check API reference docs for:

  • Valid model names (they change as new versions release)
  • Acceptable parameter ranges
  • Required vs optional fields :::

Q4: Empty response or max_tokens too small

Root Causes

max_tokens set too low (response gets truncated), or API call incomplete.

Solutions

Step 1: Set appropriate max_tokens

# ❌ max_tokens too small
response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=1,  # ❌ Only 1 token of output!
    messages=[{"role": "user", "content": "Write a 500-word essay"}]
)
# Result: truncated or empty response
 
# ✅ Reasonable max_tokens
response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=2048,  # Typical value
    messages=[{"role": "user", "content": "Write an essay"}]
)

Step 2: Check if response is complete

response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}]
)
 
# Check completion status
if response.stop_reason == "end_turn":
    print("✓ Response is complete")
elif response.stop_reason == "max_tokens":
    print("❌ Response was truncated (increase max_tokens)")
else:
    print(f"Stop reason: {response.stop_reason}")

Step 3: Verify response content

# Check response isn't empty
if response.content:
    text = response.content[0].text
    print(f"Response length: {len(text)} characters")
else:
    print("❌ Response is empty")
 
# View full response structure
print(f"Stop reason: {response.stop_reason}")
print(f"Usage: input={response.usage.input_tokens}, output={response.usage.output_tokens}")
print(f"Content blocks: {len(response.content)}")

Step 4: Verify your prompt

# ❌ Vague prompt might get empty response
messages = [{"role": "user", "content": ""}]
 
# ✅ Clear, specific prompt
messages = [{
    "role": "user",
    "content": "Explain this Python code: [code snippet here]"
}]

:::tip Context window limits by model:

  • Claude Opus 4.1: 200,000 tokens
  • Claude Sonnet 4: 200,000 tokens
  • Claude Haiku: 200,000 tokens

Set max_tokens within context window limits. For typical tasks, 1024-4096 tokens is usually sufficient. :::


Q5: Streaming not working or partial response

Root Causes

Incorrect streaming implementation, network disconnection, or incomplete request.

Solutions

Step 1: Basic streaming implementation

Python:

import anthropic
 
client = anthropic.Anthropic()
 
# ✅ Enable streaming
with client.messages.stream(
    model="claude-opus-4-1",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello, Claude!"}]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)  # flush() for real-time
 
print()  # Newline at end

Node.js:

import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic();
 
const stream = await client.messages.stream({
  model: "claude-opus-4-1",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello, Claude!" }],
});
 
// Print streamed text in real-time
for await (const chunk of stream) {
  if (chunk.type === "content_block_delta" && chunk.delta.type === "text_delta") {
    process.stdout.write(chunk.delta.text);
  }
}

Step 2: Error handling for streams

try:
    with client.messages.stream(
        model="claude-opus-4-1",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello"}]
    ) as stream:
        full_response = ""
        for text in stream.text_stream:
            full_response += text
            print(text, end="", flush=True)
 
        # After streaming ends
        final_message = stream.get_final_message()
        print(f"\nStop reason: {final_message.stop_reason}")
 
except anthropic.APIError as e:
    print(f"Stream error: {e}")

Step 3: Set timeout for long streams

# Set timeout to avoid hanging indefinitely
client = anthropic.Anthropic(timeout=60.0)  # 60 seconds
 
try:
    with client.messages.stream(
        model="claude-opus-4-1",
        max_tokens=1024,
        messages=[{"role": "user", "content": "Hello"}]
    ) as stream:
        for text in stream.text_stream:
            print(text, end="", flush=True)
 
except anthropic.APITimeoutError:
    print("Request timed out")

Step 4: Check network connectivity

# Test connection to API
curl -I https://api.anthropic.com
 
# Should return 200 OK

:::warning Streaming tips:

  • Use flush() to display text in real-time
  • Don't buffer responses in memory (defeats streaming purpose)
  • Handle timeout errors gracefully
  • Be aware of network interruptions :::

Q6: Tool use response format incorrect or unexpected

Root Causes

Malformed tool definition, incorrect tool_choice setting, or incomplete tool schema.

Solutions

Step 1: Define tools correctly

# ✅ Correct tool definition
tools = [
    {
        "name": "calculator",
        "description": "Performs mathematical operations",
        "input_schema": {
            "type": "object",
            "properties": {
                "operation": {
                    "type": "string",
                    "enum": ["add", "subtract", "multiply", "divide"],
                    "description": "The operation to perform"
                },
                "a": {
                    "type": "number",
                    "description": "First operand"
                },
                "b": {
                    "type": "number",
                    "description": "Second operand"
                }
            },
            "required": ["operation", "a", "b"]
        }
    }
]

Step 2: Use tool in messages request

# ✅ Request with tools
response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "What is 5 + 3?"}
    ]
)
 
# Check if tool was called
for block in response.content:
    if block.type == "tool_use":
        print(f"Tool used: {block.name}")
        print(f"Input: {block.input}")

Step 3: Control tool usage with tool_choice

# Auto: Claude decides whether to use tools
response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "auto"},
    messages=[{"role": "user", "content": "Calculate something"}]
)
 
# Or force a specific tool
response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=1024,
    tools=tools,
    tool_choice={
        "type": "tool",
        "name": "calculator"  # Force this tool
    },
    messages=[{"role": "user", "content": "Calculate something"}]
)

Step 4: Loop tool calls until completion

messages = [
    {"role": "user", "content": "Calculate 100 + 200 + 300"}
]
 
while True:
    # Get response
    response = client.messages.create(
        model="claude-opus-4-1",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )
 
    # Add assistant response to history
    messages.append({"role": "assistant", "content": response.content})
 
    # Look for tool calls
    tool_calls = [block for block in response.content if block.type == "tool_use"]
 
    if not tool_calls:
        # No tools called, done
        break
 
    # Execute tools and add results
    for tool_call in tool_calls:
        # Simulate tool execution
        if tool_call.name == "calculator":
            a, b = tool_call.input["a"], tool_call.input["b"]
            result = a + b
 
        # Add tool result
        messages.append({
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_call.id,
                    "content": str(result)
                }
            ]
        })
 
# Final response in messages[-1]

:::warning Tool schema requirements:

  • Must be valid JSON Schema
  • All required fields in "required" array
  • Clear, detailed descriptions (Claude uses these for selection)
  • Reasonable input constraints :::

Q7: Context window exceeded error

Root Causes

Total input size exceeds model's maximum token limit.

Solutions

Step 1: Catch the error

try:
    response = client.messages.create(
        model="claude-opus-4-1",
        max_tokens=1024,
        messages=[
            {"role": "user", "content": very_long_text}
        ]
    )
except anthropic.BadRequestError as e:
    if "context_length_exceeded" in str(e):
        print("Context window exceeded! Split your input.")

Step 2: Split large inputs

# ❌ Bad: Send entire large document at once
long_text = "..." * 100000  # Too large
 
# ✅ Good: Split and process in chunks
def summarize_large_text(text, chunk_size=10000):
    chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
    summaries = []
 
    for i, chunk in enumerate(chunks):
        response = client.messages.create(
            model="claude-opus-4-1",
            max_tokens=500,
            messages=[{
                "role": "user",
                "content": f"Summarize section {i+1}/{len(chunks)}:\n{chunk}"
            }]
        )
        summaries.append(response.content[0].text)
 
    return summaries

Step 3: Manage conversation history

# Keep recent messages, discard old ones
def trim_history(messages, max_messages=10):
    """Keep only recent messages within limit"""
    system_msgs = [m for m in messages if m.get("role") == "system"]
    other_msgs = [m for m in messages if m.get("role") != "system"]
 
    if len(other_msgs) > max_messages:
        # Keep most recent
        other_msgs = other_msgs[-max_messages:]
 
    return system_msgs + other_msgs

Step 4: Monitor token usage

response = client.messages.create(
    model="claude-opus-4-1",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}]
)
 
input_tokens = response.usage.input_tokens
output_tokens = response.usage.output_tokens
total = input_tokens + output_tokens
 
print(f"Input tokens: {input_tokens}")
print(f"Output tokens: {output_tokens}")
print(f"Total: {total}")

:::tip Context window limits:

  • Claude Opus 4.1: 200,000 tokens (~1.5M words)
  • Claude Sonnet 4: 200,000 tokens
  • Claude Haiku: 200,000 tokens

For large document processing, always split into multiple API calls. :::


Q8: "tool_result could not be submitted" stops the conversation

Cause

Almost always, your tool_use and tool_result blocks are out of sync. Common cases: the tool_use_id doesn't match, you send a tool_result without first appending the assistant's tool_use turn, or you return tool_results for only some of the tool_use blocks.

Claude strictly requires a one-to-one pairing between the tool_use blocks in the previous assistant turn and the tool_result blocks in the next user turn. Miss even one and the entire request is rejected.

Solution

Step 1: Append the assistant response as-is

response = client.messages.create(
    model="claude-opus-4-1", max_tokens=1024, tools=tools, messages=messages
)
 
# Append the full response.content (including tool_use blocks)
messages.append({"role": "assistant", "content": response.content})

Don't rebuild the tool_use blocks yourself. Passing back the returned response.content verbatim is the most robust approach.

Step 2: Return a tool_result for every tool_use

tool_uses = [b for b in response.content if b.type == "tool_use"]
 
tool_results = []
for tu in tool_uses:
    try:
        output = run_tool(tu.name, tu.input)
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": tu.id,   # must match exactly
            "content": str(output),
        })
    except Exception as e:
        # Always return a tool_result, even on failure — set is_error
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": tu.id,
            "content": f"Error: {e}",
            "is_error": True,
        })
 
# Return all results in a single user message
messages.append({"role": "user", "content": tool_results})

If three tools were called, you need three tool_results. Sending fewer triggers this error.

:::warning Frequent mistakes:

  • Typo in tool_use_id, or reusing an id from a different call
  • Tool execution throws and you forget to append the tool_result (always return one with is_error)
  • A text block inserted before the tool_result (the content array must start with tool_result) :::

Q9: "Spanner temporarily unavailable" appears out of nowhere

Cause

This is not a problem in your code. It's a transient server-side error indicating that Anthropic's backend (a distributed database layer) is momentarily unavailable. Like 529 (Overloaded) and other 5xx errors, it clears up on its own after a short wait.

Seeing this on your first integration can make you doubt your request — as an indie developer, I paused and checked my setup the first time too — but assuming the request format is valid, all you need is a solid retry strategy.

Solution

Step 1: Retry with exponential backoff, treating it as transient

import time, random
import anthropic
 
def create_with_retry(client, max_retries=5, **kwargs):
    for attempt in range(max_retries):
        try:
            return client.messages.create(**kwargs)
        except (anthropic.InternalServerError, anthropic.APIStatusError) as e:
            msg = str(e).lower()
            transient = "spanner" in msg or "overloaded" in msg or "temporarily" in msg
            if not transient or attempt == max_retries - 1:
                raise
            # Exponential backoff + jitter to avoid synchronized retries
            wait = min(2 ** attempt, 30) + random.uniform(0, 1)
            time.sleep(wait)

Step 2: Don't hammer the API Retrying rapidly before recovery only prolongs the congestion. Wait 1–2 seconds first, then double each time up to a cap.

:::tip Quick rule of thumb:

  • Spanner temporarily unavailable / Overloaded / 500 / 529 → transient server-side; retry with backoff
  • 400 / 401 / 404 → your request is wrong; retrying won't help

If it persists, check the Anthropic status page for incidents. :::


Looking back

Most Claude API errors fall into these categories:

  1. Authentication → Verify API key is set correctly (Q1)
  2. Rate limiting → Implement exponential backoff and limit concurrency (Q2)
  3. Request format → Check model names, required fields, and parameter values (Q3)
  4. Empty/truncated responses → Increase max_tokens or fix your prompt (Q4)
  5. Streaming → Use proper stream syntax and error handling (Q5)
  6. Tool use → Validate tool schema and loop until completion (Q6)
  7. Large inputs → Split into smaller chunks and process sequentially (Q7)
  8. tool_result submission → Pair every tool_use with a matching tool_result (Q8)
  9. Transient server errors → Retry "Spanner temporarily unavailable" with backoff (Q9)

Save complete error messages and check the API error code. Most issues resolve by following these steps.

Happy building with Claude API!

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API & SDK2026-06-16
Confirm Your Model Actually Responds Before a Scheduled Run Begins
A model you configured can be gone before your nightly job even wakes up. Tell retirement, withdrawal, and regional restriction apart with a single startup probe, then rewrite the run config to an eligible model — with complete, working TypeScript.
API & SDK2026-06-15
Centralizing the anthropic-beta Header So a Retired Beta Won't Kill Your Batch
Scattered anthropic-beta headers turn a beta retirement or GA graduation into a 400 that takes down an entire batch. A small capability registry, a startup preflight, and tiered fallback keep your pipeline running across feature generations.
API & SDK2026-04-20
Three Hidden Pitfalls When Implementing Claude API Streaming
Real-world lessons from building with Claude API streaming: runtime environment mismatches, error handling gaps, and silent token cost overruns — with working TypeScript examples.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →