Claude API tutorial: your first call in TypeScript
You can go from nothing to a working Claude call in about ten lines. This post builds it up step by step: a first call, a system prompt, a multi-turn chat, streaming and token counts. Everything is TypeScript and runs on Node 20 or newer.
1. Set up#
mkdir claude-hello && cd claude-hello
npm init -y
npm pkg set type=module
npm i @anthropic-ai/sdk
npm i -D tsx typescriptCreate an API key in the Claude Console and export it. The SDK reads ANTHROPIC_API_KEY automatically, so the key never appears in your code.
export ANTHROPIC_API_KEY="sk-ant-..."2. Your first call#
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const response = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain what a closure is in one paragraph." }],
});
for (const block of response.content) {
if (block.type === "text") console.log(block.text);
}Run it with npx tsx hello.ts.
Three things to notice:
modelpicks the model. Swap in a cheaper one (for exampleclaude-sonnet-5-5) for high-volume work.max_tokensis a required ceiling on the reply length.response.contentis a list of blocks, not a string. Text lives in blocks whosetypeis"text", which is why you narrow before reading.text.
3. Add a system prompt#
The system prompt sets behavior and rules. It is a top-level field, not a message.
const response = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
system: "You are a senior TypeScript reviewer. Be direct. Point out bugs first.",
messages: [{ role: "user", content: "const total = items.map(i => i.price).reduce((a, b) => a + b)" }],
});4. Hold a conversation#
The API is stateless. It does not remember the last call. To chat, you resend the whole history each time: your messages and the assistant's replies, alternating.
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const history: Anthropic.MessageParam[] = [];
async function ask(question: string): Promise<string> {
history.push({ role: "user", content: question });
const response = await client.messages.create({
model: "claude-opus-5-5",
max_tokens: 1024,
messages: history,
});
const text = response.content
.filter((b): b is Anthropic.TextBlock => b.type === "text")
.map((b) => b.text)
.join("");
history.push({ role: "assistant", content: text });
return text;
}
console.log(await ask("What is a closure?"));
console.log(await ask("Show me an example in a React hook."));Because history grows each turn, so does the cost. Long chats need trimming or summarizing, which is a context engineering job.
5. Stream the reply#
For long answers, stream so users see text immediately.
const stream = client.messages.stream({
model: "claude-opus-5-5",
max_tokens: 4096,
messages: [{ role: "user", content: "Write a short guide to TypeScript generics." }],
});
stream.on("text", (delta) => process.stdout.write(delta));
const final = await stream.finalMessage();
console.log("\n\nstop reason:", final.stop_reason);Use finalMessage() to get the complete message once it ends. No need to wrap events in a promise yourself.
6. Read the usage#
Every response reports tokens, which is what you are billed on.
console.log(response.usage.input_tokens, response.usage.output_tokens);Check response.stop_reason too: end_turn means it finished; max_tokens means you cut it off and should raise the limit.
Common mistakes#
| Mistake | Fix |
|---|---|
| Hardcoding the API key | Use the environment variable and keep it out of git |
Reading response.content[0].text directly | Narrow on block.type === "text" first |
| Expecting memory between calls | Resend the history |
max_tokens too small | Answers get cut off with stop_reason: "max_tokens" |
Putting the system prompt in messages | Use the top-level system field |
What to learn next#
Frequently asked questions
How do I call the Claude API from TypeScript?
Install @anthropic-ai/sdk, set the ANTHROPIC_API_KEY environment variable, create a client with new Anthropic(), and call client.messages.create with model, max_tokens and messages. The answer is in the text blocks of response.content.
Does the Claude API remember my conversation?
No. The API is stateless. You send the full message history on every request, appending each assistant reply and the next user message.
What is max_tokens?
The maximum number of tokens the model may generate in this reply. It is a ceiling on output length, not a target. Set it high enough that answers are not cut off.
How do I stream a Claude response?
Use client.messages.stream, listen to the text event to print deltas as they arrive, and await stream.finalMessage() to get the complete message afterwards.
Where do I put the system prompt?
In the top-level system field of the request, not as a message in the messages array.
Prefer plain text? Read this page as Markdown.