Questions? Call 1.800.716.8955

Claude Certified Developer: Foundations

Core patterns in TypeScript

The exam guide expects proficiency in Python and/or TypeScript. The worked examples in this book use Python; here are the same core patterns with the official TypeScript SDK (@anthropicai/sdk). Concepts are identical; mainly syntax differs. One practical difference to remember: SDK timeouts are in milliseconds in TypeScript and seconds in Python.

F.1 A basic 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,
  • system: "You are a concise support assistant.",

messages: [{ role: "user", content: "How do I reset my password?" }], });

if (response.stop_reason === "max_tokens") console.warn("truncated"); for (const block of response.content) {

  • if (block.type === "text") console.log(block.text);
  • }
  • console.log(response.usage);

F.2 Streaming

  • const stream = client.messages.stream({
  • model: "claude-sonnet-5-5",
  • max_tokens: 2048,

messages: [{ role: "user", content: "Explain prompt caching in two paragraphs." }], });

stream.on("text", (delta) => process.stdout.write(delta));

const final = await stream.finalMessage(); // full message, stop_reason, usage console.log("\noutput tokens:", final.usage.output_tokens);

Use finalMessage() rather than wrapping events in your own Promise; it handles completion, errors, and aborts.

F.3 Error handling

  • try {
  • await client.messages.create({ /* ... */ });
  • } catch (error) {
  • if (error instanceof Anthropic.BadRequestError) {
  • // 400: fix the request, do not retry
  • } else if (error instanceof Anthropic.AuthenticationError) {
  • // 401: check the key
  • } else if (error instanceof Anthropic.RateLimitError) {
  • // 429: the SDK already retried; back off and requeue
  • } else if (error instanceof Anthropic.APIError) {
  • console.error(`API error ${error.status}`, error.message);
  • } else {
  • throw error;
  • }
  • }

All API error classes extend Anthropic.APIError with a typed status. Check the most specific class first.

F.4 Tools with the Tool Runner and Zod

import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod"; import { z } from "zod";

  • const getOrder = betaZodTool({
  • name: "orders_get_order",
  • description:

"Look up an order's status, carrier, and estimated delivery by order ID. " + "Use when the customer asks where an order is. Does not modify orders.", inputSchema: z.object({

order_id: z.string().describe("Order ID such as ORD-12345"),

}),

run: async ({ order_id }) => JSON.stringify(await ordersDb.get(order_id)), });

  • const final = await client.beta.messages.toolRunner({
  • model: "claude-opus-5-5",
  • max_tokens: 4096,
  • tools: [getOrder],

messages: [{ role: "user", content: "Where is order ORD-881?" }], });

The runner validates each tool input against the Zod schema before calling run, then loops until Claude finishes.

F.5 A manual tool loop

const messages: Anthropic.MessageParam[] = [

{ role: "user", content: "Where is order ORD-881 and who placed it?" }, ];

  • for (let turn = 0; turn < 8; turn++) {
  • const res = await client.messages.create({
  • model: "claude-opus-5-5", max_tokens: 4096, tools, messages,
  • });
  • messages.push({ role: "assistant", content: res.content });
  • if (res.stop_reason !== "tool_use") break;
  • const results: Anthropic.ToolResultBlockParam[] = [];
  • for (const block of res.content) {
  • if (block.type !== "tool_use") continue;
  • try {
  • const output = await runTool(block.name, block.input);

results.push({ type: "tool_result", tool_use_id: block.id, content: JSON.stringify(output) });

} catch (e) {

results.push({ type: "tool_result", tool_use_id: block.id, content: String(e), is_error: true });

}

}

messages.push({ role: "user", content: results }); // all results in one message }

Use the SDK's types (Anthropic.MessageParam, Anthropic.ToolResultBlockParam) rather than defining your own interfaces.

F.6 Structured outputs with Zod

import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod"; const Ticket = z.object({

category: z.enum(["billing", "login", "bug", "feature_request", "other"]), urgency: z.enum(["low", "normal", "high"]),

  • summary: z.string(),
  • });
  • const res = await client.messages.parse({
  • model: "claude-haiku-5-5",
  • max_tokens: 512,

messages: [{ role: "user", content: `<ticket>${ticketText}</ticket>` }], output_config: { format: zodOutputFormat(Ticket) },

});

const ticket = res.parsed_output; // null if parsing failed: guard before use

F.7 Prompt caching

  • const res = await client.messages.create({
  • model: "claude-sonnet-5-5",
  • max_tokens: 1024,
  • system: [
  • { type: "text", text: "You are Acme's policy assistant." },

{ type: "text", text: POLICY_MANUAL, cache_control: { type: "ephemeral" } },

Appendix f: core patterns in typescript

  • ],
  • messages: [{ role: "user", content: question }],
  • });

console.log(res.usage.cache_read_input_tokens, res.usage.cache_creation_input_tokens);

F.8 Message Batches

for await (const item of await client.messages.batches.results(batch.id)) { if (item.result.type === "succeeded") {

save(item.custom_id, item.result.message.content);

} else {

retryQueue.push(item.custom_id); // errored, canceled, or expired }

}

F.9 Uploading a file

  • import { toFile } from "@anthropic-ai/sdk";
  • import fs from "fs";
  • const uploaded = await client.files.upload({

file: await toFile(fs.createReadStream("handbook.pdf"), undefined, { type: "application/ pdf" }),

});

// reference later as { type: "document", source: { type: "file", file_id: uploaded.id } } Older SDK versions expose the Files API under client.beta.files with a beta header; check your installed version.

F.10 An MCP server in TypeScript

The official MCP TypeScript SDK follows the same model as the Python example in Chapter 9: create a server, register tools (with a name, description, and input schema), optionally register resources and prompts, and connect a transport (stdio for local use, Streamable HTTP for remote). Consult the MCP SDK's README for the current registration API, as it has evolved across versions.

Use ← → to move between sections