Claude API TypeScript SDK Example: A Working Guide
If you're searching for a Claude API TypeScript SDK example, you probably want two things: a working code snippet you can paste into a project right now, and enough context to adapt it to your own use case (chat, streaming, tool calls). This article gives you both, using the official @anthropic-ai/sdk package with full TypeScript types.
We'll cover installation, a basic typed request, streaming responses, tool use, and error handling patterns that actually matter in production — not just toy examples that break the moment you add real user input.
Setting Up the SDK
Install the official Anthropic SDK, which ships with TypeScript definitions out of the box:
npm install @anthropic-ai/sdk
Set your API key as an environment variable rather than hardcoding it:
export ANTHROPIC_API_KEY="sk-ant-..."
The SDK picks up ANTHROPIC_API_KEY automatically, so you don't need to pass it explicitly unless you're managing multiple keys or routing through a proxy.
Basic Typed Request
Here's a minimal, fully typed example using the Messages API:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function askClaude(prompt: string): Promise<string> {
const message = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
const textBlock = message.content.find(
(block): block is Anthropic.TextBlock => block.type === "text"
);
return textBlock?.text ?? "";
}
askClaude("Explain event loops in Node.js in two sentences.").then(console.log);
Two things worth noting for TypeScript users specifically:
message.contentis an array of content blocks (TextBlock,ToolUseBlock, etc.), so you need a type guard or aswitchonblock.typeto extract text safely.max_tokensis required — there's no default, and omitting it throws a compile-time error with the SDK's types.
Streaming Responses
For chat UIs or long completions, streaming avoids making users stare at a blank screen. The SDK exposes an async iterator for this:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
async function streamResponse(prompt: string) {
const stream = client.messages.stream({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
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("\n\nUsage:", finalMessage.usage);
}
streamResponse("Write a haiku about TypeScript generics.");
The stream.finalMessage() call resolves to the complete Message object once streaming finishes, which is useful when you need the final usage stats or full content array after rendering the deltas.
Tool Use with Typed Definitions
Tool use (function calling) benefits a lot from TypeScript, since you can define strict input schemas and get compile-time safety on your tool handlers:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const tools: Anthropic.Tool[] = [
{
name: "get_weather",
description: "Get current weather for a city",
input_schema: {
type: "object",
properties: {
city: { type: "string", description: "City name" },
},
required: ["city"],
},
},
];
async function runWithTools(prompt: string) {
const message = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
tools,
messages: [{ role: "user", content: prompt }],
});
for (const block of message.content) {
if (block.type === "tool_use") {
console.log(`Claude wants to call ${block.name} with`, block.input);
// Run your actual function here, then send a tool_result back
}
}
}
runWithTools("What's the weather in Lisbon?");
Note that block.input is typed as unknown by default since it depends on your schema — you'll typically want a runtime validator (Zod, for example) to safely cast it before calling your handler.
Error Handling
The SDK throws typed errors you can catch specifically:
import Anthropic from "@anthropic-ai/sdk";
try {
await client.messages.create({ /* ... */ });
} catch (error) {
if (error instanceof Anthropic.APIError) {
console.error(`Status ${error.status}: ${error.message}`);
} else {
throw error;
}
}
This matters more than it sounds — rate limits (429s) and overload errors (529s) need different retry logic than a malformed request (400), and the typed error classes make that branching straightforward.
When You Need More Than Raw API Calls
The examples above work well for a single app talking directly to Anthropic. Once you're shipping multiple services, giving teammates separate credentials, or need per-key usage visibility without building that tooling yourself, a layer in front of the raw API starts paying off.
SubToAPI turns your existing Claude access into an HTTPS API with its own application keys (sub_live_...), so the TypeScript code above barely changes — you just point the base URL at https://api.subtoapi.app/v1/messages and swap your Authorization header. You get streaming, tool use, and usage metadata per key, plus a dashboard for team seats, without maintaining your own key-management layer. Check the quickstart or the messages endpoint docs if you want to see how closely it mirrors the SDK's own request shape, and pricing if you're evaluating it for a team.
Wrapping Up
The official @anthropic-ai/sdk gives you solid TypeScript types for messages, streaming, and tool use — the patterns above cover most of what you'll need for a production integration. Start with the basic typed request, add streaming once you have a UI that benefits from it, and reach for tool use when Claude needs to trigger real functions in your app rather than just generate text.
questions
Does the official Claude SDK support TypeScript natively? Yes. @anthropic-ai/sdk is written in TypeScript and ships its own type definitions — no @types package needed, and you get full autocomplete on request and response shapes.
How do I extract plain text from a Claude API response in TypeScript? message.content is an array of content blocks. Filter for block.type === "text" and use a type guard (block is Anthropic.TextBlock) to safely access block.text.
Can I stream Claude responses in a Node.js or browser TypeScript app? Yes, client.messages.stream() returns an async iterator you can loop over with for await, and stream.finalMessage() gives you the complete message once it's done — useful in both server and edge/browser environments.