Claude API TypeScript Types and SDK: A Dev Guide
If you're building a TypeScript app against Claude, you want two things: a reliable SDK (or a typed HTTP client) and type definitions that match the actual request/response shapes so your editor catches mistakes before they hit production. This article covers both — the official SDK, how to type raw fetch/curl-style calls yourself, and how streaming and tool use types work in practice.
The short answer: Anthropic publishes @anthropic-ai/sdk for Node/TypeScript with full type definitions for messages, content blocks, tool schemas, and streaming events. If you don't want to manage API keys, usage limits, or a separate billing relationship for Claude directly, SubToAPI exposes the same message/tool/streaming semantics behind a Messages-compatible HTTPS API, so the TypeScript types you write for one map cleanly onto the other.
The Official SDK and Its Types
@anthropic-ai/sdk ships with TypeScript types out of the box — no @types/ package needed. Installing it gives you typed classes for the client, typed parameters for messages.create, and typed response objects.
npm install @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
async function ask(prompt: string): Promise<string> {
const message = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
});
const block = message.content[0];
if (block.type === "text") {
return block.text;
}
return "";
}
Notice the block.type === "text" check. The SDK types content as a union of block types (text, tool_use, etc.), so TypeScript forces you to narrow the type before accessing .text. This is the main value of using proper types here — it prevents the classic "cannot read property of undefined" bug when a response contains a tool call instead of plain text.
Typing Requests Without the Official SDK
Sometimes you don't want a dependency — you're calling a REST endpoint directly with fetch, or you're hitting a gateway like SubToAPI instead of Anthropic's API directly. In that case, you write your own minimal type definitions. Here's a practical set that covers the common Messages API shape:
type Role = "user" | "assistant";
interface TextBlock {
type: "text";
text: string;
}
interface ToolUseBlock {
type: "tool_use";
id: string;
name: string;
input: Record<string, unknown>;
}
type ContentBlock = TextBlock | ToolUseBlock;
interface Message {
role: Role;
content: string | ContentBlock[];
}
interface CreateMessageRequest {
model: string;
max_tokens: number;
messages: Message[];
system?: string;
stream?: boolean;
}
interface CreateMessageResponse {
id: string;
role: "assistant";
content: ContentBlock[];
stop_reason: "end_turn" | "max_tokens" | "tool_use" | "stop_sequence" | null;
usage: { input_tokens: number; output_tokens: number };
}
These types work whether you're calling Anthropic's endpoint or a compatible one. Against SubToAPI, the same request/response shapes apply:
async function createMessage(req: CreateMessageRequest): Promise<CreateMessageResponse> {
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(req),
});
if (!res.ok) {
throw new Error(`Request failed: ${res.status}`);
}
return res.json() as Promise<CreateMessageResponse>;
}
Because sub_live_... keys speak the same Messages shape, you can reuse these types across both providers if you ever need a fallback path. See /docs/messages for the full request reference.
Typing Tool Use
Tool calling is where types matter most, because the model's output includes structured arguments you need to validate before executing anything.
interface ToolDefinition<TInput = Record<string, unknown>> {
name: string;
description: string;
input_schema: {
type: "object";
properties: Record<string, unknown>;
required?: string[];
};
}
function isToolUseBlock(block: ContentBlock): block is ToolUseBlock {
return block.type === "tool_use";
}
function handleResponse(response: CreateMessageResponse) {
for (const block of response.content) {
if (isToolUseBlock(block)) {
// block.input is typed as Record<string, unknown>
// validate/parse before calling your actual function
console.log(block.name, block.input);
}
}
}
If you want compile-time guarantees on input rather than Record<string, unknown>, pair your tool schema with a runtime validator (Zod, io-ts) and cast after validation — JSON Schema at the API boundary can't be mapped to static types automatically. Details on defining and parsing tool calls are in /docs/tools.
Typing Streaming Responses
Streaming responses are server-sent events, and each event has a different shape depending on type. A minimal discriminated union looks like this:
type StreamEvent =
| { type: "message_start"; message: Partial<CreateMessageResponse> }
| { type: "content_block_start"; index: number; content_block: ContentBlock }
| { type: "content_block_delta"; index: number; delta: { type: "text_delta"; text: string } }
| { type: "content_block_stop"; index: number }
| { type: "message_delta"; delta: { stop_reason: string | null } }
| { type: "message_stop" };
function handleEvent(event: StreamEvent) {
switch (event.type) {
case "content_block_delta":
process.stdout.write(event.delta.text);
break;
case "message_stop":
console.log("\n[done]");
break;
}
}
The switch statement gives you exhaustiveness checking if you enable strict mode and a default case that throws on unknown types — useful for catching API changes early. SubToAPI streams the same event format over SSE; see /docs/streaming for the connection details and parsing example.
Choosing Between SDK and Raw Types
Use the official SDK when you're calling Anthropic directly and want retries, pagination helpers, and type updates handled for you. Write your own types (as above) when you're behind a gateway, need a smaller bundle, or want one type layer that works across multiple backends. If you're evaluating SubToAPI, start with /docs/quickstart — the request/response shapes match what's shown here, so existing type definitions carry over with no changes, and you get per-key usage metadata and team seats without re-deriving billing logic.
questions
Does Anthropic's SDK support TypeScript natively? Yes. @anthropic-ai/sdk includes built-in .d.ts type definitions for requests, responses, content blocks, and streaming events — no separate @types package is required.
Can I type Claude API calls without using the official SDK? Yes. Define interfaces for Message, ContentBlock (as a union), and your request/response shapes, then use fetch with a typed return. This works against any Messages-compatible endpoint, including SubToAPI.
How should I type tool_use blocks safely? Treat input as Record<string, unknown> at the type level, then validate it at runtime with a schema library like Zod before casting it to a specific interface. Static types alone can't verify arbitrary JSON Schema output.