Claude API TypeScript Types: Definitions Guide
If you're building against the Claude API in TypeScript, you have two main options for type definitions: use the official @anthropic-ai/sdk package, which ships full TypeScript types for every request and response shape, or write your own interfaces if you're calling the HTTP API directly (common when proxying through a gateway or avoiding the SDK's dependency footprint).
This guide covers both paths: what types the official SDK gives you out of the box, how to model messages, streaming events, and tool use yourself, and how to keep your types accurate when you're not using the SDK at all — for example when calling Claude through a REST wrapper like SubToAPI.
Where Claude API types come from
Anthropic's official TypeScript SDK (@anthropic-ai/sdk) exports typed interfaces for every endpoint: MessageCreateParams, Message, ContentBlock, ToolUseBlock, Usage, and the streaming event union types. If you install the SDK, you get these for free and your editor will autocomplete fields, catch typos in role names, and flag missing required parameters at compile time.
npm install @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const response: Anthropic.Message = await client.messages.create({
model: "claude-sonnet-4",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain promises in JS" }],
});
This is the simplest path if you're calling Anthropic directly and don't mind the SDK dependency. The types are generated from Anthropic's internal schema and updated with each SDK release, so they track model and parameter changes closely.
Core message types you'll actually use
Even if you use the SDK, it helps to know the shape you're working with, because you'll often destructure or re-serialize these objects. The core request shape looks like this:
interface MessageCreateParams {
model: string;
max_tokens: number;
messages: MessageParam[];
system?: string;
temperature?: number;
stream?: boolean;
tools?: Tool[];
}
interface MessageParam {
role: "user" | "assistant";
content: string | ContentBlock[];
}
type ContentBlock =
| { type: "text"; text: string }
| { type: "image"; source: ImageSource }
| { type: "tool_use"; id: string; name: string; input: Record<string, unknown> }
| { type: "tool_result"; tool_use_id: string; content: string };
The response shape mirrors this on the way back:
interface Message {
id: string;
type: "message";
role: "assistant";
content: ContentBlock[];
model: string;
stop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | null;
usage: {
input_tokens: number;
output_tokens: number;
};
}
Writing these out yourself is useful when you're calling a gateway that returns Anthropic-compatible JSON but you'd rather not pull in the full SDK just for types. SubToAPI's /docs/messages endpoint, for instance, returns this same Message shape, so these interfaces work unchanged against it.
Typing streaming responses
Streaming is where hand-rolled types get messy if you're not careful, because the response is a sequence of discrete event types, not a single object. The official SDK models this as a discriminated union:
type StreamEvent =
| { type: "message_start"; message: Partial<Message> }
| { 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 }; usage: { output_tokens: number } }
| { type: "message_stop" };
A discriminated union on the type field lets TypeScript narrow correctly inside a switch:
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;
}
}
If you're parsing server-sent events manually against any Claude-compatible streaming endpoint — including SubToAPI's /docs/streaming — this same union works because the event names and payload shapes follow the same convention.
Typing tool use
Tool use (function calling) adds two more shapes: the tool definition you send, and the tool_use block Claude returns when it wants to call one.
interface Tool {
name: string;
description: string;
input_schema: {
type: "object";
properties: Record<string, unknown>;
required?: string[];
};
}
interface ToolUseBlock {
type: "tool_use";
id: string;
name: string;
input: Record<string, unknown>;
}
In practice you'll want to narrow input for each tool with a generic or a per-tool type, since Record<string, unknown> won't catch mistakes in the fields your tool actually expects:
interface WeatherToolInput {
location: string;
unit?: "celsius" | "fahrenheit";
}
function isWeatherInput(input: unknown): input is WeatherToolInput {
return typeof input === "object" && input !== null && "location" in input;
}
This pattern — a type guard per tool — scales better than trying to make one giant discriminated union of every possible tool input, especially once you have more than three or four tools. See /docs/tools for how tool definitions and results are structured end to end.
Using types without the official SDK
If you're not using @anthropic-ai/sdk — maybe you're calling the API with fetch, or routing through a proxy — you lose automatic type generation but keep full control over bundle size and dependencies. The practical approach is to copy the minimal interfaces above into a single types/claude.ts file and import them wherever you make requests. Since the request/response JSON shape is stable and documented, these types rarely need updates beyond new model names or occasional new stop reasons.
This is also the setup most teams land on when using a gateway service rather than calling Anthropic directly. SubToAPI exposes application keys (sub_live_...) over a plain HTTPS API with the same message and streaming shapes, so the same hand-written types apply without modification — see /docs/quickstart for a minimal typed request example, and /pricing if you're evaluating it for a team.
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",
max_tokens: 1024,
messages: [{ role: "user", content: "Summarize this ticket" }],
} satisfies MessageCreateParams),
});
const data: Message = await res.json();
The satisfies operator is worth using here — it validates your request body against the interface without widening the inferred type, which catches typos in field names before you ever send the request.
questions
Does Anthropic publish an official TypeScript types package separately from the SDK? No. Types ship bundled inside @anthropic-ai/sdk — there's no standalone @types/anthropic package. If you don't want the SDK, you write your own interfaces based on the documented request/response JSON.
Do these types work for gateways that aren't Anthropic's own API? Yes, as long as the gateway returns Anthropic-compatible message and streaming shapes. SubToAPI, for example, mirrors the same Message and SSE event structure, so hand-written types built from Anthropic's schema work unchanged.
What's the safest way to type tool inputs? Use a type guard function per tool rather than one large union. It keeps validation logic next to the type definition and avoids brittle discriminated unions as your tool count grows.