← Blog

Claude API Node.js Quickstart Guide

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

Getting a Node.js app talking to Claude takes about ten minutes if you know which pieces matter: authentication, the request shape, and how to handle streaming and errors without writing brittle code. This guide walks through all of it, from an empty project to a working chat endpoint.

If you're searching for "claude api node.js quickstart guide," you probably want one of two things: either you're setting up Anthropic's native SDK for the first time, or you already have Claude access through your plan and want a straightforward HTTPS integration without managing separate API billing. Both paths are covered below, with the second one shown through SubToAPI, which turns your existing Claude access into a standard API key you can drop into any Node.js project.

Project setup

Start with a plain Node.js project. You don't need a framework for this — plain fetch (built into Node 18+) is enough, though node-fetch works identically on older versions.

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

Set your API key as an environment variable rather than hardcoding it:

export SUBTOAPI_KEY="sub_live_xxxxxxxxxxxxxxxx"

If you don't have a key yet, create one from the signup page — there's a free trial, and keys are generated per application from the dashboard so you can rotate them without touching code.

Your first request

Here's a minimal call that sends a message and prints the response:

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

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

Run it with node index.mjs (or add "type": "module" to package.json). The request body follows the standard Messages API shape — model, max_tokens, and a messages array with role/content pairs. Full field details are in the Messages docs.

A few things worth getting right from the start:

Handling errors properly

A quickstart that ignores errors will break the first time you deploy it. Wrap requests like this:

async function askClaude(prompt) {
  const res = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 512,
      messages: [{ role: "user", content: prompt }]
    })
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(`Claude request failed (${res.status}): ${err.error?.message || "unknown error"}`);
  }

  const data = await res.json();
  return data.content[0].text;
}

This gives you a reusable function with a readable error message instead of a crash deep in JSON parsing.

Streaming responses in Node.js

For chat UIs or anything where you want tokens appearing as they're generated, streaming matters. Set stream: true and read the response as server-sent events:

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

for await (const chunk of res.body) {
  const text = Buffer.from(chunk).toString("utf8");
  for (const line of text.split("\n")) {
    if (line.startsWith("data: ")) {
      const payload = line.slice(6);
      if (payload === "[DONE]") continue;
      try {
        const event = JSON.parse(payload);
        if (event.type === "content_block_delta") {
          process.stdout.write(event.delta?.text || "");
        }
      } catch {}
    }
  }
}

Node's for await on res.body works directly against the stream without extra libraries. See streaming docs for the full event sequence, including message_start, content_block_delta, and message_stop.

Adding tool use

Once the basic request/response loop works, tool calling is the next step most people need — letting Claude call functions in your Node.js code (fetching data, running calculations, hitting internal APIs). You define tools with a JSON schema and pass them in the tools array:

const tools = [
  {
    name: "get_weather",
    description: "Get current weather for a city",
    input_schema: {
      type: "object",
      properties: { city: { type: "string" } },
      required: ["city"]
    }
  }
];

const res = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 512,
    tools,
    messages: [{ role: "user", content: "What's the weather in Lisbon?" }]
  })
});

Claude responds with a tool_use block containing the chosen tool and arguments, which your Node.js code executes and feeds back in a follow-up message. The full request/response cycle, including how to return tool_result blocks, is documented at /docs/tools.

Organizing the quickstart into a real project

For anything beyond a script, structure the client as a small module:

// claude.js
export async function createMessage({ model = "claude-sonnet-4-5", messages, maxTokens = 1024, system }) {
  const res = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ model, max_tokens: maxTokens, messages, system })
  });
  if (!res.ok) throw new Error(`Claude API error: ${res.status}`);
  return res.json();
}

Import this from your route handlers, worker jobs, or CLI scripts, and you avoid duplicating headers and error handling across the codebase. The full API reference, including rate limits and usage metadata returned per request, is in the docs.

FAQ

Do I need Anthropic's official SDK to use Claude in Node.js?

No. The Messages API is plain HTTPS/JSON, so fetch or axios work fine. An SDK adds convenience (typed responses, retries) but isn't required for a quickstart.

Why use SubToAPI instead of Anthropic's API directly?

SubToAPI exposes your existing Claude access as a standard sub_live_ API key with streaming, tool use, and per-key usage metadata in one dashboard — useful if you want application-level keys and team seats without managing separate billing. See /pricing for plan details.

How do I keep streaming responses from breaking my Node.js app?

Always parse SSE lines defensively — check for data: prefixes, handle [DONE], and wrap JSON.parse in try/catch, since partial chunks can split mid-JSON across reads.

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 →