DeepSeek Responses API: cómo usarla y mantener el historial

DeepSeek Responses API permite generar respuestas mediante POST https://api.deepseek.com/responses. La petición utiliza input y puede incluir instructions; la respuesta organiza texto, razonamiento y herramientas en una lista de elementos llamada output. La referencia oficial de Responses describe este contrato.

Los modelos actuales deepseek-flash y deepseek-v4-pro admiten esta interfaz, según la tabla oficial de modelos. Para mantener una conversación, tu aplicación debe reenviar el historial: DeepSeek no recupera el turno anterior mediante previous_response_id.

En esta guía harás una primera solicitud con cURL y construirás una conversación de dos turnos con Node.js. El objetivo es verificar la respuesta, conservar el contexto y reconocer qué debes adaptar si vienes de Chat Completions.

Documentación oficial de DeepSeek Responses API con el endpoint POST /responses y los parámetros model e input.

Primera llamada a DeepSeek Responses API

Obtén una clave desde la plataforma enlazada en la guía oficial de primera llamada. Los siguientes comandos usan Bash, disponible en Linux, macOS y entornos como WSL. Sustituye el valor de demostración por tu clave:

export DEEPSEEK_API_KEY="tu_clave_de_deepseek"

curl --silent --show-error --fail-with-body --max-time 45 \
  https://api.deepseek.com/responses \
  -H "Authorization: Bearer $DEEPSEEK_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @- <<'JSON'
{
  "model": "deepseek-flash",
  "instructions": "Responde en español latinoamericano neutro.",
  "input": "Explica en una frase para qué sirve una API.",
  "reasoning": { "effort": "none" },
  "max_output_tokens": 256,
  "stream": false
}
JSON

El comando imprime el objeto JSON completo. La opción --fail-with-body permite detectar errores HTTP conservando su contenido; no verifica el campo status del JSON. El plazo de 45 segundos es una elección local para esta prueba.

El ejemplo desactiva Thinking con reasoning.effort: "none". Los controles de esta interfaz se detallan en la guía de Thinking. Mantén la clave fuera de capturas, repositorios públicos y código que se ejecute en el navegador.

Si utilizas un SDK compatible, configura la URL base como https://api.deepseek.com y llama a responses.create(). La URL completa con /responses corresponde a la petición HTTP directa que acabas de ver.

Cómo leer output y comprobar que la respuesta terminó

Una respuesta HTTP recibida correctamente todavía puede contener una generación incompleta. Comprueba el estado del objeto antes de mostrar el resultado como terminado.

Este es un fragmento ilustrativo de la estructura, con otros campos omitidos:

{
  "status": "completed",
  "output": [
    {
      "type": "message",
      "role": "assistant",
      "content": [
        {
          "type": "output_text",
          "text": "Una API permite que dos aplicaciones intercambien datos y operaciones."
        }
      ]
    }
  ]
}

Recorre los elementos message de output y lee los campos text de sus partes output_text. Un elemento reasoning puede aparecer antes, por lo que fijar la lectura en output[0] resulta frágil.

La guía oficial muestra response.output_text al utilizar un SDK. Ese atajo no debe suponerse en el JSON que obtienes con fetch: el programa siguiente extrae el texto de los elementos originales.

Ejemplo JSON de DeepSeek Responses API con estado completed y texto de salida en output_text.

Además de leer el texto, decide qué cuenta como éxito para tu aplicación. Una contestación vacía, un mensaje incompleto o una herramienta pendiente requieren un tratamiento distinto de una respuesta de texto terminada.

Ejemplo en Node.js: una conversación de dos turnos

Prepara el archivo y la clave

Usa Node.js 24 o posterior. El ejemplo utiliza fetch y AbortSignal nativos, sin instalar dependencias. En la carpeta del proyecto crea un archivo .env:

DEEPSEEK_API_KEY=tu_clave_de_deepseek

Agrega .env a .gitignore y conserva la clave solo en tu entorno local o servidor. Guarda el siguiente programa como conversacion.mjs.

Valida la respuesta antes de incorporarla al historial

La primera pregunta presenta un proyecto ficticio y pide una confirmación breve. La segunda solicita su nombre. Así puedes comprobar que el programa reenvía el mensaje original del usuario, incluso si la primera respuesta solo dice “Listo”.

const ENDPOINT = "https://api.deepseek.com/responses";

function extraerTexto(data) {
  if (data?.status !== "completed") {
    const motivo = data?.incomplete_details?.reason ??
      data?.error?.code ?? data?.status ?? "estado ausente";
    throw new Error("Respuesta no completada: " + motivo);
  }
  if (!Array.isArray(data.output)) {
    throw new Error("La respuesta no contiene un array output");
  }

  const textos = [];
  for (const item of data.output) {
    if (item?.type === "reasoning") continue;
    if (
      item?.type !== "message" || item.role !== "assistant" ||
      !Array.isArray(item.content)
    ) throw new Error("Este ejemplo solo procesa respuestas de texto");

    if (item.status && item.status !== "completed") {
      throw new Error("Se recibió un mensaje incompleto");
    }

    const partes = [];
    for (const parte of item.content) {
      if (parte?.type !== "output_text" || typeof parte.text !== "string") {
        throw new Error("Se recibió contenido no esperado");
      }
      partes.push(parte.text);
    }
    textos.push(partes.join(""));
  }

  const texto = textos.join("\n").trim();
  if (!texto) throw new Error("La respuesta no contiene texto visible");
  return texto;
}

async function main() {
  const apiKey = process.env.DEEPSEEK_API_KEY;
  if (!apiKey) throw new Error("Falta DEEPSEEK_API_KEY");

  const historial = [];
  const signal = AbortSignal.timeout(45_000);
  const preguntas = [
    "El proyecto se llama Faro. Confirma únicamente con la palabra Listo.",
    "¿Cómo se llama el proyecto que mencioné?",
  ];

  for (const pregunta of preguntas) {
    signal.throwIfAborted();
    const usuario = {
      type: "message",
      role: "user",
      content: [{ type: "input_text", text: pregunta }],
    };
    const input = [...historial, usuario];

    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: {
        Authorization: "Bearer " + apiKey,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "deepseek-flash",
        instructions: "Responde en español latinoamericano neutro, de forma breve.",
        input,
        reasoning: { effort: "none" },
        max_output_tokens: 256,
        stream: false,
      }),
      signal,
    });

    if (!response.ok) {
      await response.body?.cancel();
      throw new Error("DeepSeek respondió HTTP " + response.status);
    }

    const data = await response.json();
    signal.throwIfAborted();
    const texto = extraerTexto(data);

    // Guarda los items originales solo después de aceptar la respuesta.
    historial.push(usuario, ...data.output);
    console.log("\nUsuario: " + pregunta);
    console.log("Asistente: " + texto);
  }
}

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

Si ejecutaste el ejemplo de cURL en la misma terminal Bash, la variable exportada tiene prioridad sobre .env. Para usar la clave del archivo, ejecuta unset DEEPSEEK_API_KEY antes de iniciar Node.js.

Ejecuta el programa desde la carpeta donde guardaste los archivos:

node --env-file=.env conversacion.mjs

La opción --env-file carga la clave en process.env. Al ejecutar el programa con una clave válida, se envían solicitudes a DeepSeek y se utiliza el saldo de esa cuenta.

La prueba pretende obtener una confirmación en el primer turno y recuperar “Faro” en el segundo. La redacción puede variar. Son resultados esperados para el ejemplo, no una transcripción de solicitudes realizadas al servicio.

Qué se envía en la segunda petición

El segundo input contiene, en este orden:

  1. El primer mensaje del usuario, donde se menciona el proyecto.
  2. Todos los elementos aceptados de output de la primera respuesta, en su orden original.
  3. La nueva pregunta del usuario.

La instrucción general se envía nuevamente en instructions. El programa conserva por separado el historial completo y el texto que imprime: los elementos de razonamiento no se mezclan con el texto visible, pero permanecen en el historial cuando aparecen.

Guardar solo “Listo” como respuesta anterior perdería el dato que necesita la segunda pregunta. Este caso también sirve como prueba de regresión cuando cambies cómo guardas o recuperas conversaciones.

El historial vive en memoria y se pierde al terminar el proceso. Si tu producto necesita recuperar conversaciones después, implementa su almacenamiento por sesión y evita mezclar mensajes de distintos usuarios.

Qué controla y qué no comprueba el ejemplo

El programa acepta únicamente respuestas de texto completas. Detiene la ejecución ante errores HTTP, estados no completados, contenido vacío o elementos inesperados, como una llamada a herramienta. Actualiza el historial después de validar la respuesta.

Tiene dos turnos, un presupuesto de 256 tokens de salida por petición y un plazo compartido de 45 segundos. Son ajustes del ejemplo. No incluye reintentos automáticos ni un ejecutor de herramientas.

Estas validaciones comprueban el intercambio y la estructura, pero no garantizan que el modelo haya entendido correctamente la pregunta. Para tu aplicación, añade comprobaciones sobre la tarea concreta, como verificar que recupera el dato esperado.

El código se comprobó en Node.js 24.22.1 con respuestas simuladas: historial de dos turnos, elementos de razonamiento, texto fragmentado, respuestas incompletas, errores HTTP y cancelación. No se realizaron llamadas reales a DeepSeek durante esa comprobación.

DeepSeek Responses API vs Chat Completions

Las dos interfaces requieren adaptar el cuerpo de la petición y la lectura del resultado. Esta comparación relaciona los campos de Chat Completions con los usados en el ejemplo:

NecesidadChat CompletionsResponses API
Enviar la conversaciónmessagesinput
Dar instrucciones generalesMensaje systeminstructions o mensaje system
Leer texto en HTTP directochoices[].message.contentPartes output_text de elementos message
Limitar la salidamax_tokensmax_output_tokens
Desactivar Thinkingthinking: {"type":"disabled"}reasoning: {"effort":"none"}
Verificar la terminaciónfinish_reasonstatus

Una migración debe revisar cada lugar donde el programa construye una solicitud, lee una respuesta o actualiza el historial. Cambiar únicamente el nombre del método deja sin resolver esas diferencias.

Responses encaja cuando tu cliente ya utiliza elementos de entrada y salida o necesitas sus eventos de streaming. Si evalúas una migración, empieza por un caso de texto de dos turnos y después incorpora cada herramienta o modalidad que use tu producto.

Codex es uno de los clientes que puede consumir este formato, como muestra la integración oficial de DeepSeek con Codex. Los ejemplos de esta página acceden directamente al endpoint y funcionan sin ese cliente.

Compatibilidad: funciones disponibles y parámetros ignorados

Thinking, JSON Schema, imágenes y herramientas

Para activar razonamiento, reasoning.effort admite low, high y max; none lo desactiva. Thinking está activado de forma predeterminada. El presupuesto de max_output_tokens incluye tanto razonamiento como texto visible.

Si lo activas, revisa el presupuesto reducido del ejemplo. Conserva los elementos originales de razonamiento que deban volver en el historial; el formato de Responses los representa como elementos independientes.

Para salida estructurada, text.format admite json_object y json_schema. Con json_schema debes proporcionar name y schema, según la referencia de la interfaz. Define después cómo validarás los datos antes de utilizarlos en tu aplicación.

Las funciones personalizadas colocan name y parameters directamente en el elemento de tools. Sus resultados se devuelven como function_call_output con el call_id correspondiente. Tu aplicación ejecuta la función, siguiendo el principio explicado en Tool Calls.

Si agregas funciones al programa anterior, necesitas implementar esa ejecución y devolver todos los resultados requeridos antes de continuar. Un estado completed puede contener una solicitud de herramienta: eso completa la generación del modelo, pero todavía queda trabajo para tu aplicación.

La guía de Vision documenta imágenes con deepseek-flash, mediante partes input_image. Puedes proporcionar una URL, datos base64 o un file_id de imagen. Esta capacidad no equivale a admitir archivos PDF mediante input_file.

La búsqueda integrada web_search está disponible con deepseek-v4-pro y se ejecuta en el servidor; con Flash se ignora. Comprueba la combinación de modelo y función que realmente necesita tu aplicación.

Ajustes que no conservan el comportamiento de otros proveedores

La tabla oficial de compatibilidad identifica varias diferencias que afectan a una migración:

Campo o funciónComportamiento documentado
previous_response_id, conversationSin continuidad gestionada por esos campos. Reenvía el historial.
store, backgroundNo admitidos; la respuesta lleva store: false.
Rol developerSe trata como user. Usa instructions o system para instrucciones generales.
parallel_tool_calls, max_tool_callsSe ignoran. Controla las ejecuciones en tu aplicación.
truncationNo admitido; exceder el contexto devuelve HTTP 400.
file_search, code_interpreter, mcpHerramientas integradas ignoradas.
Compatibilidad de previous_response_id, conversation, store y background en DeepSeek Responses API.

Durante una migración, prueba cada capacidad mediante un resultado observable. Por ejemplo, comprueba que una consulta usa el contexto previo y que una operación solicitada realmente se ejecuta. Un HTTP 200 no demuestra por sí solo que se haya aplicado cada parámetro enviado.

La condición stateless describe la continuidad de la conversación. Por sí sola, ni esa condición ni store: false establecen una garantía de retención cero para todos los datos del servicio.

Streaming: eventos de texto y finalización

Con stream: true, Responses utiliza eventos SSE. Para el texto incremental escucha response.output_text.delta. La terminación se indica mediante response.completed, response.incomplete o response.failed; no hay un marcador data: [DONE].

En el flujo HTTP, el nombre puede verse en la línea event:; el objeto JSON transportado en data: utiliza type. El diseño de tu lector debe distinguir la envoltura SSE del JSON que contiene.

Ejemplo oficial de streaming en DeepSeek Responses API con stream=True y response.output_text.delta.
  • Muestra los fragmentos de texto como contenido provisional mientras la respuesta continúa.
  • Al recibir el evento terminal, inspecciona su objeto response y decide si puedes aceptar el resultado.
  • Si termina incompleta o falla, conserva esa condición en la interfaz. No presentes el texto parcial como una tarea finalizada.
  • Si la conexión se corta antes del evento terminal, registra una interrupción; el cierre de la conexión no prueba que la generación haya terminado correctamente.

Un lector HTTP debe reconstruir eventos completos aunque la red los divida en varios fragmentos. El estándar SSE define la separación por líneas y el envío de eventos al encontrar una línea vacía. Analizar cada fragmento de red como un JSON independiente puede romper un flujo válido.

El programa de Node.js anterior usa response.json() porque trabaja sin streaming. Para activarlo necesitas sustituir esa lectura por un lector SSE; cambiar solamente el valor de stream no basta.

Para seguimiento de consumo, registra usage de la respuesta final. En Responses se utilizan input_tokens y output_tokens; relaciona ese consumo con las tarifas oficiales vigentes si necesitas calcular costos.

Errores frecuentes y cómo resolverlos

Empieza por distinguir un fallo HTTP de una respuesta recibida cuyo estado impide aceptarla. Los códigos HTTP de la API se describen en la referencia oficial de errores.

Tabla oficial de errores HTTP de DeepSeek API con códigos, causas y soluciones.
ProblemaComprobación y siguiente paso
output_text aparece como undefined.Si usas fetch, lee las partes de texto dentro de output. No dependas del atajo de un SDK.
status: "incomplete".Consulta incomplete_details.reason: puede indicar max_output_tokens o content_filter. Ajusta la siguiente solicitud según la causa.
status: "failed".Revisa error y registra un diagnóstico seguro. Mantén el turno fuera del historial aceptado hasta resolverlo.
El segundo turno pierde el contexto.Inspecciona el input enviado: debe contener los mensajes anteriores y sus salidas aceptadas, no solo un identificador.
La instrucción pierde prioridad.Comprueba si migraste un mensaje developer. En esta interfaz se trata como un mensaje de usuario.
Cambiar un parámetro no produce efecto.Revisa si figura como ignorado o si depende del modelo o del modo de razonamiento.
La lectura del stream se queda esperando.Comprueba los eventos terminales de Responses y el lector SSE. No esperes [DONE].
HTTP 401 o 402.Revisa la clave para 401 y el saldo para 402.
HTTP 400 o 422.Comprueba el cuerpo, los parámetros y el tamaño del contexto; utiliza el detalle del error para localizar el campo problemático.
HTTP 429, 503 o timeout.Revisa carga, disponibilidad y plazos. Si incorporas reintentos, limita su número y conserva el estado de la operación.

Antes de dar por terminada la integración, prueba tres recorridos: una respuesta de texto válida, una continuación que dependa del primer mensaje del usuario y una respuesta incompleta que tu aplicación rechace correctamente. Después incorpora las capacidades adicionales de una en una, verificando su resultado real.

Última verificación de las fuentes: 10 de noviembre de 2026.