@anthropic-ai/sdk
Official TypeScript/JavaScript client for the Claude API. Wraps the Messages endpoint with typed requests and responses, streaming helpers, automatic retries with backoff, and typed error classes per HTTP status. Runs on Node 20+, Deno, Bun, Cloudflare Workers, and Vercel Edge; browser use is disabled by default so you cannot accidentally ship your API key to clients. It is a 0.x package that tracks a fast-moving API, so minor releases can carry breaking changes.
The correct way to call Claude from TypeScript: well built, typed end to end, and released in lockstep with the API. Treat it like the 0.x package it is: pin versions and expect steady churn.
Use it if
- You call the Claude API from server-side TypeScript or JavaScript and want typed params instead of hand-rolled fetch calls
- You need streaming with real helpers: client.messages.stream() plus finalMessage() beats parsing SSE yourself
- You want tool use, structured outputs validated against Zod schemas, and token counting handled by the library
- You deploy to edge runtimes (Cloudflare Workers, Vercel Edge) where many HTTP clients break
- You are building a browser-only app: the SDK blocks browser use by default because it would expose your secret key; route calls through your own backend instead of setting dangerouslyAllowBrowser
- You target React Native: the README states it is not supported
- You want one client across OpenAI, Google, and Anthropic models: a multi-provider layer like the ai package saves you maintaining N SDKs
- You need a frozen API surface: this is a pre-1.0 package where minor versions have shipped breaking changes, so pin the version and read changelogs before upgrading
Setup reality
npm install @anthropic-ai/sdk, set ANTHROPIC_API_KEY, and new Anthropic() picks it up automatically. No peer dependencies; TypeScript 4.9+ and Node 20+ required. The friction is churn, not setup: the package is 0.x and tracks the API closely, so model ids, beta feature flags, and parameter shapes move between minor versions. Pin your version. Also note Jest only works with the node test environment (jsdom is unsupported), and the client timeout option is in milliseconds, unlike the Python SDK's seconds.
Patterns
Send a basic messagecreate-message
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const msg = await client.messages.create({
model: 'claude-opus-5',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello, Claude' }],
});
for (const block of msg.content) {
if (block.type === 'text') console.log(block.text);
}content is an array of typed blocks; narrow on block.type before reading .text or TypeScript will error.
Set a system promptsystem-prompt
const msg = await client.messages.create({
model: 'claude-opus-5',
max_tokens: 1024,
system: 'You are a terse SQL expert. Answer with queries only.',
messages: [{ role: 'user', content: 'Top 5 customers by revenue' }],
});system is a top-level parameter, not a message with role system at index 0.
Stream a response token by tokenstream-response
const stream = client.messages.stream({
model: 'claude-opus-5',
max_tokens: 64000,
messages: [{ role: 'user', content: 'Write a long report' }],
});
stream.on('text', (delta) => process.stdout.write(delta));
const final = await stream.finalMessage();
console.log('\ntokens:', final.usage.output_tokens);Use finalMessage() for the complete message; large max_tokens values require streaming to avoid HTTP timeouts.
Let Claude call your function (tool runner)tool-use
import { betaZodTool } from '@anthropic-ai/sdk/helpers/beta/zod';
import { z } from 'zod';
const getWeather = betaZodTool({
name: 'get_weather',
description: 'Get current weather for a location',
inputSchema: z.object({ location: z.string() }),
run: async ({ location }) => `22C and clear in ${location}`,
});
const final = await client.beta.messages.toolRunner({
model: 'claude-opus-5',
max_tokens: 1024,
tools: [getWeather],
messages: [{ role: 'user', content: 'Weather in Paris?' }],
});The tool runner (beta) drives the call-execute-loop cycle for you; drop to a manual stop_reason === 'tool_use' loop only when you need to own the whole loop.
Handle rate limits and API errors by typehandle-errors
import Anthropic from '@anthropic-ai/sdk';
try {
await client.messages.create({ /* ... */ });
} catch (err) {
if (err instanceof Anthropic.RateLimitError) {
// 429: back off and retry
} else if (err instanceof Anthropic.APIConnectionError) {
// network failure before a response
} else if (err instanceof Anthropic.APIError) {
console.error(err.status, err.message);
} else {
throw err;
}
}Check APIConnectionError before APIError; in this SDK it is a subclass of APIError. The client already retries 429/5xx twice by default.
Count tokens before sendingcount-tokens
const count = await client.messages.countTokens({
model: 'claude-opus-5',
messages: [{ role: 'user', content: longDocument }],
});
console.log(count.input_tokens);Counts are model-specific; never estimate Claude tokens with tiktoken, it is OpenAI's tokenizer and undercounts.
Get schema-validated JSON outputstructured-output
import { z } from 'zod';
import { zodOutputFormat } from '@anthropic-ai/sdk/helpers/zod';
const Contact = z.object({ name: z.string(), email: z.string() });
const res = await client.messages.parse({
model: 'claude-opus-5',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Extract: Jane Doe, jane@co.com' }],
output_config: { format: zodOutputFormat(Contact) },
});
console.log(res.parsed_output?.name);parsed_output is null when parsing failed; also note the old top-level output_format parameter is deprecated in favor of output_config.format.
Send an image for analysisimage-input
import fs from 'fs';
const data = fs.readFileSync('chart.png').toString('base64');
const msg = await client.messages.create({
model: 'claude-opus-5',
max_tokens: 1024,
messages: [{
role: 'user',
content: [
{ type: 'image', source: { type: 'base64', media_type: 'image/png', data } },
{ type: 'text', text: 'What trend does this chart show?' },
],
}],
});URL sources also work: { type: 'url', url: '...' }. Put images before the text block that asks about them.
Cache a large stable prompt prefixprompt-caching
const msg = await client.messages.create({
model: 'claude-opus-5',
max_tokens: 1024,
system: [{
type: 'text',
text: bigStableInstructions,
cache_control: { type: 'ephemeral' },
}],
messages: [{ role: 'user', content: userQuestion }],
});
console.log(msg.usage.cache_read_input_tokens);Caching is a byte-exact prefix match: a timestamp or random id anywhere in the prefix silently kills every cache hit.
Tune timeout and retriesconfigure-client
const client = new Anthropic({
timeout: 60_000, // milliseconds in the TS SDK
maxRetries: 3, // default is 2 (retries 408/429/5xx)
});
// per-request override
await client.messages.create({ /* ... */ }, { timeout: 5_000 });Timeouts are retried too, so worst-case wall clock is timeout times (maxRetries + 1).