Ir al contenido principal
Batches API de Anthropic: 50% menos coste en tu agente

Batches API de Anthropic: 50% menos coste en tu agente

AI Integration
7 min readPor Daily Miranda Pardo

Tienes un pipeline que procesa 800 facturas al cierre del mes. Cada factura es una llamada al LLM. Lanzas 800 requests en tiempo real, implementas un semáforo para no explotar la tasa de llamadas, gestionas los 429s que igual llegan, y al final del mes miras la factura de Anthropic.

Lo que ves es correcto. Pero estás pagando el doble de lo que deberías.

La Batches API de Anthropic existe desde 2024 y la mayoría de equipos no la usa. No porque no funcione — porque no saben que existe o porque no entienden cuándo tiene sentido aplicarla.

Qué es la Batches API y por qué cambia el cálculo de costes

La API de mensajes estándar de Anthropic (POST /messages) procesa una sola request y devuelve la respuesta de forma síncrona. Pagas el precio completo por token de entrada y token de salida, en tiempo real.

La Batches API (POST /messages/batches) acepta hasta 10.000 requests en una sola llamada HTTP, las procesa de forma asíncrona en la infraestructura de Anthropic, y te devuelve los resultados cuando terminan. A cambio:

  • 50% de descuento en el precio por token (tanto entrada como salida)
  • Sin límites de tasa de tu lado — Anthropic gestiona la cola
  • Tiempo de procesamiento garantizado: hasta 24 horas (en práctica suele ser menos de 1 hora para batches pequeños)
  • Retry nativo: si un request falla, Anthropic lo reintenta sin que hagas nada

El trade-off es claro: cambias latencia en tiempo real por precio. Para cualquier tarea que no necesite respuesta inmediata — procesamiento nocturno, informes, clasificación de documentos, enriquecimiento de datos — es un cambio que no tiene discusión.

Cuándo usar Batch vs Real-time

La regla es simple: si el usuario está esperando la respuesta, usa tiempo real. Si no, usa Batch.

Usar Batches API:

  • Procesamiento de documentos al final del día (facturas, contratos, informes)
  • Clasificación y etiquetado de datos históricos
  • Generación de resúmenes de conversaciones pasadas
  • Análisis de sentimiento de tickets de soporte del día anterior
  • Enriquecimiento de registros en base de datos en background

Usar API estándar:

  • Chat con usuario en tiempo real
  • Agentes que responden a eventos inmediatos
  • Validación de formularios con IA
  • Generación de contenido mientras el usuario espera

Si tu pipeline tiene ambos casos, los separas: real-time para lo urgente, Batch para lo que puede esperar.

Implementación en TypeScript

La Batches API tiene tres operaciones: crear el batch, consultar su estado y recuperar los resultados.

Crear el batch

import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

interface DocumentRequest {
  id: string;
  content: string;
}

async function createDocumentBatch(documents: DocumentRequest[]) {
  const requests = documents.map((doc) => ({
    custom_id: doc.id, // tu identificador, lo recibes en los resultados
    params: {
      model: "claude-haiku-4-5-20251001", // usa Haiku para tareas de clasificación: 4x más barato
      max_tokens: 512,
      system:
        "Clasifica el documento en una de estas categorías: FACTURA, CONTRATO, INFORME, OTRO. Responde solo con la categoría.",
      messages: [
        {
          role: "user",
          content: doc.content,
        },
      ],
    },
  }));

  const batch = await anthropic.messages.batches.create({ requests });

  console.log(`Batch creado: ${batch.id}`);
  console.log(`Estado: ${batch.processing_status}`);
  console.log(`Requests: ${batch.request_counts.processing}`);

  return batch.id;
}

El custom_id es clave: te permite hacer el join entre el resultado y tu dato original sin depender del orden de respuesta.

Monitorizar el estado

async function waitForBatch(batchId: string): Promise<void> {
  const POLL_INTERVAL_MS = 30_000; // 30 segundos entre checks
  const MAX_WAIT_MS = 24 * 60 * 60 * 1000; // 24 horas máximo
  const startTime = Date.now();

  while (Date.now() - startTime < MAX_WAIT_MS) {
    const batch = await anthropic.messages.batches.retrieve(batchId);

    if (batch.processing_status === "ended") {
      console.log(`Batch completado en ${(Date.now() - startTime) / 1000}s`);
      console.log(
        `Éxito: ${batch.request_counts.succeeded} / ${batch.request_counts.processing + batch.request_counts.succeeded + batch.request_counts.errored}`
      );
      return;
    }

    if (batch.processing_status === "canceling") {
      throw new Error("Batch cancelado");
    }

    console.log(
      `Procesando: ${batch.request_counts.processing} pendientes...`
    );
    await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
  }

  throw new Error("Timeout esperando el batch");
}

En producción, en lugar de un loop de polling activo, guarda el batchId en base de datos y dispáralo desde un cron job cada 10 minutos. El loop activo es útil solo en scripts puntuales o en local.

Recuperar y procesar los resultados

interface ClassificationResult {
  documentId: string;
  category: string | null;
  error: string | null;
}

async function processBatchResults(
  batchId: string
): Promise<ClassificationResult[]> {
  const results: ClassificationResult[] = [];

  for await (const result of await anthropic.messages.batches.results(
    batchId
  )) {
    if (result.result.type === "succeeded") {
      const content = result.result.message.content[0];
      results.push({
        documentId: result.custom_id,
        category:
          content.type === "text" ? content.text.trim() : null,
        error: null,
      });
    } else {
      results.push({
        documentId: result.custom_id,
        category: null,
        error: result.result.error.error.message,
      });
    }
  }

  return results;
}

El iterador asíncrono maneja la paginación por ti. Si el batch tiene 10.000 resultados, los recibirás en streaming sin cargar todo en memoria.

Patrón completo para producción: Batch Job nocturno

El caso de uso más común es un job que corre de noche y procesa los documentos del día. Con Next.js y tareas programadas, el flujo completo sería:

  1. 00:00 — Cron dispara el job: consulta en Supabase todos los documentos del día sin clasificar
  2. 00:01 — Crea el batch: manda los 800 documentos en una sola llamada HTTP
  3. 00:01 a ~01:00 — Polling o webhook: comprueba el estado del batch cada 10 minutos
  4. ~01:00 — Recupera resultados: actualiza la tabla de documentos con las clasificaciones
  5. 01:05 — Notifica: Slack/email con el resumen del procesamiento
// Guardamos el batchId en Supabase para que el cron lo recupere
async function saveBatchJob(batchId: string, documentIds: string[]) {
  await supabase.from("batch_jobs").insert({
    batch_id: batchId,
    document_ids: documentIds,
    status: "processing",
    created_at: new Date().toISOString(),
  });
}

// El cron de las 01:00 comprueba si el batch terminó
async function checkAndProcessBatch(jobId: string) {
  const { data: job } = await supabase
    .from("batch_jobs")
    .select("*")
    .eq("id", jobId)
    .single();

  const batch = await anthropic.messages.batches.retrieve(job.batch_id);

  if (batch.processing_status !== "ended") return; // todavía procesando

  const results = await processBatchResults(job.batch_id);

  // Upsert en bulk en lugar de 800 updates individuales
  await supabase.from("documents").upsert(
    results.map((r) => ({
      id: r.documentId,
      category: r.category,
      classified_at: new Date().toISOString(),
    }))
  );

  await supabase
    .from("batch_jobs")
    .update({ status: "completed" })
    .eq("id", jobId);
}

El cálculo de costes real

Tomemos un caso concreto. 800 documentos, 500 tokens de entrada por documento y 50 tokens de respuesta:

Tiempo real (Claude Haiku 4.5):

  • 800 × 500 tokens entrada = 400.000 tokens × $0.00080/1K = $0.32
  • 800 × 50 tokens salida = 40.000 tokens × $0.00400/1K = $0.16
  • Total: $0.48

Batches API (Claude Haiku 4.5 en batch):

  • Descuento del 50% en ambos
  • Total: $0.24

Son $0.24 de diferencia por ejecución. Si corre cada día del año: $87.60 de ahorro anual en un pipeline modesto. Si el volumen es mayor — 5.000 documentos diarios — son $547 menos al año, además de eliminar toda la lógica de rate limiting, reintentos y semáforos.

El código que no tienes que escribir también tiene valor.

Lo que cambia en tu arquitectura

Adoptar la Batches API no requiere refactorizar tu agente. Requiere separar dos conceptos que probablemente tenías mezclados:

  1. Las tareas que necesitan respuesta inmediata — siguen usando la API estándar
  2. Las tareas que pueden esperar — pasan a Batch, con polling o webhook, y un cron que las gestiona

Ese cambio conceptual — de "llamo al LLM y espero" a "encolo la tarea y proceso cuando esté" — es el mismo que separa un sistema que escala del que revienta cuando el volumen dobla.

Si estás construyendo pipelines de procesamiento documental, clasificación en batch o generación de informes nocturnos y quieres revisar la arquitectura antes de que la factura de API te dé la primera señal, en DAILYMP integramos este tipo de sistemas desde el diseño hasta el deploy.

Hablamos de cómo aplicar esto en tu empresa →

Compartir artículo

LinkedInXWhatsApp

¿Procesos repetitivos en tu empresa?

Descarga gratis el Mapa de Automatización IA — los 5 procesos que más tiempo roban y cómo resolverlos.

Sin spam. Solo el PDF. Puedes darte de baja cuando quieras.

Escrito por Daily Miranda Pardo

Ayudo a empresas a automatizar procesos, crear agentes IA y conectar sistemas inteligentes.