DeepSeek Node.js TypeScript Guide: Chat Completions, Streaming, JSON & Tools

Quick answer: To use DeepSeek with Node.js or TypeScript, install the OpenAI JavaScript/TypeScript SDK, create an OpenAI client with baseURL: "https://api.deepseek.com", use a DeepSeek API key, and call the current V4 model IDs: deepseek-v4-flash or deepseek-v4-pro.

Use deepseek-v4-flash for fast, economical chat, extraction, JSON Output, routine coding help, summaries, classification, and high-volume workloads. Use deepseek-v4-pro for harder reasoning, complex coding, long-context analysis, agentic workflows, and higher-value production tasks.

Independent note: Chat-Deep.ai is an independent DeepSeek guide and browser access site. It is not affiliated with DeepSeek, DeepSeek.com, the official DeepSeek app, the official DeepSeek developer platform, or OpenAI.

Last verified: April 24, 2026.

Current DeepSeek API snapshot

  • Current API model IDs: deepseek-v4-flash and deepseek-v4-pro
  • Current API generation: DeepSeek-V4 Preview
  • Base URL for OpenAI-compatible requests: https://api.deepseek.com
  • Context length: 1M tokens
  • Maximum output: 384K tokens
  • Thinking mode: supported on both current V4 API models
  • Non-thinking mode: supported on both current V4 API models
  • JSON Output: supported on both current V4 API models
  • Tool Calls: supported on both current V4 API models
  • FIM Completion: supported in non-thinking mode only
  • Legacy aliases: deepseek-chat and deepseek-reasoner currently route to deepseek-v4-flash non-thinking and thinking modes
  • Legacy alias retirement: DeepSeek says deepseek-chat and deepseek-reasoner will be retired after July 24, 2026, 15:59 UTC

Table of contents

Quick Answer: DeepSeek with Node.js and TypeScript

The official DeepSeek quick start uses an OpenAI-compatible API format. For Node.js and TypeScript, the practical path is to install the OpenAI JavaScript/TypeScript SDK, point it at DeepSeek’s base URL, use a DeepSeek API key, and choose a current V4 model.

  1. Install the package with npm install openai.
  2. Store your key in a server-side environment variable such as DEEPSEEK_API_KEY.
  3. Create an OpenAI client with apiKey and baseURL: "https://api.deepseek.com".
  4. Use model: "deepseek-v4-flash" for most normal workflows.
  5. Use model: "deepseek-v4-pro" for harder reasoning, complex coding, long-context, or agentic workflows.
  6. Set thinking: { type: "disabled" } for fast non-thinking routes and thinking: { type: "enabled" } for reasoning routes.

The most common TypeScript migration mistake is property spelling: JavaScript/TypeScript uses baseURL and apiKey. Python uses base_url and api_key.

import "dotenv/config";
import OpenAI from "openai";

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
    reasoning_effort?: "high" | "max";
  };

async function main(): Promise<void> {
  const completion = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages: [
      { role: "system", content: "You are a helpful TypeScript assistant." },
      { role: "user", content: "Explain DeepSeek Node.js setup in one paragraph." }
    ],
    stream: false,
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  console.log(completion.choices[0]?.message?.content ?? "");
}

main().catch((error) => {
  console.error("DeepSeek request failed:", error);
  process.exit(1);
});

Not building an API app? You can also try browser AI chat on Chat-Deep.ai. For official API keys, billing, app downloads, and production account support, use DeepSeek’s official platform and documentation.

Who this guide is for

This guide is for developers building Node.js, TypeScript, Next.js, Express, Fastify, serverless, worker, or backend chat applications with the DeepSeek API. It is also useful for teams migrating OpenAI-compatible Chat Completions code to DeepSeek.

If you only want to try AI chat without coding, use the Chat-Deep.ai browser chat page. If you need a broader API overview, start with the DeepSeek API guide and the OpenAI SDK with DeepSeek guide.

What is the DeepSeek Node.js TypeScript SDK?

Developers often search for “DeepSeek Node.js SDK” or “DeepSeek TypeScript SDK.” In practice, the documented Node.js path is to use the OpenAI JavaScript/TypeScript SDK with DeepSeek’s OpenAI-compatible API format.

In this guide, “DeepSeek Node.js TypeScript SDK” means the OpenAI Node client configured with a DeepSeek API key, DeepSeek base URL, and current DeepSeek V4 model ID. Do not install or recommend an unverified package such as npm install deepseek unless official DeepSeek documentation explicitly documents it for your use case.

Runtime choice: Node.js backend vs browser vs edge

For production apps, the DeepSeek API key should stay server-side. The browser can call your backend route, but it should not call DeepSeek directly with a secret key.

RuntimeRecommended?Notes
Node.js backendYesBest default for keeping API keys private and using the OpenAI Node SDK.
Next.js API routeYesGood for browser chat apps because the browser sends messages to your route, not directly to DeepSeek.
Express / Fastify serverYesGood for custom auth, queues, rate controls, logging, and business rules.
Serverless functionYes, with testingSet timeouts carefully and test long responses, streaming, and retries.
Edge runtime / WorkersPossibleDirect fetch can be simpler than SDK usage depending on runtime compatibility.
Public browser JavaScriptNo for secret keysNever expose DEEPSEEK_API_KEY in front-end bundles.

Install the OpenAI Node SDK

Install the OpenAI JavaScript/TypeScript SDK:

npm install openai

For local TypeScript development, these packages are often useful:

npm install dotenv
npm install -D typescript tsx @types/node

The OpenAI Node SDK is the client library. DeepSeek model names, pricing, feature support, request fields, and beta behavior should still be checked against the official DeepSeek API docs before production deployment.

Set your DeepSeek API key safely

Store your DeepSeek API key in an environment variable. Do not hard-code it in a repository, commit it to GitHub, place it in browser JavaScript, expose it in mobile code, or paste it into client-side logs.

macOS or Linux

export DEEPSEEK_API_KEY="<your_deepseek_api_key>"

Windows PowerShell

[Environment]::SetEnvironmentVariable("DEEPSEEK_API_KEY", "<your_deepseek_api_key>", "User")

Optional local .env file

DEEPSEEK_API_KEY=<your_deepseek_api_key>

If you use a .env file, add it to .gitignore and never commit it. Load it in local scripts with:

import "dotenv/config";

Do not put a secret DeepSeek API key in public browser JavaScript. Do not use dangerouslyAllowBrowser: true in a public client-side app with a real DeepSeek API key. Use a server-side route, API proxy, worker, or backend service that keeps the key private.

DeepSeek baseURL explained

The baseURL tells the OpenAI Node client to send requests to DeepSeek instead of the default OpenAI endpoint. In TypeScript and JavaScript, the property is baseURL. In Python, it is base_url.

baseURLWhen to use itImportant note
https://api.deepseek.comRecommended default for normal DeepSeek API requestsUse this for Chat Completions, streaming, JSON Output, Tool Calls, and most production apps.
https://api.deepseek.com/v1OpenAI compatibility pathThe /v1 path is for compatibility and is not a DeepSeek model version.
https://api.deepseek.com/betaOnly for documented beta featuresUse only where official docs require it, such as strict tool schemas, Chat Prefix Completion, or FIM Completion.

For most DeepSeek API Node.js projects, start with https://api.deepseek.com. Switch to /beta only when a specific documented beta feature requires it.

Choosing a current V4 model

For new code, use deepseek-v4-flash or deepseek-v4-pro. Do not present deepseek-chat or deepseek-reasoner as the primary current model IDs.

ModelUse it forRecommended modeNotes
deepseek-v4-flashFast chat, summaries, extraction, JSON Output, routine coding help, classification, support bots, and cost-sensitive applicationsUsually start with non-thinking modeBest first model for most Node.js and TypeScript migration tests.
deepseek-v4-proHard reasoning, complex coding, long-context analysis, tool planning, agentic workflows, and high-value production tasksUsually use thinking mode for difficult tasksUse when quality and reasoning matter more than lowest token price.
deepseek-chatLegacy compatibility onlyRoutes to V4-Flash non-thinking modeScheduled for retirement after July 24, 2026, 15:59 UTC.
deepseek-reasonerLegacy compatibility onlyRoutes to V4-Flash thinking modeScheduled for retirement after July 24, 2026, 15:59 UTC.

Minimal DeepSeek TypeScript example

This is the smallest practical DeepSeek TypeScript example using the OpenAI-compatible SDK and deepseek-v4-flash in non-thinking mode.

import "dotenv/config";
import OpenAI from "openai";

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
    reasoning_effort?: "high" | "max";
  };

async function main(): Promise<void> {
  const completion = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages: [
      { role: "system", content: "You are a concise developer assistant." },
      { role: "user", content: "Explain DeepSeek Node.js setup in one paragraph." }
    ],
    stream: false,
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  console.log(completion.choices[0]?.message?.content ?? "");
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

The request requires model and messages. The conversation is sent in the messages array, and the final assistant answer is usually returned in completion.choices[0].message.content.

Next.js API route example for browser chat apps

If your goal is a browser chat interface, keep the API key in a backend route. The browser sends the user message to your route, and your route calls DeepSeek. This pattern supports a Chat-Deep.ai-style use case without exposing credentials.

// app/api/deepseek/route.ts
import OpenAI from "openai";

export const runtime = "nodejs";

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
  timeout: 60_000,
  maxRetries: 2,
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
    reasoning_effort?: "high" | "max";
  };

export async function POST(req: Request): Promise<Response> {
  let body: unknown;

  try {
    body = await req.json();
  } catch {
    return Response.json({ error: "Invalid JSON body." }, { status: 400 });
  }

  const message = (body as { message?: unknown }).message;

  if (typeof message !== "string" || !message.trim()) {
    return Response.json({ error: "message must be a non-empty string." }, { status: 400 });
  }

  const completion = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages: [
      { role: "system", content: "You are a concise, helpful assistant." },
      { role: "user", content: message }
    ],
    stream: false,
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  return Response.json({
    answer: completion.choices[0]?.message?.content ?? "",
    usage: completion.usage ?? null,
  });
}

In production, add your own authentication, abuse protection, input limits, logging policy, and rate controls. If you stream responses to the browser, test platform timeouts and server-sent event handling carefully.

Direct fetch fallback for DeepSeek-specific parameters

The OpenAI Node SDK is convenient for normal Chat Completions, streaming, JSON Output, retries, and request IDs. Use direct fetch when you need full control over the raw DeepSeek request body, beta endpoints, experimental fields, or runtime environments where the SDK is not ideal.

type DeepSeekChatResponse = {
  choices?: Array<{
    message?: {
      content?: string | null;
      reasoning_content?: string | null;
    };
  }>;
  usage?: unknown;
};

async function callDeepSeekWithFetch(prompt: string): Promise<DeepSeekChatResponse> {
  const apiKey = process.env.DEEPSEEK_API_KEY;

  if (!apiKey) {
    throw new Error("Missing DEEPSEEK_API_KEY");
  }

  const response = await fetch("https://api.deepseek.com/chat/completions", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "deepseek-v4-pro",
      messages: [
        { role: "user", content: prompt }
      ],
      thinking: { type: "enabled" },
      reasoning_effort: "high",
      stream: false,
    }),
  });

  if (!response.ok) {
    const text = await response.text();
    throw new Error(`DeepSeek request failed: ${response.status} ${text}`);
  }

  return response.json() as Promise<DeepSeekChatResponse>;
}

If your TypeScript SDK version complains about provider-specific fields such as thinking, use a typed request-body extension, a local wrapper, or direct fetch for that route.

Streaming responses in TypeScript

Set stream: true to receive partial response chunks as they are generated. At the HTTP level, DeepSeek streaming uses server-sent events and the stream ends with data: [DONE]. When using the OpenAI Node SDK, you can consume the stream with for await...of.

Not every streaming chunk contains visible text. Some chunks may be empty, some may contain usage metadata when stream_options.include_usage is enabled, and thinking-mode chunks may include reasoning-related fields separately from final content.

import "dotenv/config";
import OpenAI from "openai";

type DeltaWithReasoning = {
  content?: string | null;
  reasoning_content?: string | null;
};

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
    reasoning_effort?: "high" | "max";
    stream_options?: { include_usage?: boolean };
  };

async function main(): Promise<void> {
  const stream = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages: [
      { role: "system", content: "You are a concise TypeScript assistant." },
      { role: "user", content: "Give me three tips for reliable API integrations." }
    ],
    stream: true,
    stream_options: { include_usage: true },
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  let finalUsage: unknown = null;

  for await (const chunk of stream) {
    if ("usage" in chunk && chunk.usage) {
      finalUsage = chunk.usage;
      continue;
    }

    const delta = chunk.choices?.[0]?.delta as DeltaWithReasoning | undefined;

    if (delta?.content) {
      process.stdout.write(delta.content);
    }
  }

  if (finalUsage) {
    console.error("\nusage:", finalUsage);
  }
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

For normal UI streaming, write final answer text from delta.content. If you use thinking mode, keep reasoning text and user-facing final text separate in your interface.

Multi-turn chat in TypeScript

DeepSeek’s Chat Completions API is stateless. The API does not remember earlier turns for you. If you want multi-turn chat, your application must store the conversation history and send the relevant history with each request.

import "dotenv/config";
import OpenAI from "openai";
import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
  };

async function main(): Promise<void> {
  const messages: ChatCompletionMessageParam[] = [
    { role: "system", content: "You are a helpful TypeScript assistant." },
    { role: "user", content: "What is context caching in one sentence?" }
  ];

  const first = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages,
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  const firstAnswer = first.choices[0]?.message?.content ?? "";
  console.log(firstAnswer);

  messages.push({ role: "assistant", content: firstAnswer });
  messages.push({
    role: "user",
    content: "Now explain why repeated prefixes can matter for cost."
  });

  const second = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages,
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  console.log(second.choices[0]?.message?.content ?? "");
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

For ordinary multi-turn chat without tool calls, store the assistant’s final content. For thinking-mode tool-call loops, preserve the full assistant message during the active loop because it may include reasoning_content and tool_calls.

DeepSeek JSON Output in TypeScript

DeepSeek JSON Output uses response_format: { type: "json_object" }. You still need a prompt that explicitly asks for JSON and includes the word “json”. Production code should parse and validate the returned object before using it.

import "dotenv/config";
import OpenAI from "openai";

type ClassificationResult = {
  title: string;
  summary: string;
  category: "api" | "pricing" | "models" | "other";
  confidence: number;
};

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

function isClassificationResult(value: unknown): value is ClassificationResult {
  if (!value || typeof value !== "object") {
    return false;
  }

  const record = value as Record<string, unknown>;

  return (
    typeof record.title === "string" &&
    typeof record.summary === "string" &&
    ["api", "pricing", "models", "other"].includes(String(record.category)) &&
    typeof record.confidence === "number" &&
    record.confidence >= 0 &&
    record.confidence <= 1
  );
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
  };

async function main(): Promise<void> {
  const completion = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages: [
      {
        role: "system",
        content:
          "Return only valid json. Shape: {\"title\":\"string\",\"summary\":\"string\",\"category\":\"api|pricing|models|other\",\"confidence\":0.0}. Do not include Markdown."
      },
      {
        role: "user",
        content:
          "Classify this text: DeepSeek JSON Output helps TypeScript developers build structured API workflows."
      }
    ],
    response_format: { type: "json_object" },
    max_tokens: 500,
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  const choice = completion.choices[0];
  const raw = choice?.message?.content ?? "";

  if (choice?.finish_reason === "length") {
    throw new Error("The JSON may be truncated. Increase max_tokens or reduce the requested output.");
  }

  if (!raw.trim()) {
    throw new Error("The model returned empty content. Retry with a clearer json prompt.");
  }

  let parsed: unknown;

  try {
    parsed = JSON.parse(raw);
  } catch {
    console.error("Raw content:", raw);
    throw new Error("The response was not valid JSON.");
  }

  if (!isClassificationResult(parsed)) {
    throw new Error("JSON parsed, but it did not match the expected shape.");
  }

  console.log(parsed);
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

Valid JSON is not the same as a verified business object. Check required keys, types, enum values, number ranges, and unexpected nulls before sending structured output into automation.

DeepSeek Tool Calls in TypeScript

DeepSeek Tool Calls let the model request structured function calls. The model does not execute your function automatically. Your application validates the arguments, executes the function, appends a tool message with the matching tool_call_id, and sends another request.

import "dotenv/config";
import OpenAI from "openai";
import type {
  ChatCompletionMessageParam,
  ChatCompletionTool,
} from "openai/resources/chat/completions";

type WeatherArgs = {
  location: string;
};

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

function isWeatherArgs(value: unknown): value is WeatherArgs {
  return (
    typeof value === "object" &&
    value !== null &&
    typeof (value as Record<string, unknown>).location === "string" &&
    (value as Record<string, unknown>).location !== ""
  );
}

function getWeather(location: string): Record<string, unknown> {
  return {
    location,
    temperature_c: 26,
    condition: "clear"
  };
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
  };

const tools: ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "get_weather",
      description: "Get current weather for a city or region.",
      parameters: {
        type: "object",
        properties: {
          location: {
            type: "string",
            description: "City and country, for example Cairo, Egypt"
          }
        },
        required: ["location"]
      }
    }
  }
];

async function main(): Promise<void> {
  const messages: ChatCompletionMessageParam[] = [
    { role: "user", content: "What is the weather in Cairo, Egypt?" }
  ];

  const first = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages,
    tools,
    tool_choice: "auto",
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  const assistantMessage = first.choices[0]?.message;
  const toolCalls = assistantMessage?.tool_calls;

  if (!assistantMessage || !toolCalls?.length) {
    console.log(assistantMessage?.content ?? "");
    return;
  }

  messages.push(assistantMessage as ChatCompletionMessageParam);

  for (const toolCall of toolCalls) {
    if (toolCall.function.name !== "get_weather") {
      throw new Error(`Unexpected tool call: ${toolCall.function.name}`);
    }

    let parsedArgs: unknown;

    try {
      parsedArgs = JSON.parse(toolCall.function.arguments);
    } catch {
      throw new Error("Tool call arguments were not valid JSON.");
    }

    if (!isWeatherArgs(parsedArgs)) {
      throw new Error("Tool call arguments did not match the expected shape.");
    }

    const result = getWeather(parsedArgs.location);

    messages.push({
      role: "tool",
      tool_call_id: toolCall.id,
      content: JSON.stringify(result),
    });
  }

  const second = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages,
    tools,
    thinking: { type: "disabled" },
  } as DeepSeekChatBody);

  console.log(second.choices[0]?.message?.content ?? "");
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

For business-critical tools, consider schema validation with a library such as Zod, Ajv, Valibot, or your own validation layer. These libraries are not official DeepSeek requirements; they are practical TypeScript validation options.

Thinking Mode in TypeScript

DeepSeek V4 supports both thinking and non-thinking modes. Thinking mode can improve hard reasoning, complex coding, tool planning, long-context analysis, and agentic workflows. Non-thinking mode is usually better for fast chat, extraction, classification, simple summaries, and cost-sensitive routes.

In thinking mode, reasoning_content is separate from final content. The final user-facing answer is in content. For most UIs, display content and treat reasoning_content as a separate technical field for debugging, tracing, or tool-call continuity.

import "dotenv/config";
import OpenAI from "openai";

type MessageWithReasoning = {
  content?: string | null;
  reasoning_content?: string | null;
};

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
    reasoning_effort?: "high" | "max";
  };

async function main(): Promise<void> {
  const completion = await client.chat.completions.create({
    model: "deepseek-v4-pro",
    messages: [
      { role: "user", content: "Compare two TypeScript retry strategies." }
    ],
    thinking: { type: "enabled" },
    reasoning_effort: "high",
    max_tokens: 2000,
  } as DeepSeekChatBody);

  const message = completion.choices[0]?.message as MessageWithReasoning | undefined;

  if (message?.reasoning_content) {
    console.error("Reasoning content:");
    console.error(message.reasoning_content);
  }

  console.log("Final answer:");
  console.log(message?.content ?? "");
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

Thinking Mode with Tool Calls

For thinking-mode tool workflows, use deepseek-v4-pro when the task needs stronger planning or reasoning. During a thinking + tool-call loop, preserve the full assistant message, including reasoning_content, content, and tool_calls, so the model can continue the same reasoning process after tool results.

import "dotenv/config";
import OpenAI from "openai";
import type {
  ChatCompletionMessageParam,
  ChatCompletionTool,
} from "openai/resources/chat/completions";

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
    reasoning_effort?: "high" | "max";
  };

function lookupOrderStatus(orderId: string): string {
  return JSON.stringify({
    order_id: orderId,
    status: "shipped",
    eta: "2026-04-27"
  });
}

const tools: ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "lookup_order_status",
      description: "Look up shipping status for an order ID.",
      parameters: {
        type: "object",
        properties: {
          order_id: {
            type: "string",
            description: "The user's order ID."
          }
        },
        required: ["order_id"]
      }
    }
  }
];

async function main(): Promise<void> {
  const messages: ChatCompletionMessageParam[] = [
    { role: "user", content: "Check order A123 and explain whether it will arrive this week." }
  ];

  while (true) {
    const response = await client.chat.completions.create({
      model: "deepseek-v4-pro",
      messages,
      tools,
      tool_choice: "auto",
      thinking: { type: "enabled" },
      reasoning_effort: "high",
    } as DeepSeekChatBody);

    const assistantMessage = response.choices[0]?.message;

    if (!assistantMessage) {
      throw new Error("No assistant message returned.");
    }

    // Important: append the full assistant message in a thinking + tool-call loop.
    messages.push(assistantMessage as ChatCompletionMessageParam);

    if (!assistantMessage.tool_calls?.length) {
      console.log(assistantMessage.content ?? "");
      break;
    }

    for (const toolCall of assistantMessage.tool_calls) {
      if (toolCall.function.name !== "lookup_order_status") {
        throw new Error(`Unexpected tool call: ${toolCall.function.name}`);
      }

      const args = JSON.parse(toolCall.function.arguments || "{}") as {
        order_id?: unknown;
      };

      if (typeof args.order_id !== "string") {
        throw new Error("Invalid order_id.");
      }

      messages.push({
        role: "tool",
        tool_call_id: toolCall.id,
        content: lookupOrderStatus(args.order_id),
      });
    }
  }
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

For a fresh normal user turn without tool calls, you do not need to pass previous reasoning content. For a thinking-mode tool-call loop, keep the assistant message intact until the loop is complete.

Strict mode for Tool Calls

DeepSeek strict mode is a beta Tool Calls feature. Use it when you need tighter schema adherence for function-call arguments. Strict mode requires the beta base URL and strict: true on the function definition.

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.DEEPSEEK_API_KEY,
  baseURL: "https://api.deepseek.com/beta",
});

const tools = [
  {
    type: "function",
    function: {
      name: "create_invoice_draft",
      strict: true,
      description: "Create a draft invoice. This does not send the invoice.",
      parameters: {
        type: "object",
        properties: {
          customer_id: {
            type: "string",
            description: "Internal CRM customer ID"
          },
          amount_usd: {
            type: "number",
            description: "Invoice amount in USD"
          },
          memo: {
            type: "string",
            description: "Short invoice memo"
          }
        },
        required: ["customer_id", "amount_usd", "memo"],
        additionalProperties: false
      }
    }
  }
];

Even with strict mode, validate every argument in your backend. Strict mode helps with schema shape, but it is not a replacement for authentication, authorization, business rules, rate limits, or safety checks.

Error handling, retries, and timeouts

The OpenAI Node SDK has its own error classes, retries, and timeout behavior. DeepSeek API status-code meanings should still be verified against DeepSeek’s official docs. When you use the OpenAI SDK with DeepSeek, treat HTTP status codes as provider responses from DeepSeek and SDK exceptions as the client library’s way of surfacing them.

import "dotenv/config";
import OpenAI from "openai";

function getEnv(name: string): string {
  const value = process.env[name];

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

const client = new OpenAI({
  apiKey: getEnv("DEEPSEEK_API_KEY"),
  baseURL: "https://api.deepseek.com",
  timeout: 30_000,
  maxRetries: 2,
});

type DeepSeekChatBody =
  Parameters<typeof client.chat.completions.create>[0] & {
    thinking?: { type: "enabled" | "disabled" };
  };

async function main(): Promise<void> {
  try {
    const completion = await client.chat.completions.create(
      {
        model: "deepseek-v4-flash",
        messages: [
          { role: "user", content: "Give me one TypeScript API reliability tip." }
        ],
        stream: false,
        thinking: { type: "disabled" },
      } as DeepSeekChatBody,
      {
        timeout: 60_000,
        maxRetries: 3,
      }
    );

    console.log(completion.choices[0]?.message?.content ?? "");
  } catch (error) {
    if (error instanceof OpenAI.AuthenticationError) {
      console.error("401 authentication failed. Check your DeepSeek API key.");
      return;
    }

    if (error instanceof OpenAI.RateLimitError) {
      console.error("429 rate limit or traffic-related throttling. Retry with backoff.");
      return;
    }

    if (error instanceof OpenAI.InternalServerError) {
      console.error("DeepSeek server error or overload. Retry after a brief wait.");
      return;
    }

    if (error instanceof OpenAI.APIConnectionTimeoutError) {
      console.error("Connection timed out while waiting for the API.");
      return;
    }

    if (error instanceof OpenAI.APIConnectionError) {
      console.error("Network or connection problem while reaching the API.");
      console.error(error.message);
      return;
    }

    if (error instanceof OpenAI.APIError) {
      console.error("API returned an error.");
      console.error("status:", error.status);
      console.error("message:", error.message);
      return;
    }

    throw error;
  }
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
StatusOfficial meaningWhat to check
400Invalid request body formatFix the request body format, message order, tool messages, or thinking-mode history.
401Authentication failsCheck that you are using a valid DeepSeek API key with the DeepSeek base URL.
402Insufficient balanceCheck account balance and billing setup on the official DeepSeek platform.
422Invalid parametersCheck unsupported or malformed request parameters.
429Rate limit reachedPace requests, reduce concurrency, queue work, and retry with backoff.
500Server errorRetry after a brief wait.
503Server overloadedRetry later and consider graceful fallback.

Token usage, pricing, and context caching

DeepSeek bills based on input and output tokens. In production, read token usage from the response instead of estimating from character count alone.

type DeepSeekUsage = {
  prompt_tokens?: number;
  completion_tokens?: number;
  total_tokens?: number;
  prompt_cache_hit_tokens?: number;
  prompt_cache_miss_tokens?: number;
  completion_tokens_details?: {
    reasoning_tokens?: number;
  };
};

const usage = completion.usage as DeepSeekUsage | undefined;

if (usage) {
  console.log("prompt_tokens:", usage.prompt_tokens);
  console.log("completion_tokens:", usage.completion_tokens);
  console.log("total_tokens:", usage.total_tokens);
  console.log("prompt_cache_hit_tokens:", usage.prompt_cache_hit_tokens);
  console.log("prompt_cache_miss_tokens:", usage.prompt_cache_miss_tokens);
  console.log("reasoning_tokens:", usage.completion_tokens_details?.reasoning_tokens);
}

Current official V4 pricing is listed per 1M tokens and differs by model:

ModelInput cache hitInput cache missOutput
deepseek-v4-flash$0.028 / 1M tokens$0.14 / 1M tokens$0.28 / 1M tokens
deepseek-v4-pro$0.145 / 1M tokens$1.74 / 1M tokens$3.48 / 1M tokens

The request-level cost formula is:

request_cost =
(prompt_cache_hit_tokens / 1_000_000 * cache_hit_rate) +
(prompt_cache_miss_tokens / 1_000_000 * cache_miss_rate) +
(completion_tokens / 1_000_000 * output_rate)

Context caching is enabled by default in DeepSeek’s API. Repeated prefixes can become cache hits, so stable system prompts, repeated document prefixes, and consistent prompt layout can matter for cost. Track prompt_cache_hit_tokens and prompt_cache_miss_tokens where available.

For deeper usage tracking, read our DeepSeek token usage guide and the official DeepSeek Token & Token Usage page. For current pricing, see our DeepSeek API pricing page and the official DeepSeek Models & Pricing page.

Legacy aliases: deepseek-chat and deepseek-reasoner

The older names deepseek-chat and deepseek-reasoner are now legacy compatibility aliases. They should not be the primary model names in new Node.js examples, TypeScript examples, SDK migration guides, pricing pages, or developer tutorials.

Legacy aliasCurrent compatibility behaviorRecommended replacement
deepseek-chatRoutes to deepseek-v4-flash non-thinking modedeepseek-v4-flash with thinking disabled
deepseek-reasonerRoutes to deepseek-v4-flash thinking modedeepseek-v4-flash or deepseek-v4-pro with thinking enabled

Migration rule: replace old examples that use model: "deepseek-chat" with model: "deepseek-v4-flash", and replace reasoning-heavy examples that use model: "deepseek-reasoner" with model: "deepseek-v4-pro" plus thinking enabled.

Common mistakes

  1. Using old model names in new code: Use deepseek-v4-flash and deepseek-v4-pro directly.
  2. Using an OpenAI API key with DeepSeek baseURL: Use a DeepSeek API key for DeepSeek requests.
  3. Using a DeepSeek API key with the default OpenAI endpoint: Set baseURL: "https://api.deepseek.com".
  4. Using base_url in TypeScript: TypeScript uses baseURL. Python uses base_url.
  5. Using api_key in TypeScript: TypeScript uses apiKey. Python uses api_key.
  6. Treating /v1 as a DeepSeek model version: It is an OpenAI compatibility path, not a model version.
  7. Copying OpenAI Responses API examples into DeepSeek: DeepSeek’s official examples use Chat Completions. Do not assume client.responses.create() works unless DeepSeek documents it.
  8. Putting secret API keys in browser code: Keep DeepSeek API keys on the server side.
  9. Assuming app/web behavior equals API behavior: Use official DeepSeek API docs for developer integrations.
  10. Ignoring finish_reason: Check for length, tool_calls, and other completion states.
  11. Passing reasoning text into normal history incorrectly: Use final content for normal conversation history unless the active thinking + tool-call loop requires the full assistant message.
  12. Forgetting to validate JSON or tool arguments: Parse and validate before using structured output or executing tools.
  13. Treating pricing as permanent: Re-check official pricing before launch.

Production checklist

  • Use environment variables for DEEPSEEK_API_KEY.
  • Keep API keys server-side; use backend routes for browser apps.
  • Rotate keys when needed and remove old keys from deployment environments.
  • Use deepseek-v4-flash or deepseek-v4-pro in new code.
  • Use baseURL: "https://api.deepseek.com" for normal production requests.
  • Use https://api.deepseek.com/beta only for documented beta features.
  • Use direct fetch or explicit body control for DeepSeek-specific fields when SDK typings are unclear.
  • Set explicit timeouts.
  • Configure retries and backoff for retryable errors.
  • Handle 400, 401, 402, 422, 429, 500, and 503 with clear behavior.
  • Monitor token usage and response usage fields.
  • Track prompt_cache_hit_tokens and prompt_cache_miss_tokens.
  • Log the model ID used for every request.
  • Validate JSON Output before using it in automation.
  • Validate tool-call arguments before executing functions.
  • Use schema validation for business-critical tool arguments.
  • Use streaming for long UI responses.
  • Use queues and backoff for high-volume workloads.
  • Test prompts with real representative inputs.
  • Verify pricing, model names, context length, output limits, and feature support before launch.

FAQ

What is the DeepSeek Node.js TypeScript SDK?

The phrase usually means using DeepSeek from Node.js or TypeScript through the OpenAI JavaScript/TypeScript SDK configured with a DeepSeek API key, DeepSeek baseURL, and a current DeepSeek V4 model ID.

Does DeepSeek have an official Node.js SDK?

DeepSeek’s official quick start documents Node.js usage through the OpenAI JavaScript/TypeScript SDK configured with DeepSeek’s base URL. This guide uses that documented approach.

How do I install the DeepSeek Node.js TypeScript SDK?

Install the OpenAI JavaScript/TypeScript package with npm install openai, then create an OpenAI client with your DeepSeek API key and baseURL: "https://api.deepseek.com".

What baseURL should I use for DeepSeek in Node.js?

Use https://api.deepseek.com for normal API requests. https://api.deepseek.com/v1 is also supported for OpenAI compatibility, but /v1 is not a model version.

Should I use deepseek-v4-flash or deepseek-v4-pro?

Use deepseek-v4-flash for fast, lower-cost workflows and deepseek-v4-pro for harder reasoning, complex coding, long-context analysis, and agentic workflows.

Should I still use deepseek-chat or deepseek-reasoner?

Not for new code. They are legacy compatibility aliases. deepseek-chat currently routes to deepseek-v4-flash non-thinking mode, and deepseek-reasoner currently routes to deepseek-v4-flash thinking mode.

Can I use the OpenAI Node SDK with DeepSeek?

Yes. DeepSeek’s official quick start says the API uses an OpenAI-compatible format, so Node.js and TypeScript users can use the OpenAI SDK by changing the API key, baseURL, and model ID.

Can I use OpenAI Responses API with DeepSeek?

DeepSeek’s official examples use Chat Completions. Do not assume OpenAI Responses API examples work with DeepSeek unless DeepSeek documents support for your use case.

How do I stream DeepSeek responses in TypeScript?

Set stream: true in client.chat.completions.create() and iterate over the returned stream with for await...of. Read delta.content for visible text and handle empty chunks safely.

How do I use JSON Output with DeepSeek in TypeScript?

Set response_format: { type: "json_object" }, explicitly ask for json in the prompt, provide an example JSON shape, set enough max_tokens, and validate the parsed JSON in your code.

How do I use Tool Calls with DeepSeek in TypeScript?

Define a tools array, let the model return tool_calls, validate the arguments, execute the function in your application, append a tool message with the matching tool_call_id, and send another request.

How do I use thinking mode in Node.js?

Use deepseek-v4-flash or deepseek-v4-pro and send thinking: { type: "enabled" }. For harder reasoning, use deepseek-v4-pro with reasoning_effort: "high" or reasoning_effort: "max".

Why am I getting a 401 error?

A 401 error means authentication failed. Check that you are using a valid DeepSeek API key, that it is loaded into DEEPSEEK_API_KEY, and that your client is pointed at the DeepSeek baseURL.

Can I call DeepSeek directly from browser JavaScript?

Do not put a secret DeepSeek API key in public browser JavaScript. Use a server-side route, API proxy, worker, or backend service that keeps the key private.

Is Chat-Deep.ai the official DeepSeek website?

No. Chat-Deep.ai is an independent guide and browser access site. It is not affiliated with DeepSeek, DeepSeek.com, the official DeepSeek app, or the official DeepSeek developer platform.

Official sources and last verified

Last verified: April 24, 2026. DeepSeek model names, pricing, feature support, output limits, endpoint behavior, and legacy alias behavior can change. Use the official sources below before shipping production code.