← Blog

Claude API TypeScript Client Library Example

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

If you're looking for a working Claude API TypeScript client library example, here's the short answer: install @anthropic-ai/sdk, instantiate a client with your API key, call messages.create() with a typed request object, and handle the response or stream. Below is a complete, annotated example you can copy into a project today, along with patterns for streaming, error handling, and tool use.

This article covers the official Anthropic SDK for TypeScript/Node, how to type your requests and responses correctly, and how the same patterns map to SubToAPI if you're routing requests through a team-managed API key instead of a personal Anthropic key.

Setting Up the Client

Install the SDK and set your API key as an environment variable:

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

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});

TypeScript users get full type inference out of the box — anthropic.messages.create() returns a typed Message object, and your request parameters are validated against the SDK's exported types (Anthropic.MessageCreateParams, Anthropic.MessageParam, etc.).

A Minimal Typed Request

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

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

async function askClaude(prompt: string): Promise<string> {
  const message: Anthropic.Message = await anthropic.messages.create({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: prompt }],
  });

  const textBlock = message.content.find((block) => block.type === "text");
  return textBlock?.type === "text" ? textBlock.text : "";
}

askClaude("Explain the difference between `interface` and `type` in TypeScript.")
  .then(console.log)
  .catch(console.error);

Note the type narrowing on message.content: Claude's response content is an array of typed blocks (text, tool_use, etc.), so you need to check block.type before accessing .text. This is one of the most common TypeScript compile errors people hit when first wiring up the SDK.

Typing Multi-Turn Conversations

For a conversation history, use the Anthropic.MessageParam[] type to keep your array well-typed across turns:

const history: Anthropic.MessageParam[] = [
  { role: "user", content: "What's a closure in JavaScript?" },
];

const first = await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 500,
  messages: history,
});

history.push({ role: "assistant", content: first.content });
history.push({ role: "user", content: "Give me a practical example." });

const second = await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 500,
  messages: history,
});

Pushing first.content directly works because the SDK's ContentBlock[] type is assignable to the content field expected in a MessageParam — no manual reshaping required.

Streaming Responses

For chat UIs or long completions, stream tokens as they arrive instead of waiting for the full response:

const stream = anthropic.messages.stream({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Write a haiku about recursion." }],
});

stream.on("text", (textDelta: string) => {
  process.stdout.write(textDelta);
});

const finalMessage = await stream.finalMessage();
console.log("\n--- usage ---", finalMessage.usage);

The .stream() helper returns an event emitter with typed events (text, message, error), so you get autocomplete on event names and payload shapes without casting.

Tool Use with Typed Schemas

Defining tools in TypeScript benefits from strict typing on the JSON schema shape:

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

const response = await anthropic.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  tools,
  messages: [{ role: "user", content: "What's the weather in Lisbon?" }],
});

const toolCall = response.content.find(
  (block): block is Anthropic.ToolUseBlock => block.type === "tool_use"
);

if (toolCall) {
  console.log(toolCall.name, toolCall.input);
}

Using a type predicate (block is Anthropic.ToolUseBlock) in the .find() callback gives you a properly narrowed object afterward, so toolCall.input is typed instead of unknown.

Error Handling

The SDK throws typed error classes you can catch specifically:

import Anthropic, { APIError, RateLimitError } from "@anthropic-ai/sdk";

try {
  await anthropic.messages.create({ /* ... */ });
} catch (err) {
  if (err instanceof RateLimitError) {
    console.error("Rate limited, retry after backoff");
  } else if (err instanceof APIError) {
    console.error(`API error ${err.status}: ${err.message}`);
  } else {
    throw err;
  }
}

Using SubToAPI Instead of a Raw Anthropic Key

If your team shares Claude access through a managed subscription rather than individual Anthropic API keys, SubToAPI exposes an HTTPS endpoint that mirrors the same request shape, so your TypeScript code barely changes — you swap the base URL and key:

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: "Summarize this PR description." }],
  }),
});

const data = await response.json();

This is useful when you want application-scoped keys (sub_live_...), per-key usage tracking, and team seat management without every developer holding their own Anthropic credentials. Streaming and tool use work the same way as the direct SDK calls shown above — see /docs/streaming and /docs/tools for request formats, or /docs/quickstart to get a key from /signup. Plans are listed at /pricing.

Project Structure Tips

For anything beyond a script, wrap the client in a small module:

// claude-client.ts
import Anthropic from "@anthropic-ai/sdk";

export const claude = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
  maxRetries: 2,
  timeout: 30_000,
});

This keeps configuration (retries, timeouts, base URL overrides) in one place and makes it trivial to swap providers or endpoints later without touching call sites.

questions

Does Anthropic have an official TypeScript SDK? Yes, @anthropic-ai/sdk on npm is maintained by Anthropic and includes full TypeScript type definitions for requests, responses, streaming events, and errors.

Why does message.content need type narrowing? Claude responses return an array of content blocks that can be text, tool calls, or other types. TypeScript needs you to check block.type before accessing type-specific fields like .text or .input.

Can I use the same TypeScript code with SubToAPI? Yes — SubToAPI's /v1/messages endpoint follows the same request and response structure, so most of your typed logic only needs a base URL and key change. See /docs/messages.

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 →