Para usar DeepSeek API con Node.js, necesitas una clave de DeepSeek, un proyecto de JavaScript y un cliente configurado para enviar solicitudes a su servicio. En esta guía prepararás una consulta de texto, recibirás respuestas en streaming y conservarás el contexto de una conversación.
El ejemplo utiliza el paquete openai, publicado por OpenAI, con la configuración compatible que muestra DeepSeek en su guía oficial. La clave y la facturación corresponden a DeepSeek: los archivos se ejecutan en tu computadora, pero el modelo responde desde la API remota.
Primera llamada a DeepSeek API desde Node.js
Usa Node.js 24 LTS para seguir este tutorial. Es una rama con soporte y está incluida entre los entornos admitidos por el SDK de JavaScript. Puedes comprobar las ramas vigentes en el calendario oficial de Node.js.
- Una terminal y un editor de texto en tu computadora.
- Una clave creada en la plataforma de DeepSeek.
- Saldo disponible para las solicitudes de API. Revisa las condiciones de facturación antes de ejecutar los ejemplos.
Comprueba que Node.js y npm están disponibles:
node --version
npm --version
1. Crea el proyecto e instala el SDK
Ejecuta estos comandos, uno por uno, en PowerShell, Terminal de macOS o una terminal de Linux:
mkdir deepseek-node
cd deepseek-node
npm init -y
npm install [email protected] --save-exact
La instalación fija openai 7.55.0, la versión usada para la comprobación local de los ejemplos. Conserva package-lock.json para reproducir las dependencias.
Los archivos llevarán la extensión .mjs. Node.js los interpreta como módulos ES, por lo que puedes utilizar import y await sin modificar el campo type de package.json.
2. Guarda la clave y configura el cliente
Dentro de deepseek-node, crea un archivo llamado exactamente .env. Sustituye el marcador por tu clave real y guarda el archivo:
DEEPSEEK_API_KEY=TU_CLAVE_DE_DEEPSEEK
Crea también .gitignore con este contenido:
node_modules/
.env
.env.*
!.env.example
Si compartes el proyecto, un archivo .env.example puede contener el marcador, nunca tu clave. Si una clave ya se publicó, quitarla del archivo no la vuelve privada: revócala y genera otra.
Ahora crea cliente.mjs. Centraliza la conexión para reutilizarla en los tres ejemplos:
import OpenAI from "openai";
const apiKey = process.env.DEEPSEEK_API_KEY?.trim();
if (!apiKey || apiKey === "TU_CLAVE_DE_DEEPSEEK") {
throw new Error("Configura DEEPSEEK_API_KEY y ejecuta con --env-file=.env.");
}
export const modelo = "deepseek-flash";
export const cliente = new OpenAI({
apiKey,
baseURL: "https://api.deepseek.com",
maxRetries: 0,
});
export function informarError(error, signal) {
if (signal?.aborted) {
console.error("Se agotó el plazo local de la solicitud.");
} else if (error instanceof OpenAI.APIError) {
console.error("Error de API. Estado HTTP:", error.status ?? "no disponible");
} else {
console.error(error instanceof Error ? error.message : "Error inesperado.");
}
process.exitCode = 1;
}
Con baseURL: "https://api.deepseek.com", el SDK envía las solicitudes a DeepSeek. Utiliza una clave de ese proveedor; una clave de OpenAI o de un intermediario no corresponde a esta configuración.

3. Envía una consulta y lee la respuesta
Crea consulta.mjs en la misma carpeta:
import { cliente, modelo, informarError } from "./cliente.mjs";
const signal = AbortSignal.timeout(60_000);
try {
const respuesta = await cliente.chat.completions.create({
model: modelo,
messages: [
{ role: "system", content: "Responde en español latinoamericano." },
{ role: "user", content: "Explica qué es una API en tres frases." },
],
thinking: { type: "disabled" },
max_tokens: 512,
stream: false,
}, { signal });
const opcion = respuesta.choices?.[0];
const texto = opcion?.message?.content;
if (typeof texto !== "string" || !texto.trim()) {
throw new Error("La API no devolvió una respuesta de texto.");
}
console.log(texto);
console.log("Uso (tokens):", respuesta.usage ?? "No recibido.");
if (opcion.finish_reason !== "stop") {
throw new Error(
"Respuesta incompleta o detenida: " + (opcion.finish_reason ?? "sin motivo")
);
}
} catch (error) {
informarError(error, signal);
}
Ejecuta el archivo desde la carpeta del proyecto:
node --env-file=.env consulta.mjs
La opción --env-file de Node.js carga el archivo antes de ejecutar el programa. Crear .env sin cargarlo no basta para que su contenido aparezca en process.env.
Si la solicitud se completa, verás una explicación en español seguida del objeto de uso de tokens. El texto y los contadores dependerán de la respuesta recibida. El programa también comprueba que exista texto y que la generación haya terminado normalmente.
Si aparece un mensaje de respuesta incompleta después del texto, revisa el motivo de finalización. El ejemplo señala el problema y termina con un código de error en lugar de presentar esa salida como una ejecución correcta.
Qué hace cada ajuste del ejemplo
| Ajuste | Función en este proyecto |
|---|---|
model: modelo | Usa deepseek-flash, definido en cliente.mjs. |
messages | Incluye una instrucción de idioma y la pregunta que debe responder el modelo. |
thinking: { type: "disabled" } | Desactiva expresamente el modo de pensamiento para esta demostración de texto. |
max_tokens: 512 | Fija el presupuesto máximo de salida elegido para el ejemplo. No representa el máximo de la plataforma. |
AbortSignal.timeout(60_000) | Cancela la espera local al alcanzar 60 segundos. Es una decisión del ejemplo, no un plazo garantizado por DeepSeek. |
maxRetries: 0 | Desactiva los reintentos automáticos del SDK. Cada consulta del ejemplo hace una sola tentativa. |
El identificador deepseek-flash figura en la configuración oficial de primera llamada. Comprueba allí los nombres vigentes cuando cambies de modelo, especialmente si partes de un tutorial antiguo.
DeepSeek documenta el modo de pensamiento como activado por defecto. Aquí se desactiva para mantener un recorrido sencillo. Si lo habilitas, trata reasoning_content y content como campos distintos y revisa los parámetros compatibles.
Los 60 segundos se implementan mediante AbortSignal.timeout(). La señal se pasa en las opciones del SDK, fuera del cuerpo de la solicitud. Puedes adaptar ese plazo a tu aplicación; cancelar la espera no demuestra que el proveedor no haya procesado parte del trabajo.
Cómo recibir respuestas en streaming
El streaming permite mostrar fragmentos conforme llegan. Para conservar el mismo proyecto, crea stream.mjs junto a cliente.mjs:
import { cliente, modelo, informarError } from "./cliente.mjs";
const signal = AbortSignal.timeout(60_000);
let texto = "";
let motivoFinal;
let uso;
try {
const flujo = await cliente.chat.completions.create({
model: modelo,
messages: [
{ role: "system", content: "Responde en español latinoamericano." },
{ role: "user", content: "Explica tres usos de Node.js en una lista breve." },
],
thinking: { type: "disabled" },
max_tokens: 512,
stream: true,
stream_options: { include_usage: true },
}, { signal });
for await (const parte of flujo) {
if (parte.usage) uso = parte.usage;
const opcion = parte.choices?.[0];
const fragmento = opcion?.delta?.content;
if (typeof fragmento === "string") {
texto += fragmento;
process.stdout.write(fragmento);
}
if (opcion?.finish_reason) {
motivoFinal = opcion.finish_reason;
}
}
signal.throwIfAborted();
process.stdout.write("\n");
if (motivoFinal !== "stop") {
throw new Error(
"El flujo no terminó de forma normal: " + (motivoFinal ?? "sin confirmación")
);
}
if (!texto.trim()) {
throw new Error("El flujo terminó sin una respuesta de texto.");
}
console.log("Uso (tokens):", uso ?? "No recibido.");
} catch (error) {
if (texto) {
process.stderr.write("\nLa respuesta mostrada es parcial o no confirmada.\n");
}
informarError(error, signal);
}
Inícialo con:
node --env-file=.env stream.mjs
La referencia de Chat Completions describe el flujo como eventos SSE. El SDK permite recorrerlos con for await...of; algunos eventos no contienen texto.

Este código imprime únicamente delta.content, recoge usage cuando llega y comprueba el cierre. En esta demostración, finish_reason: "stop" indica finalización normal; "length" señala que se alcanzó un límite. Un corte sin confirmación final se informa como error.
Si faltan las estadísticas de uso, se muestra No recibido.. Eso no significa cero consumo. Tampoco se reinicia automáticamente una respuesta interrumpida: el usuario podría haber visto una parte y un segundo intento generaría una respuesta nueva.
Cómo mantener una conversación de varios turnos
El endpoint /chat/completions no reconstruye automáticamente el contexto de llamadas anteriores. Debes enviar los mensajes necesarios en cada solicitud, como explica la guía oficial de conversaciones.

Crea conversacion.mjs. Este archivo hace dos solicitudes: la segunda incluye la primera pregunta y la respuesta del asistente.
import { cliente, modelo, informarError } from "./cliente.mjs";
const historial = [
{ role: "system", content: "Responde en español y con explicaciones breves." },
];
const preguntas = [
"¿Qué es una variable de entorno?",
"Ahora dame un ejemplo para una clave API, sin usar una clave real.",
];
for (const pregunta of preguntas) {
const signal = AbortSignal.timeout(60_000);
const mensaje = { role: "user", content: pregunta };
try {
const respuesta = await cliente.chat.completions.create({
model: modelo,
messages: [...historial, mensaje],
thinking: { type: "disabled" },
max_tokens: 512,
}, { signal });
const opcion = respuesta.choices?.[0];
const texto = opcion?.message?.content;
if (opcion?.finish_reason !== "stop" ||
typeof texto !== "string" || !texto.trim()) {
throw new Error("No se agregó al historial una respuesta incompleta.");
}
historial.push(mensaje, { role: "assistant", content: texto });
console.log("\nPregunta:", pregunta);
console.log("Respuesta:", texto);
console.log("Uso (tokens):", respuesta.usage ?? "No recibido.");
} catch (error) {
informarError(error, signal);
break;
}
}
node --env-file=.env conversacion.mjs
El historial solo se amplía cuando llega una respuesta completa. Si falla una llamada, el programa se detiene sin incorporar una respuesta parcial a la conversación.
En una aplicación, conserva un historial separado por usuario o sesión. Define cuánto contexto necesita cada tarea y elimina o resume lo que dejó de ser útil. Enviar una conversación creciente aumenta el texto de entrada de las siguientes solicitudes. Este ejemplo mantiene todo únicamente en memoria mientras el proceso está abierto.
Errores frecuentes y cómo resolverlos
Empieza por distinguir un problema local de una respuesta HTTP del proveedor. Los significados de los estados de la tabla proceden de la documentación oficial de errores de DeepSeek; las acciones indican qué revisar en este proyecto.
| Síntoma o estado | Qué revisar |
|---|---|
Configura DEEPSEEK_API_KEY | Sustituye el marcador de .env y ejecuta con --env-file=.env. |
No se encuentra .env | Abre la terminal en deepseek-node. Comprueba que el archivo no se haya guardado como .env.txt. |
ERR_MODULE_NOT_FOUND | Instala la dependencia en esa carpeta y conserva cliente.mjs junto a los otros archivos. |
Error relacionado con import | Mantén la extensión .mjs y comprueba node --version. |
400 o 422 | Revisa el cuerpo, el nombre del modelo y los parámetros. Vuelve al ejemplo mínimo antes de añadir opciones. |
401 | Verifica que la clave sea válida para DeepSeek y que el cliente use su dirección oficial. |
402 | Comprueba el saldo de la cuenta en la plataforma de DeepSeek. |
429 | Reduce la frecuencia y las solicitudes simultáneas; espera antes de volver a intentarlo. |
500 o 503 | Espera y repite de forma limitada. Si persiste, consulta el soporte del proveedor. |
| Estado HTTP no disponible | Revisa la conexión, DNS y la configuración de red. No desactives la verificación TLS para resolverlo. |
| Se agotó el plazo local | La operación superó el plazo del ejemplo. Evalúa si necesitas más tiempo o si existe un problema de conectividad. |
Respuesta con length | Solicita una salida más breve o ajusta el presupuesto de tokens y el tamaño del contexto. |

Si cambiaste la clave en .env y el problema continúa, comprueba si la terminal ya tiene otra variable con el mismo nombre. Node.js da prioridad al valor existente en el entorno sobre el del archivo. Puedes comprobar su presencia sin imprimir el secreto:
node -e "console.log(Object.hasOwn(process.env, 'DEEPSEEK_API_KEY'))"
Un resultado true indica que la terminal ya define esa variable, incluso si está vacía, y puede impedir que se use el valor de .env. Actualízala o elimínala de esa sesión antes de volver a ejecutar el ejemplo. Esta comprobación no valida la clave ni muestra su contenido.
Cuándo conviene reintentar
El SDK dispone de reintentos automáticos, que aquí se desactivan. Si los habilitas después, define un número limitado y evita añadir otra capa que multiplique los intentos.
Un error de clave o de saldo exige corregir la causa. Para fallos transitorios, aplica esperas progresivas y un máximo de intentos. Si ya mostraste texto de un flujo, decide cómo avisar del corte antes de iniciar otra generación.
Tokens, costos y límites del ejemplo
Las solicitudes de modelos se facturan según los tokens procesados. La página oficial de precios diferencia entrada, salida, caché y condiciones horarias. Revisa la tarifa aplicable a tu modelo; el acceso a la interfaz de chat no implica que estas llamadas de API sean gratis.
El objeto usage permite observar el consumo. Para interpretar las unidades y sus estimaciones, consulta la guía de tokens de DeepSeek. En los ejemplos encontrarás:
prompt_tokens: tokens de entrada de la solicitud.completion_tokens: tokens generados en la salida.total_tokens: total informado para esa solicitud.
No conviertas palabras o caracteres en tokens mediante una equivalencia fija. Tampoco multipliques todos los tokens por una sola tarifa si los tipos de entrada y salida tienen precios diferentes.
Para controlar el consumo, limita el tamaño de las entradas, el historial que reenvías y la salida solicitada. Añade seguimiento por usuario y presupuestos en tu aplicación: imprimir usage no impone un tope de gasto.
Los límites de concurrencia se aplican por cuenta, según la documentación de límites de DeepSeek. Crear otra clave dentro de la misma cuenta no crea una cuota independiente. Los valores de 512 tokens y 60 segundos de estos archivos son ajustes de la demostración.
Qué cambiar antes de conectarlo a una aplicación web
Mantén la llamada a DeepSeek en tu backend. El navegador debe comunicarse con una ruta de tu aplicación, mientras la clave permanece en el servidor. El SDK advierte sobre la exposición de credenciales en el navegador.
- Controla el acceso: autentica y autoriza a quien usa tu endpoint.
- Valida las entradas: acepta los campos previstos y limita el tamaño de los mensajes.
- Establece cuotas: controla frecuencia, concurrencia y presupuesto por usuario.
- Gestiona las interrupciones: cancela el trabajo asociado cuando corresponda y comunica si una respuesta quedó incompleta.
- Reduce los registros: conserva datos operativos útiles sin guardar claves ni conversaciones sensibles por defecto.
Estos archivos son ejemplos de consola. Reutilizar el cliente en Express o en una ruta de servidor de Next.js es un paso posterior; un endpoint público necesita sus propios controles. No coloques DEEPSEEK_API_KEY en variables públicas del frontend ni actives dangerouslyAllowBrowser para evitar configurar el backend.
¿Puedo usar fetch o Responses API?
Usar fetch sin el SDK
Sí. Node.js dispone de fetch para realizar la solicitud HTTP directamente. En ese caso debes preparar las cabeceras de autenticación, el cuerpo JSON y la lectura de errores; en streaming también debes procesar SSE y sus fragmentos. El SDK reduce ese trabajo en los ejemplos de esta guía.
Usar el formato Responses
DeepSeek también documenta compatibilidad con Responses API. Es una alternativa con sus propios campos, eventos y limitaciones. Si la eliges, adapta todo el flujo de solicitud y lectura de respuesta: no combines los campos de Chat Completions con los de Responses ni presupongas que todas las funciones de OpenAI están disponibles.
Verificación de los ejemplos: se comprobaron localmente con Node.js 24 y el SDK indicado mediante respuestas simuladas, incluidos errores e interrupciones. No se ejecutaron solicitudes reales a DeepSeek ni se midieron costos o tiempos de respuesta del proveedor.
Última verificación: 10 de noviembre de 2026.

