Claude API with Node.js — Developer Guide

Integrating the claude api nodejs stack comes down to one package: @anthropic-ai/sdk, Anthropic’s official TypeScript SDK. It ships full type definitions, automatic retries, and streaming helpers, and it works identically in plain JavaScript. This guide takes you from npm install to a production-ready integration — messages, streaming into an Express route, tool calling, guaranteed JSON output, and error handling — using the current 2026 API shapes throughout.

TL;DR: Install @anthropic-ai/sdk, set the ANTHROPIC_API_KEY environment variable, and call client.messages.create(). Stream with client.messages.stream() and for await. Check errors with instanceof Anthropic.RateLimitError. Current models reject temperature — use prompts and output_config.effort. SDK timeouts are in milliseconds, not seconds.

Install and Authenticate the SDK

The SDK supports Node.js 18+ and installs from npm:

npm install @anthropic-ai/sdk

Grab a key from the Claude Console (the API key walkthrough covers account setup and billing), put it in ANTHROPIC_API_KEY, and construct the client:

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

const client = new Anthropic(); // reads ANTHROPIC_API_KEY automatically

In a plain-JavaScript CommonJS project, use const Anthropic = require("@anthropic-ai/sdk").default; — everything else is identical. Never expose the key to browser code; API calls belong on your server.

Making Your First Claude API Node.js Request

All generation goes through client.messages.create(), which maps 1:1 to the POST /v1/messages endpoint:

const message = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  system: "You are a concise technical assistant.",
  messages: [
    { role: "user", content: "Explain the Node.js event loop in two sentences." },
  ],
});

const block = message.content[0];
if (block.type === "text") console.log(block.text);
console.log(message.usage); // { input_tokens, output_tokens, ... }

TypeScript narrows content blocks by their type field, which is why the block.type === "text" check matters — the array can also contain tool_use or thinking blocks. The SDK exports types for everything (Anthropic.Message, Anthropic.MessageParam, Anthropic.Tool), so don’t define your own message interfaces.

Model IDs are the same across every SDK: claude-fable-5 ($10/$50 per MTok), claude-opus-4-8 ($5/$25), claude-sonnet-5 ($3/$15, intro $2/$10 through August 31, 2026), and claude-haiku-4-5 ($1/$5). If you’re unsure which tier fits your workload, the Claude models comparison breaks down the trade-offs. And as with Python: temperature, top_p, and top_k are removed on current models — sending them returns a 400. Use thinking: { type: "adaptive" } for reasoning and output_config: { effort: "high" } to trade depth against latency.

Streaming Responses (and an Express SSE Route)

The streaming helper returns an object that is both an async iterable and an event emitter:

const stream = client.messages.stream({
  model: "claude-sonnet-5",
  max_tokens: 2048,
  messages: [{ role: "user", content: "Write a limerick about npm." }],
});

stream.on("text", (delta) => process.stdout.write(delta));

const finalMessage = await stream.finalMessage(); // assembled Message + usage

In a real app you usually pipe those deltas straight to the browser. Here’s the Express server-sent-events pattern I use:

app.post("/chat", async (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");

  const stream = client.messages.stream({
    model: "claude-sonnet-5",
    max_tokens: 4096,
    messages: [{ role: "user", content: req.body.prompt }],
  });

  for await (const event of stream) {
    if (
      event.type === "content_block_delta" &&
      event.delta.type === "text_delta"
    ) {
      res.write(`data: ${JSON.stringify(event.delta.text)}\n\n`);
    }
  }
  res.write("data: [DONE]\n\n");
  res.end();
});

Stream anything long: above roughly 16K max_tokens, non-streaming requests risk HTTP timeouts, and current models can emit up to 128K output tokens when streamed. One SDK-specific trap — the timeout client option is in milliseconds (new Anthropic({ timeout: 60_000 })), unlike the Python SDK’s seconds.

Tool Use in TypeScript

Tools turn Claude into an orchestrator for your own functions. Define them with JSON Schema, then loop while stop_reason is tool_use:

const tools: Anthropic.Tool[] = [
  {
    name: "get_stock_price",
    description:
      "Get the latest price for a ticker symbol. Call this whenever the user asks about a stock price.",
    input_schema: {
      type: "object",
      properties: {
        ticker: { type: "string", description: "e.g. AAPL" },
      },
      required: ["ticker"],
    },
  },
];

const messages: Anthropic.MessageParam[] = [
  { role: "user", content: "What is AAPL trading at?" },
];

let response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1024,
  tools,
  messages,
});

while (response.stop_reason === "tool_use") {
  const toolResults: Anthropic.ToolResultBlockParam[] = [];

  for (const block of response.content) {
    if (block.type === "tool_use") {
      const input = block.input as { ticker: string };
      const price = await fetchPrice(input.ticker); // your implementation
      toolResults.push({
        type: "tool_result",
        tool_use_id: block.id,
        content: JSON.stringify(price),
      });
    }
  }

  messages.push({ role: "assistant", content: response.content });
  messages.push({ role: "user", content: toolResults });

  response = await client.messages.create({
    model: "claude-opus-4-8",
    max_tokens: 1024,
    tools,
    messages,
  });
}

The rules are the same as everywhere: push the whole response.content back as the assistant turn, and if Claude requested multiple tools in parallel, return every tool_result in one user message. The SDK also offers a beta tool runner (client.beta.messages.toolRunner with Zod-based betaZodTool helpers) that automates this loop; I hand-write it in examples because it makes the mechanics visible. Developers building coding agents on top of this pattern should read how it compares to off-the-shelf options in Claude Code vs Cursor.

Structured Outputs for Type-Safe JSON

Instead of prompting for JSON and hoping, constrain the response with a schema via output_config:

const extraction = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "Extract: 'Order #4411, 3x USB-C cables, ships Tuesday'" },
  ],
  output_config: {
    format: {
      type: "json_schema",
      schema: {
        type: "object",
        properties: {
          order_id: { type: "string" },
          items: { type: "array", items: { type: "string" } },
          ship_day: { type: "string" },
        },
        required: ["order_id", "items", "ship_day"],
        additionalProperties: false,
      },
    },
  },
});

const first = extraction.content[0];
const data = first.type === "text" ? JSON.parse(first.text) : null;

The output is guaranteed to parse and match the schema. Remember additionalProperties: false is mandatory on every object, and numeric range constraints like minimum aren’t supported — the SDK validates those client-side if you use its Zod helpers.

Error Handling, Retries, and Timeouts

Every non-2xx response maps to a typed error class on the Anthropic namespace. Branch with instanceof, most specific first:

try {
  const msg = await client.messages.create({
    model: "claude-sonnet-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello" }],
  });
} catch (err) {
  if (err instanceof Anthropic.RateLimitError) {
    // 429 — the SDK already retried twice; back off and re-queue
  } else if (err instanceof Anthropic.AuthenticationError) {
    // 401 — bad key
  } else if (err instanceof Anthropic.BadRequestError) {
    // 400 — malformed request (e.g. temperature on a current model)
  } else if (err instanceof Anthropic.APIConnectionError) {
    // network failure — check before APIError: it is a subclass
  } else if (err instanceof Anthropic.APIError) {
    console.error(err.status, err.name, err.message);
  } else {
    throw err;
  }
}

Retries for 429s, 5xx errors, and dropped connections are built in (two attempts with exponential backoff — configure via maxRetries). Rate-limited responses include a retry-after header the SDK honors automatically; if you’re designing for scale, the rate limits guide explains how the per-model quotas and usage tiers actually work.

Common Mistakes in Node.js Integrations

  • Setting timeout in seconds. The TypeScript SDK takes milliseconds. timeout: 60 gives you a 60ms timeout and instant failures.
  • Calling the API from the browser. Your key leaks in the first network tab screenshot. Proxy through your server.
  • Copying pre-2026 snippets with temperature. Current models return a 400. Remove sampling parameters entirely.
  • Assuming content[0] is text. Narrow on block.type — thinking and tool-use blocks can come first.
  • Rebuilding SDK types by hand. Use Anthropic.MessageParam, Anthropic.Tool, and friends instead of custom interfaces that drift.
  • Forgetting to await stream.finalMessage(). If you need usage data for billing, grab the final message — the deltas alone don’t carry it.

FAQ

How do I install the Claude API SDK for Node.js?

Run npm install @anthropic-ai/sdk. It requires Node.js 18 or newer, ships TypeScript definitions out of the box, and works in plain JavaScript too. Set the ANTHROPIC_API_KEY environment variable and the client constructor picks it up with no arguments.

Does the Claude API work with plain JavaScript, not TypeScript?

Yes. The @anthropic-ai/sdk package is plain JavaScript with bundled type declarations, so it runs in any Node project. TypeScript just adds compile-time safety like narrowing content blocks by type. In CommonJS, import it with require(‘@anthropic-ai/sdk’).default.

Can I call the Claude API directly from a browser app?

You shouldn’t. Any key shipped to the browser is publicly visible and will be abused. Keep API calls on a server route (Express, Next.js API routes, serverless functions) and stream results to the client over server-sent events or WebSockets.

How do I stream Claude responses in Node.js?

Use client.messages.stream(), which returns an object that is both an async iterable and an event emitter. Listen with stream.on(‘text’, callback) or iterate events with for await, then call await stream.finalMessage() to get the assembled response with token usage.

Does the Node SDK retry failed requests automatically?

Yes. Rate limit errors (429), server errors (5xx), and connection failures are retried twice by default with exponential backoff, honoring the retry-after header. You can adjust this with the maxRetries option on the client constructor or per request.

Next Steps

That’s a complete Node.js integration: install, authenticate, generate, stream, call tools, and fail gracefully. Explore the rest of the Claude API section for pricing and quota guides, work through the first-integration tutorial if you want a project-shaped walkthrough, or compare notes with the Python version of this guide if your team runs both stacks.

ClaudeAI.Guide Editorial Team

ClaudeAI.Guide Editorial Team

Independent editorial team behind ClaudeAI.Guide — an unofficial, third-party reference that is not affiliated with, endorsed by, or sponsored by Anthropic, PBC. We cover Anthropic’s Claude AI assistant from a practitioner’s perspective: hands-on tutorials, practical prompts, model comparisons (Claude Opus, Sonnet, Haiku), API walkthroughs, and honest reviews grounded in our own daily use. Everything we publish is tested in real workflows and verified at the time of writing, with no affiliate-driven hype. “Claude” and “Anthropic” are trademarks of Anthropic, PBC, used here for descriptive, nominative fair-use purposes only.

Articles: 72