Production Claude: Building with the Anthropic SDK Beyond the Chat Interface

Beginner22m readFull-stack developers

Production Claude: Building with the Anthropic SDK Beyond the Chat Interface Claude Code is great for development work, but production AI apps need the Anthropic SDK directly.

Primary Focus

ai development

AI Tools Covered

AI-firstNext.jsConvex

What You'll Learn

  • The Decision Matrix and Basic Setup
  • Current Pricing (April 2026)
  • Basic Setup
  • The Tool Use Loop and Parallel Tool Handling
  • Error Handling and Retry Logic
  • Error Handling in Tools

Guide Curriculum

SDK vs Claude Code — When to Use Each

Learn key concepts

3 lessons
  • The Decision Matrix and Basic Setup1m
  • Current Pricing (April 2026)1m
  • Basic Setup1m

Tool Use — The Complete Loop with Parallel Handling

Learn key concepts

5 lessons
  • The Tool Use Loop and Parallel Tool Handling2m
  • Error Handling and Retry Logic1m
  • Error Handling in Tools1m
  • Handling API-Level Failures2m
  • Infinite Loop Guards1m

Streaming + Prompt Caching in a Next.js API Route

Learn key concepts

10 lessons
  • Streaming Responses and Token Management1m
  • Server-Side Streaming (Next.js Route Handler)1m
  • Client-Side Stream Consumption1m
  • Token Management and Budget Guards1m
  • Prompt Caching1m
  • Prompt Caching1m
  • Cost Tracking1m
  • Cost Tracking1m
  • The Batch API1m
  • The Batch API: 50% Off for Async Workloads1m

Putting It Together

Learn key concepts

2 lessons
  • Next Steps and Production Checklist1m
  • Production Readiness Checklist1m

Preview: First Lesson

SDK vs Claude Code — When to Use Each

The Decision Matrix and Basic Setup

SDK vs Claude Code — When to Use Each

Use Claude Code if:

  • You're building tools for yourself or your team
  • The work is exploratory or one-off
  • You need interactive debugging

Use the Anthropic SDK if:

  • You're building a production service
  • Multiple users will interact with it
  • You need custom authentication or cost control
  • You're integrating Claude into a larger app
Free Access

Start learning with this comprehensive guide

This guide includes:

4 modules with 20 lessons
22m estimated reading time

About the Author

H
✨ Vibe Coder
@hiram-clark

Hiram Clark is the founder and managing editor of vybecoding.ai and sets editorial direction for the guides and news published here. Articles are drafted with AI assistance and edited before publication. He works hands-on with the AI development tools, workflows, and infrastructure covered on the site.

Full Guide Content

Complete lesson text — start the interactive course above for exercises and progress tracking.

Module 1SDK vs Claude Code — When to Use Each

1.1The Decision Matrix and Basic Setup

SDK vs Claude Code — When to Use Each

Use Claude Code if:
  • You're building tools for yourself or your team
  • The work is exploratory or one-off
  • You need interactive debugging
Use the Anthropic SDK if:
  • You're building a production service
  • Multiple users will interact with it
  • You need custom authentication or cost control
  • You're integrating Claude into a larger app

1.2Current Pricing (April 2026)

| Model | Input (per MTok) | Output (per MTok) |

|---|---|---|

| Haiku 4.5 | $1.00 | $5.00 |

| Sonnet 4.6 | $3.00 | $15.00 |

| Opus 4.7 | $5.00 | $25.00 |

Cache pricing (Sonnet 4.6):
  • Standard input: $3.00/MTok
  • Cache write (5-min TTL): $3.75/MTok (1.25x)
  • Cache write (1-hour TTL): $6.00/MTok (2x)
  • Cache read: $0.30/MTok (0.1x)

1.3Basic Setup

npm install @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY
});

const message = await client.messages.create({
  model: "claude-sonnet-4-6-20250514",
  max_tokens: 1024,
  messages: [{ role: "user", content: "What is the capital of France?" }]
});

console.log(message.content[0].text);

Module 2Tool Use — The Complete Loop with Parallel Handling

2.1The Tool Use Loop and Parallel Tool Handling

Tool Use — The Complete Loop

Claude can call tools you provide. The loop works like this:

  1. Call Claude with available tools
  2. Check stop_reason: is it "tool_use"?
  3. If yes: extract tool calls, execute them
  4. Loop back with results
  5. If "end_turn": Claude is done
const tools: Anthropic.Messages.Tool[] = [
  {
    name: "get_weather",
    description: "Get weather for a city",
    input_schema: {
      type: "object" as const,
      properties: {
        city: { type: "string" }
      },
      required: ["city"]
    }
  }
];

let messages: Anthropic.Messages.MessageParam[] = [
  { role: "user", content: "What's the weather in Seattle?" }
];

// Main loop
while (true) {
  const response = await client.messages.create({
    model: "claude-sonnet-4-6-20250514",
    max_tokens: 1024,
    tools,
    messages
  });

  if (response.stop_reason === "end_turn") {
    const text = response.content.find((c) => c.type === "text");
    console.log("Final answer:", text?.type === "text" ? text.text : "");
    break;
  }

  if (response.stop_reason === "tool_use") {
    const toolUseBlocks = response.content.filter(
      (c) => c.type === "tool_use"
    ) as Anthropic.Messages.ToolUseBlock[];

    // Execute all tools in PARALLEL — Claude may return multiple
    const results = await Promise.all(
      toolUseBlocks.map(async (block) => {
        const result = await executeTool(block.name, block.input);
        return {
          type: "tool_result" as const,
          tool_use_id: block.id,
          content: JSON.stringify(result)
        };
      })
    );

    // Add Claude's response and all tool results to messages
    messages.push({ role: "assistant", content: response.content });
    messages.push({ role: "user", content: results });
  }
}
The parallel handling is critical. Claude may return multiple tool_use blocks in a single response. If you only handle the first one, you'll get an API error on the next turn because Claude expects results for all tools it called.

2.3Error Handling in Tools

If a tool fails, tell Claude explicitly rather than throwing and crashing:

return {
  type: "tool_result" as const,
  tool_use_id: block.id,
  content: `Error: ${error.message}`,
  is_error: true
};

Claude sees the error and can decide to retry, ask the user for help, or try a different approach. This is far better than letting an uncaught exception kill the whole request.

2.4Handling API-Level Failures

Production traffic surfaces failures that never appear in development: rate limits, overloaded servers, network timeouts. The SDK throws typed errors you can inspect and handle specifically.

import Anthropic from "@anthropic-ai/sdk";

async function callClaudeWithRetry(
  client: Anthropic,
  params: Anthropic.Messages.MessageCreateParamsNonStreaming,
  maxRetries = 3
): Promise {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.messages.create(params);
    } catch (error) {
      if (error instanceof Anthropic.RateLimitError) {
        // Back off and retry — rate limits are temporary
        const waitMs = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
        await new Promise((resolve) => setTimeout(resolve, waitMs));
        lastError = error;
        continue;
      }

      if (error instanceof Anthropic.APIConnectionError) {
        // Network issue — retry immediately once, then back off
        const waitMs = attempt === 0 ? 0 : 2000;
        await new Promise((resolve) => setTimeout(resolve, waitMs));
        lastError = error;
        continue;
      }

      if (error instanceof Anthropic.InternalServerError) {
        // Anthropic server error — short wait and retry
        await new Promise((resolve) => setTimeout(resolve, 1000));
        lastError = error;
        continue;
      }

      // AuthenticationError, PermissionDeniedError, InvalidRequestError
      // These won't resolve with retries — throw immediately
      throw error;
    }
  }

  throw lastError ?? new Error("Max retries exceeded");
}
Key distinction: Rate limit errors and server errors are worth retrying. Authentication errors, invalid parameters, and permission errors will never resolve — retrying them wastes time and quota.

2.5Infinite Loop Guards

Tool use loops can run indefinitely if Claude keeps calling tools without reaching end_turn. Always add a turn cap:

const MAX_TURNS = 10;
let turnCount = 0;

while (true) {
  if (turnCount++ >= MAX_TURNS) {
    throw new Error(`Tool loop exceeded ${MAX_TURNS} turns — possible infinite loop`);
  }

  const response = await client.messages.create({ ... });

  if (response.stop_reason === "end_turn") break;
  // ... handle tool_use
}

This is especially important in production where a bad prompt or edge-case input could trap a request in a loop, running up your bill and blocking the user.


Module 3Streaming + Prompt Caching in a Next.js API Route

3.1Streaming Responses and Token Management

Streaming Responses

Streaming sends tokens to the client as they arrive instead of waiting for the full response. For users, this means text appears immediately rather than after a 5–10 second wait — a significant UX improvement for anything generating more than a sentence or two.

3.2Server-Side Streaming (Next.js Route Handler)

// app/api/stream/route.ts
import { Anthropic } from "@anthropic-ai/sdk";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const { query } = await req.json();

  const client = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY
  });

  const stream = await client.messages.stream({
    model: "claude-sonnet-4-6-20250514",
    max_tokens: 1024,
    messages: [{ role: "user", content: query }]
  });

  return new NextResponse(stream.toReadableStream(), {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache"
    }
  });
}

3.3Client-Side Stream Consumption

const response = await fetch("/api/stream", {
  method: "POST",
  body: JSON.stringify({ query: "Explain quantum computing" })
});

const reader = response.body?.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader!.read();
  if (done) break;
  const text = decoder.decode(value);
  appendToPage(text);  // Show text as it arrives
}

3.4Token Management and Budget Guards

Runaway generation is a real cost risk in production. max_tokens is your primary control, but you should also track actual usage per request and surface it in your metrics.

The stop_reason field tells you why the model stopped:

  • "end_turn" — Claude finished naturally
  • "max_tokens" — the generation hit the limit you set
  • "stop_sequence" — a custom stop string was matched

A "max_tokens" stop in production is a signal worth logging — it may mean your limit is too tight and responses are getting cut off, or it may mean a user crafted a prompt that tries to generate far more than expected.

const response = await client.messages.create({
  model: "claude-sonnet-4-6-20250514",
  max_tokens: 2048,
  messages
});

if (response.stop_reason === "max_tokens") {
  logger.warn("Response truncated at max_tokens", {
    inputTokens: response.usage.input_tokens,
    outputTokens: response.usage.output_tokens
  });
}

For applications with variable prompt sizes — user-uploaded documents, dynamic context — consider calculating token counts before sending and rejecting requests that exceed a safe threshold. The SDK provides a countTokens method for this:

const tokenCount = await client.messages.countTokens({
  model: "claude-sonnet-4-6-20250514",
  messages
});

if (tokenCount.input_tokens > 50_000) {
  return { error: "Input too large — please reduce document size" };
}

3.6Prompt Caching

Cache your system prompt to avoid paying full input price on every request:

const systemPrompt = `You are an expert code reviewer...
[500 lines of detailed review guidelines]`;

const response = await client.messages.create({
  model: "claude-sonnet-4-6-20250514",
  max_tokens: 2048,
  system: [
    {
      type: "text",
      text: systemPrompt,
      cache_control: { type: "ephemeral" }  // Cache for 5 minutes
    }
  ],
  messages: [{ role: "user", content: userCode }]
});
Break-even math: A 10,000-token system prompt on Sonnet 4.6:
  • First request (cache write): 10,000 × $3.75/MTok = $0.0375
  • Subsequent requests (cache read): 10,000 × $0.30/MTok = $0.003 each
  • Break-even: 5 total requests. After that, every read saves 90%.

If your service handles even moderate traffic with a fixed system prompt, caching pays for itself within minutes of going live.

3.8Cost Tracking

Track usage from the response object:

function calculateCost(response: Anthropic.Messages.Message): number {
  const usage = response.usage;
  const inputCost = usage.input_tokens * (3 / 1_000_000);
  const cacheWriteCost = (usage.cache_creation_input_tokens || 0) * (3.75 / 1_000_000);
  const cacheReadCost = (usage.cache_read_input_tokens || 0) * (0.30 / 1_000_000);
  const outputCost = usage.output_tokens * (15 / 1_000_000);
  return inputCost + cacheWriteCost + cacheReadCost + outputCost;
}

Log cost alongside every request in production so you can track which endpoints, user segments, or input patterns are driving your bill. A sudden spike in cache_creation_input_tokens might indicate your cache TTL is expiring faster than expected. A high ratio of output tokens to input tokens in a tool-use endpoint is a signal that Claude is generating verbose tool results you may be able to trim.

3.10The Batch API: 50% Off for Async Workloads

For non-real-time tasks (nightly processing, bulk analysis, content moderation queues), the Batch API cuts all prices in half:

const batch = await client.beta.messages.batches.create({
  requests: [
    {
      custom_id: "review-1",
      params: {
        model: "claude-sonnet-4-6-20250514",
        max_tokens: 1024,
        messages: [{ role: "user", content: "Review this code..." }]
      }
    }
  ]
});

// Check status
const status = await client.beta.messages.batches.retrieve(batch.id);
if (status.processing_status === "completed") {
  for await (const result of await client.beta.messages.batches.results(batch.id)) {
    console.log(result.custom_id, result.result);
  }
}

Batch API pricing: Haiku $0.50/$2.50 MTok, Sonnet $1.50/$7.50 MTok, Opus $2.50/$12.50 MTok. Batches complete in minutes to hours depending on load.


Module 4Putting It Together

4.1Next Steps and Production Checklist

What to Build Next

You now have the core production patterns for the Anthropic SDK. Here is where to go from here depending on what you are building:

If you are building a user-facing assistant, combine streaming (for UX) with prompt caching (for cost) and the retry wrapper (for reliability). This combination handles the vast majority of production chat use cases. If you are building tool-use automation, the loop with infinite-turn guards plus typed error handling is your foundation. Add per-tool timeouts and consider a dead-letter queue for tool executions that fail after retries. If you are running batch workloads (content generation, document processing, analysis pipelines), the Batch API is almost always the right choice. The 50% discount adds up fast at scale, and the async model fits naturally with queue-based architectures.

4.2Production Readiness Checklist

Before going live with an SDK integration, verify these are in place:

  • API key security: stored in environment variables, never in client-side code or version control
  • Retry logic: exponential backoff on rate limits and server errors, immediate throw on auth/validation errors
  • Turn caps: all tool-use loops have a MAX_TURNS guard
  • Token budgets: max_tokens set on every request, countTokens checks for variable-size inputs
  • Cost logging: every request logs input_tokens, output_tokens, and cache token fields
  • Stop reason monitoring: alert on unexpected "max_tokens" stops in production
  • Graceful degradation: Claude API failures should degrade gracefully (fallback response, user-facing error message) rather than crashing your whole request handler

These are not advanced optimizations — they are the baseline for any production AI service. Getting them in place before you see real traffic is far easier than retrofitting them after your first incident.

The Anthropic SDK documentation at docs.anthropic.com covers additional topics worth exploring once you have the fundamentals working: vision inputs, PDF processing, the Files API for reusing large documents, and the extended thinking feature for complex reasoning tasks. Each builds on the same patterns you have learned here.