← Blog

Claude API Node.js Quickstart Tutorial

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

What You'll Build

This tutorial gets you from zero to a working Claude integration in Node.js: installing dependencies, authenticating, sending your first message, handling the response, and streaming output to the console. By the end you'll have a small script you can extend into a chatbot, CLI tool, or backend endpoint.

You need Node.js 18+ (for native fetch), an API key, and about 10 minutes. We'll use plain fetch calls so the code works whether you're calling Anthropic's API directly or a compatible proxy like SubToAPI — the request/response shape is the same either way.

Step 1: Project Setup

Create a new project and initialize it:

mkdir claude-node-quickstart
cd claude-node-quickstart
npm init -y

Set "type": "module" in package.json so you can use ES module import syntax, or just use require if you prefer CommonJS — both work fine for the examples below.

Create a .env file (don't commit it) to hold your key:

SUBTOAPI_KEY=sub_live_xxxxxxxxxxxxxxxx

Install dotenv to load it:

npm install dotenv

Step 2: Your First Request

Create index.js:

import "dotenv/config";

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 what a quickstart tutorial should cover." }
    ]
  })
});

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

Run it:

node index.js

If everything is wired correctly, you'll see Claude's reply printed to your terminal. That's the whole loop: build a request body with a model name, a token limit, and a messages array, send it with your bearer token, and read the text back out of content[0].text.

No SDK install is required for this — raw fetch is enough for most Node.js projects, which keeps your dependency tree small and avoids version-mismatch issues between an SDK and the underlying API.

Step 3: Handling Multi-Turn Conversations

Claude's API is stateless — you resend the full conversation history with every request. In Node.js, the natural pattern is to keep an array of messages and push to it:

const history = [
  { role: "user", content: "What's a good name for a note-taking app?" }
];

async function ask(messages) {
  const res = 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: 512,
      messages
    })
  });
  const data = await res.json();
  return data.content[0].text;
}

const reply = await ask(history);
history.push({ role: "assistant", content: reply });
history.push({ role: "user", content: "Make it one word." });

const reply2 = await ask(history);
console.log(reply2);

This is the core pattern behind any chatbot or assistant built on Claude: accumulate turns, resend the array, append the new assistant reply before the next user message.

Step 4: Streaming Responses

For anything user-facing, you don't want to wait for the full response before showing text. Add "stream": true and read the response body as a stream of server-sent events:

const res = 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 short haiku about Node.js." }]
  })
});

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

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = decoder.decode(value);
  for (const line of chunk.split("\n")) {
    if (line.startsWith("data: ")) {
      try {
        const event = JSON.parse(line.slice(6));
        if (event.delta?.text) process.stdout.write(event.delta.text);
      } catch {
        // ignore non-JSON keepalive lines
      }
    }
  }
}

This prints tokens as they arrive, which is what gives chat interfaces that "typing" feel. Full details on event types and delta shapes are in the streaming docs.

Step 5: Error Handling and Retries

Production code should handle rate limits and transient failures. A minimal retry wrapper:

async function sendWithRetry(body, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch("https://api.subtoapi.app/v1/messages", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`
      },
      body: JSON.stringify(body)
    });
    if (res.ok) return res.json();
    if (res.status === 429 && i < attempts - 1) {
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
      continue;
    }
    throw new Error(`Request failed: ${res.status} ${await res.text()}`);
  }
}

Check response status codes explicitly rather than assuming success — this catches malformed requests early during development.

Why Route Through SubToAPI

If you're building this inside a team, SubToAPI turns a Claude subscription into a standard HTTPS API with application-scoped keys (sub_live_...), per-key usage metadata, and seat-based billing — useful once more than one developer or service needs access. The request format above is identical to what you'd use directly; you're just pointing at a different base URL and getting a dashboard for keys, usage, and streaming on top. Start with the quickstart guide or see full request/response details in the messages docs. Plans start at €9/month with a free trial — see pricing or sign up to get a key.

Next Steps

From here, the natural extensions are: adding tool/function calling so Claude can call your own functions (see the tools docs), persisting conversation history to a database instead of an in-memory array, and wrapping the fetch calls in an Express or Fastify route so your frontend talks to your backend instead of directly to the API.

Questions

Do I need the official Anthropic SDK to use Node.js with Claude? No. Node 18+ ships native fetch, which is enough to call the API directly with JSON requests. An SDK adds convenience methods but isn't required for a quickstart.

Why isn't my streaming response showing up incrementally? Make sure you're reading res.body as a stream with getReader() rather than calling res.json(), and that "stream": true is set in the request body — otherwise the API returns one complete JSON response.

Can I use this same code with a different Claude API provider? Yes, as long as the provider implements the same messages/streaming request shape — you typically only need to change the base URL and the bearer token used in the Authorization header.

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 →