Cada semana veo desarrolladores gastando presupuesto innecesario en llamadas a la API de Claude — ejecutando trabajos de clasificación de forma secuencial, sobrecargando el endpoint en tiempo real con cargas de trabajo de tipo batch, y preguntándose por qué su factura de infraestructura de IA sigue subiendo. La solución es la Message Batches API: envía hasta 10.000 prompts en una sola solicitud asíncrona y paga un 50% menos por token, según la documentación de Anthropic. No es una optimización menor — en una carga de trabajo masiva real, puede reducir tu gasto mensual en la API a la mitad.
Este tutorial recorre la implementación completa en Python y C# (.NET). Al final, sabrás cómo enviar un batch, hacer polling hasta que esté listo, descargar los resultados en formato JSONL y decidir cuándo tiene sentido usar batches en lugar de llamadas en tiempo real. Estos son los mismos patrones utilizados en pipelines de enriquecimiento de datos backend que disparan trabajos nocturnos en Claude y recogen los resultados a la mañana siguiente.
Si no has leído mis guías sobre prompt caching en la API de Claude (en inglés) o structured outputs (en inglés), ambas se combinan bien con el batching — puedes usar prompt caching y schemas de respuesta estructurada dentro de las solicitudes individuales de un batch para obtener reducciones de costo acumuladas. Señalaré los puntos de integración a medida que avancemos.
¿Qué es la Message Batches API de Claude?
La Message Batches API es un endpoint asíncrono diseñado para cargas de trabajo de alto volumen que no son sensibles al tiempo. En lugar de enviar una llamada a /v1/messages por vez y esperar cada respuesta, agrupas hasta 10.000 solicitudes en un solo payload de batch, lo envías y esperas los resultados — todo con un 50% de descuento en los tokens de entrada y salida, según la página de precios de Anthropic.
El tradeoff es la latencia. Las solicitudes de batch no retornan de inmediato; Anthropic las procesa de forma asíncrona y el tiempo de respuesta depende del tamaño del batch y la carga actual. Según la documentación, los batches vencen 29 días después de su creación, aunque el campo expires_at del objeto batch te da el límite exacto. En la práctica, los batches pequeños de unos pocos cientos de solicitudes suelen completarse en minutos; los trabajos más grandes tardan más. Para cualquier cosa orientada al usuario — un chatbot, una sugerencia de autocompletado en tiempo real, cualquier cosa que alguien esté observando — usa el endpoint estándar. Para pipelines programados, la batch API es la opción correcta.
Las cargas de trabajo donde esto destaca: clasificar tickets de soporte por categoría y urgencia, extraer campos estructurados de cientos de documentos, ejecutar análisis nocturnos de calidad de código en un repositorio, generar metadatos SEO para un catálogo de productos, o volver a generar embeddings cuando cambias de modelo. El patrón es siempre el mismo: un tipo de prompt aplicado a muchos inputs, donde puedes esperar los resultados. Si eso describe tu carga de trabajo, sigue leyendo.
Requisitos previos: Configurando tu entorno
Necesitarás una API key de Anthropic generada desde la consola en console.anthropic.com. Para Python, instala el SDK oficial: pip install anthropic. El SDK gestiona la autenticación, la serialización y los helpers de polling. También expone un namespace client.messages.batches con métodos dedicados para cada operación de batch. Para .NET, llamarás directamente a la REST API via HttpClient — lo muestro en detalle en una sección específica más adelante.
Para este tutorial uso Claude Haiku (claude-haiku-4-5) — el modelo de Claude más rápido y económico, y la opción correcta para tareas de clasificación, extracción y etiquetado donde no necesitas el razonamiento profundo de Sonnet u Opus. Si tu caso de uso involucra revisión de código compleja o análisis de múltiples pasos, sube a claude-sonnet-4-5. La batch API funciona de forma idéntica con cualquier modelo de Claude; simplemente cambia el string del modelo.
Una nota práctica sobre la configuración: si ejecutas estos scripts en un servidor local o en tu homelab, guarda tu API key en una variable de entorno — nunca en el código fuente. Para trabajos de batch específicamente, los archivos de resultados pueden llegar a varios megabytes de JSONL dependiendo del tamaño del batch y la longitud de las respuestas, así que asegúrate de tener suficiente espacio en disco. Si quieres una máquina compacta y silenciosa para ejecutar trabajos programados en Python, el Beelink SER5 Mini PC (~$299) maneja bien este tipo de carga — es la misma plataforma que uso para correr Proxmox y Ollama.
Tu primera solicitud batch
Cada solicitud en un batch recibe un custom_id — un string que defines tú, de hasta 64 caracteres — para que puedas mapear los resultados a tus inputs originales sin necesidad de una tabla de búsqueda separada. El campo params acepta exactamente los mismos argumentos que una llamada estándar a /v1/messages: model, max_tokens y messages. Aquí tienes un ejemplo completo que clasifica tickets de soporte:
import anthropic
client = anthropic.Anthropic() # lee ANTHROPIC_API_KEY del entorno
tickets = [
{"id": "ticket-001", "text": "App crashes on iOS 18 when I tap the login button"},
{"id": "ticket-002", "text": "How do I export my data as a CSV?"},
{"id": "ticket-003", "text": "Billing charged twice this month, need a refund"},
# ... hasta 10.000 items
]
requests = []
for ticket in tickets:
requests.append(
anthropic.types.message_create_params.MessageCreateParamsNonStreaming(
custom_id=ticket["id"],
params={
"model": "claude-haiku-4-5",
"max_tokens": 64,
"messages": [
{
"role": "user",
"content": (
"Classify this support ticket into exactly one category: "
"Bug, Feature Request, or Billing. "
"Reply with only the category name.\n\n"
f"Ticket: {ticket['text']}"
),
}
],
},
)
)
batch = client.messages.batches.create(requests=requests)
print(f"Batch ID: {batch.id}")
print(f"Status: {batch.processing_status}") # "in_progress"
# Guarda el ID — lo necesitarás para hacer polling y recuperar los resultados
with open("batch_id.txt", "w") as f:
f.write(batch.id)
La respuesta llega casi de inmediato con un batch.id y un processing_status de "in_progress". El batch ahora está en cola para procesamiento asíncrono. Asegúrate de persistir el batch.id — sin él no puedes recuperar los resultados, y el SDK no mantiene estado local entre ejecuciones del script. Un archivo de texto simple funciona para trabajos puntuales; para uso en producción lo guardo en una tabla de base de datos junto con los metadatos del trabajo.
Algunas notas sobre la construcción del batch: cada custom_id debe ser único dentro del batch. Usa la clave primaria de tu base de datos, un hash del archivo, o cualquier identificador estable — el objetivo es evitar ambigüedades al correlacionar los resultados. Según la documentación de Anthropic, el payload del batch también tiene límites de tamaño total, por lo que prompts individuales muy largos pueden reducir el número de items que caben en un solo batch. Ante la duda, prueba primero con un batch pequeño y luego escala.
Polling y descarga de resultados
El procesamiento de batches es asíncrono, así que haces polling sobre el batch hasta que processing_status cambia a "ended". El objeto batch también expone un desglose de request_counts — processing, succeeded, errored, canceled — para que puedas monitorear el progreso en batches grandes. Aquí tienes un script completo de polling y descarga de resultados:
import anthropic
import time
client = anthropic.Anthropic()
with open("batch_id.txt") as f:
batch_id = f.read().strip()
# Polling hasta que el batch esté listo
while True:
batch = client.messages.batches.retrieve(batch_id)
counts = batch.request_counts
print(
f"Status: {batch.processing_status} | "
f"Processing: {counts.processing} | "
f"Succeeded: {counts.succeeded} | "
f"Errored: {counts.errored}"
)
if batch.processing_status == "ended":
break
time.sleep(30) # polling cada 30 segundos
# Descargar resultados en streaming como JSONL
print("Descargando resultados...")
results = {}
for result in client.messages.batches.results(batch_id):
if result.result.type == "succeeded":
text = result.result.message.content[0].text.strip()
results[result.custom_id] = text
elif result.result.type == "errored":
results[result.custom_id] = f"ERROR: {result.result.error.type}"
for ticket_id, category in results.items():
print(f" {ticket_id}: {category}")
El método client.messages.batches.results() hace streaming del archivo JSONL y devuelve un objeto de resultado tipado por solicitud. Siempre verifica result.result.type: un valor de "errored" significa que esa solicitud específica falló (típicamente por filtrado de contenido o un prompt mal formado), pero el resto del batch sigue procesándose normalmente. El código de producción debe manejar los fallos parciales con elegancia — registrar errores, marcar los registros afectados para revisión manual y continuar procesando los resultados exitosos.
Para batches grandes, haz streaming de los resultados directamente a un archivo o base de datos en lugar de acumularlos todos en un dict de Python. El stream JSONL puede ser considerable para respuestas largas, y cargar todo en memoria de una vez causará problemas a escala. Si combinas batches con la guía de structured outputs (en inglés), agrega un modelo Pydantic para parsear el texto JSON de cada resultado en objetos tipados mientras haces el streaming — manejo de resultados limpio y con tipos, sin pasos de post-procesamiento.
Implementación en .NET (C#)
Si tu backend corre en .NET — como el mío para la mayoría de los proyectos de clientes — llamas a la Message Batches API directamente mediante HttpClient. Aún no existe un SDK oficial de Anthropic para .NET con soporte nativo de batches, por lo que el enfoque REST te da el mayor control. Lo más importante: la batches API requiere un header anthropic-beta en cada solicitud, según la documentación.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")!;
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", apiKey);
http.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
http.DefaultRequestHeaders.Add("anthropic-beta", "message-batches-2024-09-24");
var tickets = new[]
{
new { Id = "ticket-001", Text = "App crashes on iOS 18 when I tap the login button" },
new { Id = "ticket-002", Text = "How do I export my data as a CSV?" },
};
var requests = tickets.Select(t => new
{
custom_id = t.Id,
@params = new
{
model = "claude-haiku-4-5",
max_tokens = 64,
messages = new[]
{
new
{
role = "user",
content = $"Classify this ticket into Bug, Feature Request, or Billing. " +
$"Reply with only the category name.\n\nTicket: {t.Text}"
}
}
}
}).ToArray();
var payload = JsonSerializer.Serialize(new { requests });
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await http.PostAsync(
"https://api.anthropic.com/v1/messages/batches", content);
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine(body); // {"id":"msgbatch_...","processing_status":"in_progress",...}
El header anthropic-beta: message-batches-2024-09-24 es obligatorio en todas las solicitudes relacionadas con batches — create, retrieve, results y cancel. Olvidarlo devuelve un error 400 que es fácil de malinterpretar como un problema del payload. Configúralo en los DefaultRequestHeaders de tu HttpClient una sola vez y todas las llamadas posteriores a los endpoints de batches lo llevarán automáticamente.
Para hacer polling en un servicio .NET, los background jobs de Hangfire funcionan bien. Crea un job que recupere el estado del batch con un horario programado, y cuando processing_status sea "ended", descarga el endpoint de resultados línea por línea con StreamReader. Deserializa cada línea JSONL en una clase tipada con System.Text.Json. Para la recuperación de resultados: GET https://api.anthropic.com/v1/messages/batches/{id}/results con los mismos headers. Si estás integrando esto en una API .NET más amplia, los patrones de la guía de tool use en la API de Claude (en inglés) aplican directamente — puedes componer llamadas con tool use dentro de solicitudes batch de la misma forma que lo harías en tiempo real.
Casos de uso reales para desarrolladores
El caso de uso más común en la práctica es el enriquecimiento masivo de datos. Tienes una tabla de base de datos con miles de filas — descripciones de productos, reseñas de clientes, tickets de soporte, posts de blog — y necesitas agregar un nuevo campo derivado del texto: una etiqueta de categoría, una puntuación de sentimiento, un tag de tema, un resumen de una frase. Ejecutar esto secuencialmente al precio de la API en tiempo real es lento y caro. Envíalos todos como un batch durante la noche, recoge los resultados por la mañana y haz una actualización masiva en SQL. El patrón operativo es limpio y el 50% de descuento lo hace económicamente viable a escala.
El análisis nocturno de calidad de código es otro patrón que vale la pena implementar. Configura un cron job (o un workflow programado en n8n) que obtenga todos los archivos modificados en las últimas 24 horas de tu repositorio Git, envíe cada uno a Claude con un prompt de revisión, y escriba los resultados en un dashboard o los publique como comentarios de PR. Al precio de batch con Haiku, puedes cubrir una base de código considerable sin que el costo de la API sea una preocupación. Combínalo con schemas de output estructurado y obtienes niveles de severidad legibles por máquina y referencias de líneas específicas que se integran directamente con tus herramientas de CI.
La localización de contenido y la generación de metadatos SEO son otros casos donde el batching rinde beneficios rápidamente. Si estás traduciendo un catálogo de productos o generando meta títulos y descripciones para cientos de páginas, la batch API convierte lo que sería un trabajo secuencial de varias horas en un envío de tipo fire-and-forget. Para los patrones de ingeniería más amplios detrás de construir este tipo de pipelines de procesamiento de IA a escala, AI Engineering de Chip Huyen (~$50) cubre las decisiones de diseño de sistemas con claridad — es uno de los libros más útiles sobre arquitectura de LLMs en producción disponibles actualmente, según reseñas de la comunidad de ML engineering. Para una mirada más profunda al lado de la implementación práctica, Hands-On Large Language Models de Jay Alammar (~$60) lo complementa bien.
Análisis de costos: cuándo vale la pena usar batches
Según los precios publicados por Anthropic, las Message Batches se facturan al 50% de las tarifas estándar de tokens de entrada y salida. Usando Claude Haiku como ejemplo: los tokens de entrada en la API estándar cuestan $0,80 por millón, por lo que en batch cuestan $0,40. Los tokens de salida pasan de $2,00 a $1,00 por millón. Para los modelos Claude Sonnet el descuento es proporcionalmente igual. Si procesas 1 millón de tokens de entrada al mes en cargas de trabajo de batch, eso es un ahorro de $400/mes en Haiku — significativamente más en Sonnet. A escala real, esto importa.
El framework de decisión es simple: si tu workflow está programado o es disparado por un pipeline de datos (no por un usuario esperando una respuesta), es un candidato fuerte para batch. Si alguien está mirando un spinner, usa tiempo real. El punto de equilibrio en términos de tolerancia a la latencia suele estar entre uno y cinco minutos — si puedes diseñar tu workflow para aceptar ese retraso, casi siempre puedes hacer batch. Trabajos nocturnos, reportes semanales, pipelines de enriquecimiento post-evento, hooks de CI que se ejecutan después de que se fusiona un PR — todos estos encajan naturalmente.
No subestimes tampoco el argumento de la simplicidad operativa. Un workflow de batch es una llamada HTTP para enviar, unas pocas llamadas de polling para verificar el estado, y una descarga para recoger los resultados JSONL. Compara eso con gestionar concurrencia, rate limits, backoff exponencial y lógica de reintentos para miles de llamadas secuenciales en tiempo real. El patrón de batching es arquitectónicamente más simple y resistente para cargas de trabajo masivas. Si un batch falla a mitad del proceso, las solicitudes completadas aún devuelven sus resultados — solo reintetas las que dieron error.
Limitaciones y puntos a tener en cuenta
La restricción más importante que debes internalizar: las solicitudes batch no soportan streaming. Obtienes la respuesta completa de cada item en el batch cuando todo el batch termina — sin output carácter a carácter, sin resultados incrementales a medida que se completan las solicitudes individuales. Esto es por diseño dado la arquitectura asíncrona, pero significa que batch nunca es la herramienta correcta para algo interactivo con el usuario. Si estás construyendo una funcionalidad de UI, usa tiempo real. El batching es un patrón de backend.
Según la documentación de Anthropic, los batches individuales están limitados a 10.000 solicitudes, y el tamaño total del payload también tiene límites. Para cargas de trabajo muy grandes, divide tu dataset en múltiples batches y haz seguimiento con una tabla de estado simple — columnas para batch_id, status, submitted_at, item_count y results_fetched. Puedes cancelar un batch en progreso via POST /v1/messages/batches/{id}/cancel si detectas un bug en tu template de prompt antes de que termine — esto es mucho más barato que pagar por miles de resultados incorrectos.
Un detalle sobre rate limits que confunde a la gente: aunque el procesamiento de batches es asíncrono, hay límites en cuántos batches puedes tener simultáneamente en vuelo, según el tier de tu cuenta. Si envías muchos batches en rápida sucesión (por ejemplo, dividiendo un dataset muy grande en chunks), puedes alcanzar un límite en la creación concurrente de batches. Verifica los límites de tu tier en la consola de Anthropic antes de diseñar un workflow que envíe docenas de batches simultáneamente. Para la mayoría de los casos de uso — un trabajo nocturno, un workflow programado — esto no es un problema, pero vale la pena saberlo antes de arquitectar algo a mayor volumen.
¿Quieres Claude bien configurado para tu flujo de trabajo?
Reserva una llamada 1 a 1 para configurar Claude: 60 minutos, en vivo, en inglés o en español, grabada para ti. $127. Reserva la llamada →
O empieza con el Claude Prompt Pack: 50 prompts bilingües, $17, descarga inmediata.