← Blog

Claude API Node.js Quickstart: From Zero to First Request

2026-10-13 · 4 min read · SubToAPI Team

If you want to call the Claude API from Node.js, you need three things: an API key, an HTTP client (or SDK), and a correctly formatted request to the messages endpoint. This guide walks through all of it — from installing dependencies to sending your first message and streaming a response — using plain Node.js with no unnecessary abstractions.

By the end you'll have a working script that sends a prompt to Claude and prints the reply, plus a streaming version for real-time output. We'll also cover the two realistic paths for getting access: Anthropic's native API, and a wrapped API like SubToAPI if you already have Claude Pro/Max access and want an HTTPS key without a separate Anthropic billing account.

Prerequisites

Check your Node version:

node -v

If you're below v18, either upgrade or install node-fetch as a polyfill.

Step 1: Set Up Your Project

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

Store your key as an environment variable rather than hardcoding it:

export ANTHROPIC_API_KEY="your-key-here"
# or, if using SubToAPI
export SUBTOAPI_KEY="sub_live_..."

Step 2: Send Your First Request

You don't strictly need an SDK — Node's built-in fetch handles this fine. Create index.js:

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-3-5-sonnet-latest",
    max_tokens: 1024,
    messages: [
      { role: "user", content: "Explain what a quickstart guide is in one sentence." },
    ],
  }),
});

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

Run it:

node index.js

If everything is configured correctly, you'll see a short reply printed to your terminal. This same request shape works whether you're hitting Anthropic's endpoint directly or an API-key-based wrapper like SubToAPI — the body format is intentionally compatible. See the full request/response schema in the messages docs.

Step 3: Handle Multi-Turn Conversations

The API is stateless — you send the full conversation history with every request. Track messages in an array and append to it after each turn:

const messages = [{ role: "user", content: "What's the capital of Portugal?" }];

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-3-5-sonnet-latest",
      max_tokens: 1024,
      messages,
    }),
  });
  const data = await res.json();
  return data.content[0].text;
}

const reply = await ask(messages);
console.log(reply);

messages.push({ role: "assistant", content: reply });
messages.push({ role: "user", content: "And what language is spoken there?" });

console.log(await ask(messages));

This pattern is the foundation of any chat interface, CLI tool, or backend service built on Claude.

Step 4: Stream the Response

For anything user-facing, streaming matters — users see tokens as they're generated instead of waiting for the full response. Add "stream": true to the request body and read the response 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-3-5-sonnet-latest",
    max_tokens: 1024,
    stream: true,
    messages: [{ role: "user", content: "Write a haiku about debugging." }],
  }),
});

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

More event types and edge cases are covered in the streaming docs.

Step 5: Add Error Handling

Production code needs to handle rate limits, timeouts, and malformed requests gracefully:

async function callClaude(messages, retries = 3) {
  for (let attempt = 0; attempt < retries; attempt++) {
    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-3-5-sonnet-latest",
        max_tokens: 1024,
        messages,
      }),
    });

    if (res.status === 429) {
      const wait = Math.pow(2, attempt) * 1000;
      await new Promise((r) => setTimeout(r, wait));
      continue;
    }

    if (!res.ok) {
      const err = await res.json();
      throw new Error(`Claude API error: ${err.error?.message || res.statusText}`);
    }

    return res.json();
  }
  throw new Error("Max retries exceeded");
}

Why Some Teams Use SubToAPI Instead of Direct Anthropic Billing

If you already pay for Claude Pro or Max and don't want to set up separate Anthropic API billing, SubToAPI turns that subscription access into a standard HTTPS API: application keys (sub_live_...), streaming, tool use, usage metadata per key, and team seats in one dashboard. The request/response format mirrors what's shown above, so migrating existing code is usually a find-and-replace on the base URL and auth header. Plans start at €9/month for Solo, with Team (€19/seat) and Scale (€49/seat) tiers for shared usage and higher limits. There's a free trial at signup and full pricing details on the pricing page.

For a broader walkthrough of available endpoints, start with the quickstart docs.

Questions

Do I need the official Anthropic SDK to use Claude from Node.js? No. Node 18+ has built-in fetch, which is enough to call the messages endpoint directly with plain JSON requests, as shown above.

What's the difference between streaming and non-streaming requests? Non-streaming waits for the full response before returning it; streaming sends tokens incrementally as server-sent events, which is better for chat UIs and reduces perceived latency.

Can I use the same code against SubToAPI and Anthropic's native API? Yes — the request and response shapes are compatible. You typically only need to change the base URL and the Authorization header value to switch providers.

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 →