← Blog

Claude API Node.js Integration Example

2026-10-04 · 5 min read · SubToAPI Team

If you're searching for a Claude API Node.js integration example, you probably want three things: a minimal working request, a streaming version for chat UIs, and a sense of how to handle errors and production concerns like retries and usage tracking. This article covers all three with copy-pasteable code.

The short version: you install an SDK (or just use fetch), send a POST request with your API key in the headers, pass a model, messages, and max_tokens, and parse the JSON response. Everything else — streaming, tool use, multi-turn conversations — builds on that same pattern. Below we walk through each piece.

Prerequisites

You'll need:

Both approaches work. The SDK gives you typed responses and built-in retry logic; raw fetch gives you fewer dependencies and more control. The examples below use plain HTTP requests so they translate directly to any backend compatible with the Claude Messages format.

Basic Request Example

Here's the minimal integration — a single prompt, a single response:

const response = await fetch("https://api.anthropic.com/v1/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": process.env.ANTHROPIC_API_KEY,
    "anthropic-version": "2023-06-01",
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Explain event loops in Node.js in two sentences." }],
  }),
});

const data = await response.json();
console.log(data.content[0].text);

If you're building this behind an internal gateway or want a single key your whole team can use with seats and usage dashboards instead of managing raw Anthropic credentials, the same request works against SubToAPI's endpoint with no structural changes:

const response = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Explain event loops in Node.js in two sentences." }],
  }),
});

The only differences are the base URL and the auth header. See /docs/messages for the full request schema if you go that route.

Multi-Turn Conversations

Claude's API is stateless — you resend the full conversation history on every call. A typical Node.js chat handler looks like this:

function appendMessage(history, role, content) {
  return [...history, { role, content }];
}

let history = [];
history = appendMessage(history, "user", "What's a closure in JavaScript?");

const res = await callClaude(history);
history = appendMessage(history, "assistant", res.content[0].text);

history = appendMessage(history, "user", "Give me a short example.");
const res2 = await callClaude(history);

Wrap the fetch call in a reusable callClaude(messages) function and this pattern scales to any conversational app, from support bots to internal tools.

Streaming Responses in Node.js

For chat interfaces, you don't want to wait for the full response — you want tokens as they're generated. Set stream: true and read the response body as a stream of server-sent events:

const response = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    stream: true,
    messages: [{ role: "user", content: "Write a haiku about async/await." }],
  }),
});

for await (const chunk of response.body) {
  const text = new TextDecoder().decode(chunk);
  for (const line of text.split("\n")) {
    if (line.startsWith("data:")) {
      const payload = line.replace("data: ", "").trim();
      if (payload === "[DONE]") continue;
      try {
        const event = JSON.parse(payload);
        if (event.delta?.text) process.stdout.write(event.delta.text);
      } catch {}
    }
  }
}

This is the same event-parsing logic you'd use with Express, Fastify, or a serverless function — just pipe the parsed chunks to your client via WebSocket or server-sent events instead of stdout. Full event shapes are documented at /docs/streaming.

Adding Tool Use

If your Node.js app needs Claude to call functions — looking up a database record, hitting an internal API, running a calculation — define tools in the request and handle the tool_use block in the response:

const tools = [
  {
    name: "get_order_status",
    description: "Look up the status of an order by ID",
    input_schema: {
      type: "object",
      properties: { order_id: { type: "string" } },
      required: ["order_id"],
    },
  },
];

const res = await callClaude(history, tools);
const toolCall = res.content.find((b) => b.type === "tool_use");

if (toolCall) {
  const result = await lookupOrder(toolCall.input.order_id);
  history = appendMessage(history, "assistant", res.content);
  history = appendMessage(history, "user", [
    { type: "tool_result", tool_use_id: toolCall.id, content: JSON.stringify(result) },
  ]);
  const finalRes = await callClaude(history, tools);
}

This loop — call, check for tool_use, execute locally, feed the result back — is the core of every agentic Node.js integration. See /docs/tools for schema details and multi-tool examples.

Error Handling and Retries

Production integrations need to handle rate limits (429), overloaded errors (529), and transient network failures. A simple exponential backoff wrapper covers most cases:

async function callWithRetry(fn, attempts = 4) {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === attempts - 1) throw err;
      await new Promise((r) => setTimeout(r, 2 ** i * 500));
    }
  }
}

Wrap your fetch call in callWithRetry and you've covered the most common failure mode without pulling in a full HTTP client library.

Why a Gateway Instead of Direct Keys

Calling the Claude API directly from Node.js works fine for a solo project. It gets harder once you have multiple developers, multiple environments, and a need to know which feature or teammate is burning through usage. SubToAPI sits in front of your existing Claude access and gives every app or environment its own sub_live_... key, with streaming, tool use, and usage metadata available per key from one dashboard. You swap the base URL and header in the examples above — the request and response shapes stay identical. Start with a free trial or check the Quickstart to see the full setup in under five minutes. Plans start at €9/month for solo use and scale to team seats at /pricing.

Questions

Do I need the official SDK, or can I use plain fetch? Either works. fetch requires zero dependencies and is shown throughout this guide; the @anthropic-ai/sdk package adds typed responses and built-in retries if you prefer less boilerplate.

How do I keep conversation history across requests in a stateless API? Resend the full messages array on every call, appending the user's new message and the assistant's prior reply each turn — the API itself does not store state.

What's the fastest way to add per-developer API keys to a Node.js project? Use a gateway like SubToAPI to issue separate sub_live_... keys per app or teammate from one dashboard instead of sharing a single raw Anthropic key across your codebase.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →