API em lote: quando vale processar tarefas que não precisam de resposta na hora?

API em lote da Anthropic para processar tarefas assíncronas sem urgência
Resposta rápida

A API em lote (Message Batches API) processa requisições de forma assíncrona com 50% de desconto sobre os tokens de entrada e de saída em relação à API síncrona. Cada lote aceita até 100.000 requisições ou 256 MB, expira 24 horas depois da criação e, segundo a documentação, a maioria termina em menos de 1 hora. Os resultados saem num arquivo .jsonl, sem ordem garantida, casados pelo campo custom_id, e ficam disponíveis por 29 dias. Vale quando ninguém está na tela esperando: classificação em massa, resumo de acervo, extração de campos de documentos

Fala aí, beleza? Tem uma conta que quase todo mundo paga sem perceber: preço de urgência por uma resposta que ninguém vai ler nos próximos minutos

Classificar 40 mil tickets antigos, resumir um acervo inteiro de artigos, extrair campos de milhares de documentos, enriquecer catálogo de produto

Esse tipo de trabalho quase sempre roda na API síncrona por HÁBITO, não por necessidade

E existe um caminho assíncrono para isso, com metade do preço: a Message Batches API processa as requisições em lote com 50% de desconto sobre os tokens de entrada e de saída, comparado à API síncrona de Mensagens

Mesmo modelo, mesmo prompt, metade da conta

O que muda é a forma de escrever o código, e é aí que mora o assunto deste post 🙂

Quais tarefas cabem no processamento em lote

O corte não é "é muita coisa?"

O corte é uma pergunta só: alguém está esperando essa resposta agora?

Se a resposta é não, cabe em lote. Se é sim, nem discute, fica no síncrono

Tarefas que cabem bem:

Formação Vibe Coding
Formação Recomendada

Formação Vibe Coding

Do Prompt ao Produto: Crie Software Real com IA

  • 474 aulas
  • 20 projetos
  • 39h 27min
  • classificação de base histórica (sentimento, tema, prioridade em cima do que já aconteceu)
  • resumo de acervo (artigos, transcrições, chamados antigos)
  • extração de campos de documentos em volume
  • enriquecimento de catálogo (descrição, tags, categoria)
  • avaliação de saídas em massa, quando tu roda uma bateria de testes em cima do que o modelo respondeu

E o contraponto, que é igualmente importante

Não cabe em lote: chat, autocomplete, busca conversacional, qualquer fluxo com um humano olhando o spinner girar

Não é questão de volume, é questão de espera. Um único pedido de chat não cabe em lote, e 100 mil resumos noturnos cabem numa boa

É o mesmo tipo de decisão de escolher ferramenta pela tarefa, igual saber quando fazer na mão sai mais rápido do que orquestrar tudo

Quanto muda no preço: síncrono x lote

Aqui é onde a conversa fica concreta

Preços por milhão de tokens na API:

ModeloEntrada (síncrono)Saída (síncrono)Entrada (lote)Saída (lote)
Claude Sonnet 5US$ 2US$ 10US$ 1US$ 5
Claude Opus 5US$ 5US$ 25US$ 2,50US$ 12,50
Claude Haiku 4.5US$ 1US$ 5US$ 0,50US$ 2,50

Uma nota que vale registrar: o preço de US$ 2 / US$ 10 do Sonnet 5 entrou como introdutório, e ficou confirmado como preço padrão

Ou seja, o reajuste para US$ 3 / US$ 15 que estava previsto não vai acontecer

E tem um empilhamento que muita gente ignora: a Message Batches API suporta prompt caching, e os descontos de cache e de lote se somam

Como o acerto de cache custa 10% do preço base de entrada, um job em lote com prompt estável na frente (instrução grande, esquema de saída, exemplos) fica mais barato ainda

Se o teu job repete o mesmo bloco de instruções em cada uma das 50 mil requisições, é exatamente esse o cenário

Como programar um job em lote passo a passo

O desenho muda: em vez de pedir e receber, tu envia, some, e volta depois pra buscar

  1. Monta o array requests, cada item com custom_id único e um objeto params. O params é o payload padrão da Messages API (model, max_tokens, messages), no formato não-streaming, e a criação é inline via client.messages.batches.create
import anthropic

client = anthropic.Anthropic()

linhas = carregar_tickets()  # sua fonte de dados

batch = client.messages.batches.create(
    requests=[
        {
            "custom_id": f"ticket-{linha['id']}",
            "params": {
                "model": MODEL_ID,  # o id do modelo que você já usa
                "max_tokens": 512,
                "messages": [
                    {"role": "user", "content": f"Classifique o ticket:\n{linha['texto']}"}
                ],
            },
        }
        for linha in linhas
    ]
)

print(batch.id)

O erro comum deste passo: gerar custom_id por índice do laço (item-0, item-1) sem amarrar no id real do teu banco. Depois vira um quebra-cabeça pra saber de quem era cada resposta

  1. Respeita o teto do lote: 100.000 requisições ou 256 MB, o que vier primeiro. Se a tua base é maior, fatia em vários lotes e guarda o id de cada um

O erro comum deste passo: contar só requisições e esquecer o peso. Prompt gordo com documento colado dentro estoura os 256 MB muito antes das 100 mil linhas

  1. Acompanha o processing_status. Ele começa em in_progress e passa para ended quando todas as requisições do lote terminam

O erro comum deste passo: tratar ended como "deu tudo certo". Ended diz que ACABOU, não que todas as requisições tiveram sucesso

  1. Lê o results_url. É um arquivo .jsonl, uma linha JSON válida por requisição, e ele só é preenchido depois que o processamento termina

O erro comum deste passo: ler o campo logo depois de criar o lote e concluir que a API falhou porque veio vazio

  1. Casa cada linha pelo custom_id. A ordem dos resultados não é garantida em relação à ordem que tu enviou
import json
import urllib.request

resultados = {}

with urllib.request.urlopen(results_url) as arquivo:
    for linha in arquivo:
        item = json.loads(linha)
        resultados[item["custom_id"]] = item["result"]

O erro comum deste passo, e esse é o clássico dos clássicos: assumir que a saída volta na ordem da entrada e fazer um zip entre entradas e saídas. Isso não estoura erro nenhum, só embaralha os teus dados em silêncio, que é bem pior

  1. Trata o campo result de cada requisição e confere o request_counts. Cada resultado indica se a requisição foi succeeded, errored, canceled ou expired, e o objeto request_counts traz os cinco contadores (succeeded, errored, canceled, expired, processing)

O erro comum deste passo: gravar tudo no banco assumindo sucesso e descobrir semanas depois que um pedaço da base ficou com campo vazio

Armadilhas do modo assíncrono e como evitar

Resultados fora de ordem

Sintoma: o resumo do documento A apareceu no registro do documento B

Causa: a ordem dos resultados não é garantida

Solução: casar SEMPRE por custom_id, nunca por índice

O results_url veio vazio

Sintoma: o campo não tem nada e o código quebra na hora de baixar

Causa: o processamento ainda não terminou

Solução: só tentar baixar quando o processing_status estiver em ended

Requisições marcadas como expired

Sintoma: parte do lote volta sem resposta

Causa: o lote expira e encerra o processamento 24 horas depois da criação

Solução: fatiar volume grande em lotes menores em vez de mandar um monstro único e torcer

Download que falha semanas depois

Sintoma: o job de reprocessamento não acha mais o arquivo

Causa: os resultados ficam disponíveis por 29 dias contados a partir da criação do lote. Depois disso o lote ainda é visível, mas os resultados não podem mais ser baixados

Solução: baixar e persistir o arquivo .jsonl no teu lado assim que o lote fecha, sem tratar a API como armazenamento de longo prazo

A fila anda mais devagar do que tu esperava

Sintoma: o lote de ontem à noite ainda está rodando de manhã

Causa: a documentação avisa que o processamento pode ficar mais lento conforme a demanda no momento e o volume de requisições enviado. Não existe SLA de agendamento além do teto de 24 horas

Solução: não prometer horário pra área de negócio. Prometer "até amanhã", que é o que a API garante

Estouro de limite

Sintoma: a criação do lote é recusada por limite de taxa

Causa: as requisições dentro de um lote contam para os rate limits da conta, com um grupo de limite específico para lotes (requisições enfileiradas)

Solução: espaçar a criação dos lotes e tratar a recusa como parte normal do fluxo, com nova tentativa

Tem também o endpoint de cancelamento, que existe justamente pra quando tu percebe que mandou o prompt errado pra 80 mil linhas 😅 As requisições canceladas aparecem com o campo result igual a canceled

E a prevenção que resolve quase tudo isso de uma vez: desenhar o job pra ser retomável e idempotente desde o primeiro dia

Guarda o id do lote, guarda o que já foi gravado, e faz o script poder rodar duas vezes sem duplicar nada. Job assíncrono que só funciona se rodar do começo ao fim sem falha é bomba relógio

Lote na Anthropic, na OpenAI e no Gemini

Dá pra colocar os três lado a lado com o que é público:

PlataformaDescontoJanelaDetalhe
Message Batches API (Anthropic)50% em entrada e saídaaté 24 horasresultados por 29 dias, casamento por custom_id
Batch API (OpenAI)50% sobre o preço síncronoresultados dentro de 24 horasmesma lógica de envio e retorno posterior
Batch Mode (Gemini)50% em relação às APIs síncronasresultados em até 24 horasmesma lógica de envio e retorno posterior

A leitura é bem direta: o desconto de 50% e a janela de 24 horas viraram padrão de mercado

Ou seja, o modo assíncrono não é mais um diferencial de fornecedor

A escolha volta a ser pelo modelo e pelo preço base dele, não pelo modo de execução

Vale a pena? O critério para decidir

Vale, em volume alto e sem urgência, e o motivo é simples: a conta cai pela metade sem trocar de modelo, sem piorar qualidade, sem gambiarra de prompt

Somado ao prompt caching, o corte é maior ainda

Mas tem preço, e ele não é em dólar

Tu abre mão de previsibilidade de tempo: a documentação diz que a maioria dos lotes termina em menos de 1 hora, e ao mesmo tempo avisa que a fila pode ficar mais lenta conforme a demanda. O único compromisso firme é o teto de 24 horas

Tu aceita escrever código orientado a fila: criar, acompanhar estado, baixar arquivo, reconciliar por custom_id, tratar falha parcial

Isso é mais superfície pra bug do que uma chamada síncrona que devolve a resposta na hora

E aqui vem o veredito honesto: quem processa poucas requisições por dia ganha pouco e paga caro em complexidade

Economizar centavos e ganhar uma máquina de estados pra manter não é troca boa. Nesses casos vale até olhar o outro lado da régua, tipo quando a interface já resolve sem entrar em API nenhuma

O que NÃO dá pra prometer: prazo abaixo das 24 horas. Se o teu processo precisa estar pronto às 8h da manhã em ponto, o lote não te dá essa garantia

Próximo passo

Começa separando, na tua aplicação, o que tem usuário esperando do que só precisa estar pronto amanhã

Na prática é fazer uma lista de chamadas de modelo e marcar cada uma com "tem gente na tela?" sim ou não

Depois estima a economia com os preços de lote do modelo que tu JÁ usa, com o volume de tokens que tu já gasta hoje. Metade da conta daquele pedaço é o número que tu leva pra decisão

E migra primeiro um job isolado, retomável e idempotente, medindo o tempo real de conclusão por alguns dias antes de mover o resto

Se o lote fecha rápido o suficiente pro teu processo, aí sim tu move o resto com segurança

Se não fecha, tu descobriu isso num job só, e não na base inteira 😀

até o próximo post!

Perguntas frequentes

Quanto tempo demora pra um lote ficar pronto na Message Batches API?

O teto é de 24 horas a partir da criação do lote, mas a documentação da Anthropic afirma que a maioria dos lotes termina em menos de 1 hora. Não existe SLA de agendamento além desse teto: o processamento pode ficar mais lento conforme a demanda do momento e o volume enviado.

Por quanto tempo os resultados de um lote ficam disponíveis pra download?

Os resultados ficam disponíveis por 29 dias contados a partir da criação do lote (created_at). Depois desse prazo, o lote continua visível, mas os resultados não podem mais ser baixados.

Dá pra cancelar um lote depois que ele já foi criado?

Sim, existe endpoint de cancelamento na API de Message Batches. As requisições que não chegaram a terminar aparecem no resultado com o status canceled.

O prompt caching funciona dentro de um lote?

Funciona, e os descontos se somam: a Message Batches API suporta prompt caching, e o desconto de cache acumula com o desconto de lote. Como um acerto de cache custa 10% do preço base de entrada, um job com instruções fixas repetidas em cada requisição fica ainda mais barato.

As requisições de um lote contam pro limite de taxa (rate limit) da conta?

Contam, só que num grupo separado: requisições dentro de um lote entram como requisições enfileiradas, com um grupo de rate limit próprio, diferente do usado pela API síncrona.

Qual é o tamanho máximo de um lote na API da Anthropic?

Um lote é limitado a 100.000 requisições de Mensagens ou 256 MB, o que for atingido primeiro. Base maior que isso precisa ser fatiada em vários lotes, cada um com seu próprio id pra acompanhar depois.



Subscribe
Notify of
guest

0 Comentários
Oldest
Newest Most Voted
Inline Feedbacks
View all comments

Formações

Formação SAAS com IA

Formação SAAS com IA

Tire usas ideias do papel criando softwares com IA, integre pagamentos e lance seu projeto!

  • 291 aulas
  • 18 projetos
  • 24h 17min

Blog | Mais populares