← Blog

Claude API SDK TypeScript Type Definitions Guide

2026-10-01 · 5 min read · SubToAPI Team

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:

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

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.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →