Claude API SDK TypeScript Type Definitions Guide
If you're searching for "claude api sdk typescript type definitions," you probably want one of two things: either you're looking for the actual type names exported by Anthropic's SDK (Message, MessageParam, ContentBlock, etc.), or you're trying to figure out how to properly type requests and responses when calling the Claude API from a TypeScript project — including streaming and tool use.
The short answer: Anthropic's official SDK, @anthropic-ai/sdk, ships full TypeScript definitions out of the box. You don't need @types/anthropic or any separate package — install the SDK and the types come with it. Below is a practical rundown of the types you'll actually use, how discriminated unions work for content blocks, and how this applies whether you call Claude directly or through a gateway like SubToAPI that exposes the same Messages API shape.
Where the types live
The types are bundled inside the SDK package itself, under @anthropic-ai/sdk/resources/messages. The ones you'll touch most often:
MessageParam— a single message in the conversation history (role+content)Message— the full response object returned bymessages.createContentBlock— a union type representing text, tool use, tool result, or image contentTextBlock,ToolUseBlock,ToolResultBlockParam— the concrete members of that unionMessageStreamEvent— the discriminated union of events emitted during streamingTool— the shape of a tool definition passed in thetoolsarray
import Anthropic from "@anthropic-ai/sdk";
import type {
Message,
MessageParam,
ContentBlock,
TextBlock,
ToolUseBlock,
} from "@anthropic-ai/sdk/resources/messages";
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const messages: MessageParam[] = [
{ role: "user", content: "Explain TypeScript generics in one paragraph." },
];
const response: Message = await anthropic.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages,
});
response.content is ContentBlock[], which is a discriminated union on the type field. That's the part most people trip over.
Narrowing content blocks
Because ContentBlock can be text, tool use, or a few other variants, TypeScript needs a type guard to narrow it before you access variant-specific fields:
function isTextBlock(block: ContentBlock): block is TextBlock {
return block.type === "text";
}
function extractText(blocks: ContentBlock[]): string {
return blocks.filter(isTextBlock).map((b) => b.text).join("");
}
The same pattern applies to tool calls:
function isToolUse(block: ContentBlock): block is ToolUseBlock {
return block.type === "tool_use";
}
for (const block of response.content) {
if (isToolUse(block)) {
console.log(block.name, block.input); // block.input is typed as unknown by default
}
}
Note that block.input on a ToolUseBlock is typed loosely (usually unknown or Record<string, unknown>) because it depends on your tool's JSON schema. If you want strong typing on tool arguments, define your own interface and cast or validate with a library like Zod rather than trusting the SDK to infer it — the SDK can't know your schema's shape at compile time.
Typing tool definitions
Tool definitions themselves are plain objects matching the Tool type:
interface WeatherArgs {
city: string;
}
const weatherTool = {
name: "get_weather",
description: "Get current weather for a city",
input_schema: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
} as const;
Pairing a const tool definition with a hand-written argument interface (WeatherArgs) gives you a type-safe handler without fighting the SDK's generic input_schema typing. For a deeper walkthrough of defining and handling tools, see /docs/tools.
Typing streaming events
Streaming responses emit a union of event types (message_start, content_block_delta, message_stop, etc.), all covered by MessageStreamEvent. The SDK's MessageStream helper also exposes typed event emitters so you rarely need to switch on the raw union yourself:
const stream = anthropic.messages.stream({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages,
});
stream.on("text", (text: string) => {
process.stdout.write(text);
});
const finalMessage: Message = await stream.finalMessage();
If you need to handle raw events instead (for custom SSE parsing), switch on event.type and let TypeScript narrow the payload for you. Details on event shapes are in /docs/streaming.
Reusing these types with SubToAPI
If you're calling Claude through SubToAPI instead of directly through Anthropic, you don't need a separate type package. SubToAPI's /v1/messages endpoint returns the same JSON shape as Claude's native Messages API, so Message, MessageParam, and ContentBlock from @anthropic-ai/sdk apply unchanged — you just swap the base URL and the auth header:
import type { Message, MessageParam } from "@anthropic-ai/sdk/resources/messages";
async function callSubToAPI(messages: MessageParam[]): Promise<Message> {
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-sonnet-4-5",
max_tokens: 1024,
messages,
}),
});
return res.json() as Promise<Message>;
}
This is useful when a team already turned a Claude subscription into an application API key (sub_live_...) through SubToAPI and wants every service hitting it to stay type-safe without maintaining a duplicate schema. Full request/response field references are in /docs/messages, and setup steps are in /docs/quickstart.
Writing minimal types if you're not using the SDK
Some runtimes (edge functions, lightweight workers) prefer fetch over pulling in the full SDK. In that case, write a minimal interface matching only the fields you use:
interface ClaudeMessageParam {
role: "user" | "assistant";
content: string;
}
interface ClaudeResponse {
id: string;
model: string;
stop_reason: "end_turn" | "max_tokens" | "tool_use" | "stop_sequence" | null;
content: Array<{ type: "text"; text: string } | { type: "tool_use"; name: string; input: unknown }>;
usage: { input_tokens: number; output_tokens: number };
}
This avoids bundling the full SDK in environments where package size matters, at the cost of maintaining the types yourself if the API shape changes.
Common pitfalls
- Forgetting
stop_reasoncan benull— strict null checks will flag this if you don't handle it. - Assuming
contentis always a single text block — it's always an array, even for simple text replies. - Casting
tool_use.inputwithout validation — validate against your schema (Zod, io-ts) before trusting the shape in production code. - Mixing SDK major versions — type names and import paths changed between SDK versions; pin a version in
package.jsonto avoid silent breakage.
If you're evaluating whether to call Claude directly or through a managed layer, /pricing and /signup have the plan details for teams that want API keys, usage metadata, and seats without managing SDK upgrades themselves.
questions
Do I need a separate @types package for the Claude API in TypeScript? No. The official @anthropic-ai/sdk package includes its own TypeScript definitions — install the SDK and import types directly from it.
How do I type a response that could be text or a tool call? Use the ContentBlock union and write a type guard checking block.type ("text" vs "tool_use") before accessing variant-specific fields like text or input.
Can I reuse Claude's TypeScript types when calling a gateway instead of Anthropic directly? Yes, as long as the gateway returns the same Messages API shape. SubToAPI's /v1/messages endpoint matches Claude's response format, so existing Message and ContentBlock types apply without changes.