Claude API TypeScript SDK: A Practical Usage Guide
If you're building with Claude in a Node.js or TypeScript backend, the official @anthropic-ai/sdk package is the fastest way to get typed, autocompleted access to the Messages API without hand-rolling HTTP requests. This guide walks through installation, basic message calls, streaming, tool use, and common patterns you'll actually need in production.
The short version: install the SDK, instantiate a client with your API key, call client.messages.create() with a model, max_tokens, and a messages array, and you get back a typed response object. Everything below expands on that with real code.
Installing the SDK
npm install @anthropic-ai/sdk
The package ships its own TypeScript types, so there's no separate @types package to install. It works in Node.js server environments out of the box; if you want to call it directly from a browser, you need to pass a flag explicitly acknowledging the security implications, because API keys should never live in client-side code.
Basic client setup
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
async function main() {
const message = await client.messages.create({
model: "claude-3-5-sonnet-latest",
max_tokens: 1024,
messages: [
{ role: "user", content: "Summarize the plot of Dune in two sentences." },
],
});
console.log(message.content);
}
main();
The response object is fully typed. message.content is an array of content blocks (usually a single text block for simple prompts), message.usage gives you input_tokens and output_tokens, and message.stop_reason tells you why generation ended (end_turn, max_tokens, tool_use, etc.). TypeScript will autocomplete all of these fields, which catches a lot of bugs before they hit production.
System prompts and conversation history
The SDK treats system instructions as a separate top-level field, not a message role:
const message = await client.messages.create({
model: "claude-3-5-sonnet-latest",
max_tokens: 1024,
system: "You are a terse technical writer. No filler words.",
messages: [
{ role: "user", content: "Explain what a CDN does." },
],
});
For multi-turn conversations, you append both user and assistant turns to the messages array yourself — the SDK is stateless between calls, so your application is responsible for maintaining history (or truncating it as the conversation grows).
Streaming responses
For chat UIs or anything where perceived latency matters, streaming is essential. The SDK exposes a .stream() helper that returns an async iterator over events:
const stream = client.messages.stream({
model: "claude-3-5-sonnet-latest",
max_tokens: 1024,
messages: [{ role: "user", content: "Write a haiku about databases." }],
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
const finalMessage = await stream.finalMessage();
console.log(finalMessage.usage);
stream.finalMessage() resolves once the stream completes and gives you the fully assembled response, including usage metadata — useful when you want to show text incrementally but still log token counts afterward.
Tool use (function calling)
Tool use lets Claude call functions you define instead of just generating text. You describe tools with a JSON Schema, and Claude decides when to invoke them:
const message = await client.messages.create({
model: "claude-3-5-sonnet-latest",
max_tokens: 1024,
tools: [
{
name: "get_weather",
description: "Get current weather for a city",
input_schema: {
type: "object",
properties: {
city: { type: "string" },
},
required: ["city"],
},
},
],
messages: [{ role: "user", content: "What's the weather in Lisbon?" }],
});
const toolUse = message.content.find((block) => block.type === "tool_use");
if (toolUse) {
console.log(toolUse.name, toolUse.input);
}
If stop_reason is tool_use, you run the actual function, then send the result back as a tool_result content block in a follow-up messages.create() call so Claude can finish reasoning with the real data.
Error handling
The SDK throws typed errors you can catch and branch on:
try {
await client.messages.create({ /* ... */ });
} catch (err) {
if (err instanceof Anthropic.APIError) {
console.error(err.status, err.message);
}
throw err;
}
Rate limit errors (429) and overload errors (529) are common enough that production code should implement retry with backoff — the SDK does some retries automatically, but you'll still want your own handling for sustained load.
Where SubToAPI fits in
The official SDK talks directly to Anthropic and expects an Anthropic API key with its own billing. If you already pay for Claude through a subscription and want to reuse that access from your TypeScript backend without managing separate API billing, SubToAPI exposes it as a standard HTTPS endpoint. You generate an application key (sub_live_...) in the dashboard and point the same-shaped requests at https://api.subtoapi.app/v1/messages instead — streaming, tool use, and usage metadata all work the same way. The quickstart shows the minimal setup, and the messages and streaming docs cover request/response shapes in detail if you're porting existing SDK code.
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-3-5-sonnet-latest",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
}),
});
Team plans add per-member keys with shared usage visibility, which is useful once more than one developer on a project needs access — see pricing for the Solo, Team, and Scale tiers, or start a free trial at signup.
questions
Do I need the official SDK to use TypeScript with Claude? No. The SDK is a convenience wrapper with types and helpers like .stream(), but any HTTP client works fine since the underlying API is plain JSON over HTTPS — useful if you're calling a compatible endpoint like SubToAPI's.
How do I keep conversation context across multiple SDK calls? The SDK doesn't persist state. Store the growing messages array yourself (in memory, a database, or session storage) and pass the full history on each messages.create() call.
Can I use the SDK's tool use feature with more than one tool? Yes — pass an array of tool definitions in the tools field. Claude picks which one to call based on the conversation, and you can define multiple tool schemas in a single request. See tool use docs for more detail on the request/response format.