Pular para o conteúdo

Certificação Claude Developer Foundations · parte 3 de 20

Mecânica da API Claude: requisição, resposta e stop_reason

Como cada chamada à API do Claude é montada e lida: os campos da requisição e da resposta, o stop_reason, o usage, o streaming e os limites de taxa.

Publicada em · 39 min de leitura

Tamanho da fonte:
Largura da coluna:

Esta nota explica a mecânica de cada chamada à API do Claude. A pergunta que ela responde é simples de dizer e cheia de detalhe na prática: o que acontece em cada chamada à API do Claude, quais campos da requisição e da resposta importam, e como o seu código deve reagir a cada resposta. Ao terminar, você deve conseguir escrever o laço que chama o modelo, ler o stop_reason e decidir o próximo passo para cada valor, contar tokens pelo usage, consumir um stream, montar um lote assíncrono, esperar do jeito certo quando bater um limite de taxa, guardar a chave sem vazá-la e escolher entre um alias e um id de modelo com data.

Os exemplos de código usam Python com o SDK oficial anthropic. Os trechos marcados como saída real foram executados nesta sessão, em 18/10/2026, contra um servidor local que imita o formato documentado da API. Nenhuma chamada saiu para a rede e nenhuma chave real foi usada, então o que você vê prova o comportamento do código e do SDK diante daquele formato, não o comportamento do serviço em produção. Quando um fato depende do serviço real (preços, limites por tier, prazos), a nota cita a documentação oficial consultada no mesmo dia, e a seção de fontes diz o que ficou sem verificação. Os nomes de modelos e de campos mudam a cada lançamento, então confira a documentação antes de copiar um id.

Mapa da aula

SeçãoO que responde
1. O que acontece em uma chamadaQual é o caminho de uma requisição, do seu código até a resposta?
2. Requisição: mensagens e o campo de sistemaQuais campos são obrigatórios e quem fala em cada papel?
3. Resposta: o objeto MessageO que volta do servidor e onde estão as partes que você usa?
4. stop_reason: um valor de cada vezO que o código faz com cada um dos sete valores?
5. usage: a conta de tokensComo contar entrada e saída, e por que a entrada tem três números?
6. StreamingQuais eventos chegam e como montar a resposta final?
7. Message Batches e trabalho em filaQuando trocar a chamada síncrona por um lote, e o que muda no código?
8. Limites de taxaQuais limites existem, o que é o retry-after e qual espera usar?
9. Chave de APIOnde guardar a chave, como trocá-la e o que nunca fazer?
10. Versão do modelo: alias ou id com dataComo fixar o modelo para o comportamento não mudar sem aviso?
11. Erros comuns e boas práticasQuais armadilhas aparecem em produção, e por quê?
12. ExercíciosO que você pode rodar sozinho?
13. GlossárioQuais termos desta aula valem guardar?
14. FontesOnde cada fato foi confirmado e o que não foi?

Parte 1: a chamada

1. O que acontece em uma chamada

Pense em uma chamada à API como pedir um prato por um balcão com regras rígidas. Você escreve o pedido num formulário padronizado (o corpo JSON), entrega junto a sua identificação (a chave), e o balcão devolve o prato pronto, acompanhado de uma etiqueta que diz se o prato saiu completo, se foi interrompido ou se a cozinha se recusou a fazê-lo. A etiqueta importa tanto quanto o prato, porque é nela que você descobre se pode servir ou se precisa pedir de novo.

Tecnicamente, a chamada é uma requisição HTTP POST para o endpoint /v1/messages, em https://api.anthropic.com. O corpo é JSON, e o servidor responde com um JSON também (ou com um fluxo de eventos, se você pediu streaming). A figura mostra o caminho completo, com os cabeçalhos que o SDK envia por você e os dois caminhos de saída: a resposta com status 200 e os erros.

3 01 chamada
figura 1 · 3 01 chamada

Comece pela esquerda. O seu código chama o método do SDK, que monta o corpo JSON com model, max_tokens, system e messages. A chave vem de uma variável de ambiente, não do código, e entra no cabeçalho de autenticação. Quando o corpo chega ao endpoint, a API valida o pedido, roda o modelo e devolve um objeto Message. Esse é o caminho verde, que segue para o seu código, onde você lê o texto, o stop_reason e o usage. O caminho rosa, à direita, é o das falhas: um status 4xx ou 5xx com um corpo que traz error.type. Repare que o erro e a resposta bem-sucedida são ramos separados. Muita gente trata só o caminho verde e deixa o rosa escapar como exceção genérica, e é aí que a aplicação quebra sem explicação.

Os cabeçalhos obrigatórios são três. O anthropic-version precisa estar presente, e o valor documentado atual é 2023-06-01. O content-type é application/json. E a autenticação vai em x-api-key ou em Authorization: Bearer. O SDK preenche tudo isso sozinho, então na prática você só precisa saber que eles existem, porque ao ler um log de erro ou uma requisição crua eles aparecem e alguém vai perguntar por quê.

2. Requisição: mensagens e o campo de sistema

O corpo da requisição tem três campos obrigatórios: model (qual modelo responde), max_tokens (teto de tokens que a resposta pode gerar) e messages (a conversa até agora). Os principais opcionais são system (instruções fixas), stop_sequences (textos que interrompem a geração), stream (liga o streaming), tools (ferramentas que o modelo pode pedir para usar) e metadata (um user_id opcional, sem dados pessoais). Outros campos, como thinking, output_config (com effort) e service_tier, existem na referência, mas fogem do escopo desta aula.

O ponto que mais confunde quem começa é o papel das mensagens. Em messages, cada item tem um role que é user ou assistant, e um content, que pode ser uma string ou uma lista de blocos. As instruções que valem para a conversa inteira não vão em messages como um papel system: vão no campo de nível superior system. A razão é prática. Se as instruções fossem mais uma mensagem, o histórico cresceria e elas poderiam se perder no meio, enquanto o campo system fica sempre no mesmo lugar da requisição e é lido em toda chamada. Vale notar que uma das páginas de referência consultadas listou system como um valor possível de role, o que contradiz a orientação do guia. Esta nota segue o guia, e a seção de fontes registra a dúvida.

Um exemplo mínimo, com o pedido exato que o servidor recebeu na execução da seção 4:

import anthropic

cliente = anthropic.Anthropic()  # lê ANTHROPIC_API_KEY do ambiente

resposta = cliente.messages.create(
    model="claude-haiku-5-5",
    max_tokens=200,
    system="Você é conciso.",
    messages=[{"role": "user", "content": "Oi"}],
)
print(resposta.stop_reason, resposta.usage.input_tokens, resposta.usage.output_tokens)
print(resposta.content[0].text)

Três decisões aqui merecem explicação. A primeira é o max_tokens: ele limita o custo e a duração de uma resposta, e ele é o motivo pelo qual você vai ver o valor max_tokens no stop_reason (seção 4). Um teto baixo demais corta a resposta no meio. Um teto alto demais não custa nada a mais por si só, porque o que se cobra é o que o modelo gera, não o limite. A segunda é o system: ele é o lugar das regras estáveis, e a documentação de prompts trata essa separação com mais detalhe. A terceira é a chave, que o construtor do SDK lê do ambiente. Você pode passar api_key= explicitamente, mas o caminho que não deixa segredo no código é deixar a variável no ambiente.

3. Resposta: o objeto Message

A resposta de sucesso é um objeto Message. Os campos que você vai usar são estes: id (identificador da mensagem, útil para suporte e para logs), type (sempre "message"), role (sempre "assistant"), content (lista de blocos gerados), model (o modelo que realmente respondeu), stop_reason (por que a geração parou), stop_sequence (qual sequência disparou, se alguma), stop_details (detalhes quando o motivo é recusa) e usage (contagem de tokens).

O campo content é uma lista, não um texto. Um bloco pode ser text, mas também pode ser tool_use (o modelo pede para rodar uma ferramenta), thinking (raciocínio, quando ligado) ou blocos de ferramentas do servidor, como server_tool_use. Por isso o código que lê só content[0].text funciona por sorte até o dia em que a resposta vem com um bloco de ferramenta na frente. O jeito seguro é filtrar pelo tipo, como faz o código da seção 4.

Na execução de exemplo, o objeto que o SDK devolveu para o pedido simples chegou com estes valores:

stop_reason: end_turn
usage.input_tokens: 25 | usage.output_tokens: 12
texto: Olá! Posso ajudar.

O servidor de teste devolveu esses números fixos, então eles não dizem nada sobre o custo real. Servem apenas para mostrar onde cada número mora na resposta e como o SDK o expõe em usage.

Parte 2: o que o modelo devolve

4. stop_reason: um valor de cada vez

O stop_reason responde à pergunta que mais importa no código: por que a geração parou? A resposta não é um detalhe de depuração. Ela decide se você deve servir o texto, rodar uma ferramenta, pedir a continuação de um turno ou tratar uma recusa. A documentação lista sete valores, confirmados na página de referência de Messages e na página de tratamento de stop_reason em 18/10/2026:

ValorSignificado oficialO que o código deve fazer
end_turnO modelo terminou a resposta naturalmente.Usar o texto. É o caso normal.
stop_sequenceGerou uma das suas stop_sequences.Ler stop_sequence para saber qual disparou.
max_tokensA resposta bateu no limite max_tokens.Tratar como cortada: subir o limite ou continuar, sem servir o texto como completo.
tool_useO modelo pediu uma ferramenta.Executar a ferramenta e devolver um bloco tool_result na mensagem seguinte.
pause_turnUm laço de ferramenta do servidor atingiu o limite de iterações.Reenviar o content como está para o modelo continuar.
refusalUm classificador de segurança interveio.Ler stop_details e reformular ou usar outro modelo de fallback.
model_context_window_exceededA resposta encheu a janela de contexto do modelo.Tratar como truncada.

A figura organiza os sete valores em três grupos, pelo que o código faz depois.

3 02 stop reason
figura 2 · 3 02 stop reason

O grupo verde, à esquerda do conjunto, é o de resposta pronta. O grupo azul, do meio, é o de laço que continua: tool_use e pause_turn não são fins, são pedidos de mais um passo. O grupo rosa, abaixo, é o de atenção, onde a resposta existe mas não pode ser tratada como completa. Repare que refusal chega com status HTTP 200. Isso é o ponto mais traiçoeiro da tabela, e a seção 11 volta a ele.

Cada valor merece um parágrafo, porque o erro costuma estar no detalhe.

Quando end_turn aparece, o modelo chegou ao fim de um pensamento completo, e você pode servir o texto. Quando stop_sequence aparece, a geração parou porque encontrou um texto que você mesmo pediu para parar, e a resposta é completa do ponto de vista do seu contrato. Por isso o código deve ler o campo stop_sequence: se você usou duas sequências diferentes para dois fins diferentes, é a única forma de saber qual delas venceu.

max_tokens é o valor que mais engana. A resposta existe, o status é 200, e o texto pode estar cortado no meio de uma palavra. Se o seu código serve esse texto como resposta final, o usuário recebe uma frase incompleta sem nenhum erro registrado. O jeito certo é tratar o corte como um problema: ou você sobe o max_tokens e repete, ou, se a resposta for uma sequência de partes, pede a continuação. A documentação também observa que um bloco de tool_use incompleto nesse caso exige repetir a requisição com um limite maior, porque uma ferramenta cortada não tem argumentos válidos.

tool_use é o valor que faz o laço de agente girar. O modelo não executa nada. Ele devolve um bloco tool_use com name e input, e quem executa a função é o seu código. Depois você responde com uma mensagem user que contém um bloco tool_result com o mesmo tool_use_id. Essa é a base da seção sobre ferramentas, e a regra que importa aqui é que o stop_reason só anuncia o pedido. A ação é sua.

pause_turn aparece quando a API está rodando uma ferramenta do lado do servidor, como busca na web, e o laço interno atingiu o limite de iterações (o padrão documentado é 10 por requisição). A orientação é devolver o content exatamente como veio, em uma nova requisição, para o modelo continuar de onde parou. Não é preciso mudar a conversa. Se a continuação também pausar, o laço deve ter um teto, como no exemplo da página de tratamento de stop_reason, que usa cinco continuações no máximo.

refusal precisa ser verificado por campo, porque não lança exceção. Quando um classificador de segurança intervém, a resposta chega como 200 normal com stop_reason: "refusal". O campo stop_details traz a categoria acionada (por exemplo cyber, bio ou general_harms, além de outras listadas na referência) e uma explicação que a própria documentação diz não ser estável. Para os modelos da linha 5.5 e Fable 5.1, a página de recusas informa que uma recusa geralmente pode ser atendida por outro modelo Claude, com fallback configurável no servidor ou no cliente.

model_context_window_exceeded significa que a resposta encheu a janela. O texto que chegou é parcial, e tratá-lo como completo é o mesmo erro de max_tokens, só que com um motivo que você não controla diretamente. Se isso acontece com frequência, o problema está no tamanho do histórico, e a janela de contexto vale a aula de Prompt e contexto.

Um código que cobre os sete valores precisa também de uma regra para o caso que ainda não existe. A documentação avisa que a API pode acrescentar novos valores a campos desse tipo, então o código deve falhar alto diante de algo desconhecido, em vez de seguir como se fosse end_turn:

def tratar(resposta):
    """Decide o próximo passo a partir de stop_reason. Valor novo falha alto."""
    motivo = resposta.stop_reason
    texto = " ".join(b.text for b in resposta.content if b.type == "text")
    if motivo == "end_turn":
        return f"usar o texto: {texto!r}"
    if motivo == "stop_sequence":
        return f"parou na sequência {resposta.stop_sequence!r}: {texto!r}"
    if motivo == "max_tokens":
        return f"CORTADA, subir max_tokens ou continuar: {texto!r}"
    if motivo == "tool_use":
        chamadas = [(b.name, b.input) for b in resposta.content if b.type == "tool_use"]
        return f"executar ferramentas e devolver tool_result: {chamadas}"
    if motivo == "pause_turn":
        return "reenviar content como está (turno pausado do lado do servidor)"
    if motivo == "refusal":
        return f"recusa, ler stop_details: {resposta.stop_details.category}"
    if motivo == "model_context_window_exceeded":
        return "tratar como truncada"
    raise ValueError(f"stop_reason novo, não previsto: {motivo!r}")

A execução contra o servidor de teste, com um pedido para cada valor, produziu esta saída:

ok       stop_reason=end_turn                       -> usar o texto: 'Resposta de teste.'
parar    stop_reason=stop_sequence                  -> parou na sequência 'FIM': 'Resumo'
cortar   stop_reason=max_tokens                     -> CORTADA, subir max_tokens ou continuar: 'A frase para no meio da'
ferramenta stop_reason=tool_use                       -> executar ferramentas e devolver tool_result: [('buscar_pedido', {'id': 42})]
pausa    stop_reason=pause_turn                     -> reenviar content como está (turno pausado do lado do servidor)
recusa   stop_reason=refusal                        -> recusa, ler stop_details: cyber
janela   stop_reason=model_context_window_exceeded  -> tratar como truncada

O servidor de teste devolveu cada caso com o formato documentado, então a saída mostra que a função classifica os sete valores corretamente. Ela não mostra como o modelo real escolhe cada motivo, e isso não está em escopo.

5. usage: a conta de tokens

O campo usage é a contabilidade da chamada. Ele registra quantos tokens entraram e quantos saíram, e é com ele que você calcula custo, monitora orçamento e confere se o cache está funcionando. Os campos documentados são input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens, cache_creation (a divisão por duração do cache), service_tier (o nível usado: standard, priority ou batch), speed e server_tool_use (quantas buscas e leituras de página o servidor fez).

A parte que confunde é a entrada. O total de tokens de entrada de uma requisição é a soma de três números: input_tokens, cache_creation_input_tokens e cache_read_input_tokens. O input_tokens sozinho representa só o que vem depois do último ponto de cache. Se você mandar um documento de 200 mil tokens em cache e uma pergunta de 50 tokens, o input_tokens pode mostrar 50, embora o total de entrada seja 200.050. Quem lê só input_tokens subestima o custo de todo prompt que usa cache, e subestima também o consumo do limite de taxa, que é o tema da seção 8.

A saída já é outro assunto. O output_tokens é o total faturado, e inclui os tokens de raciocínio quando o thinking está ligado. O campo output_tokens_details.thinking_tokens, quando presente, mostra quanto dessa saída foi raciocínio, e é sempre menor ou igual ao total. Por isso a conta de saída é um número só, e não uma soma.

Uma última observação: a contagem de tokens não corresponde letra por letra ao texto visível. O servidor transforma a requisição antes de passá-la ao modelo, e a própria documentação diz que output_tokens nunca é zero, mesmo em uma resposta vazia. Para antecipar o custo antes de enviar, existe o endpoint de contagem (/v1/messages/count_tokens), que a aula LLMs usa no exercício de custo. Ele devolve uma estimativa, não a contagem faturada.

Parte 3: streaming, lotes e limites

6. Streaming

Por padrão, a API espera a resposta inteira e só então devolve o JSON. Com stream: true, ela devolve a resposta aos poucos, em eventos enviados pelo servidor (Server-Sent Events, SSE). O motivo é a experiência do usuário: quem lê o texto aparecer palavra por palavra espera menos do que quem olha uma tela vazia por vários segundos. O custo é que o seu código precisa montar a resposta sozinho, e é nisso que esta seção se concentra.

A figura mostra a sequência de eventos e o que o código faz com cada um.

3 03 streaming
figura 3 · 3 03 streaming

A ordem documentada é fixa. Primeiro vem message_start, com um objeto Message cujo content está vazio e cujo stop_reason é null. Depois vem uma série de blocos de conteúdo, cada um com um content_block_start, um ou mais content_block_delta e um content_block_stop. O campo index liga cada evento ao bloco correspondente. Em seguida vem um ou mais message_delta, que trazem as mudanças de nível superior, como o stop_reason final e o usage acumulado. Por fim, message_stop encerra o fluxo. Podem aparecer eventos ping em qualquer ponto, e o código deve ignorá-los.

O tipo de delta define o que fazer. Para texto, o text_delta traz um pedaço de string, e você concatena os pedaços do mesmo index. Para ferramentas, o input_json_delta traz um pedaço de JSON parcial, que você acumula e só faz json.loads depois do content_block_stop, porque um pedaço isolado não é JSON válido. Para raciocínio, existem thinking_delta e signature_delta, este último enviado logo antes do content_block_stop. A documentação também lista citations_delta para citações.

Há duas regras que pegam quem monta a resposta na mão. A primeira é que o stop_reason só aparece no message_delta. Em message_start ele é null, e um código que o lê antes disso acha que a resposta não terminou. A segunda é que o usage no message_delta é cumulativo, então você substitui o valor anterior em vez de somar. A terceira regra, a mais importante, é que um erro pode chegar no meio do fluxo. A API já respondeu 200 quando o evento error aparece, com um corpo como {"type": "error", "error": {"type": "overloaded_error", ...}}. Um código que só olha o status HTTP nunca vê esse erro. Quem repete uma chamada em streaming precisa decidir o que fazer com o texto parcial já recebido.

O SDK faz o trabalho pesado. O helper stream acumula o texto e devolve a mensagem final, e é o caminho recomendado quando você não precisa dos eventos crus. A execução abaixo usa o helper e depois lê os eventos crus do mesmo servidor de teste:

with cliente.messages.stream(model="claude-haiku-5-5", max_tokens=200,
                             messages=[{"role": "user", "content": "Oi"}]) as fluxo:
    for pedaco in fluxo.text_stream:
        print(repr(pedaco))
    final = fluxo.get_final_message()
print("texto montado:", final.content[0].text)
print("stop_reason:", final.stop_reason, "| output_tokens:", final.usage.output_tokens)

for evento in cliente.messages.create(model="claude-haiku-5-5", max_tokens=200, stream=True,
                                      messages=[{"role": "user", "content": "Oi"}]):
    extra = ""
    if evento.type == "content_block_delta":
        extra = f" delta={evento.delta.type} text={evento.delta.text!r}"
    if evento.type == "message_delta":
        extra = f" stop_reason={evento.delta.stop_reason} output_tokens={evento.usage.output_tokens}"
    print(evento.type + extra)

Saída real:

'Olá'
', tudo'
' bem?'
texto montado: Olá, tudo bem?
stop_reason: end_turn | output_tokens: 15
message_start
content_block_start
content_block_delta delta=text_delta text='Olá'
content_block_delta delta=text_delta text=', tudo'
content_block_delta delta=text_delta text=' bem?'
content_block_stop
message_delta stop_reason=end_turn output_tokens=15
message_stop

Os eventos ping do servidor de teste não aparecem na listagem crua. O SDK pode filtrá-los, e esta execução não confirmou se isso acontece em todas as versões, então não vou afirmar mais do que vi. A ordem message_start, bloco de texto, message_delta, message_stop é a esperada.

7. Message Batches e trabalho em fila

Até aqui, cada chamada espera a resposta. Muitas tarefas não precisam disso: classificar dez mil e-mails, extrair dados de mil contratos, rodar uma avaliação inteira. Para esses casos existe a Message Batches API, que aceita um lote de pedidos, processa tudo em segundo plano e devolve um arquivo de resultados. Segundo a documentação consultada em 18/10/2026, o lote custa 50% do preço da chamada síncrona, a maioria termina em menos de uma hora, e os resultados ficam disponíveis quando todos os pedidos terminam ou após 24 horas, o que vier primeiro.

A figura compara os dois caminhos lado a lado.

3 05 sincrona vs lote
figura 4 · 3 05 sincrona vs lote

Na coluna da esquerda, o caminho síncrono é uma linha reta: um pedido, uma espera, uma resposta. Na coluna da direita, o lote é uma fila. Você envia uma lista de pedidos, cada um com um custom_id, e recebe um lote em in_progress. O servidor processa a fila, e o lote passa a ended. Os resultados ficam num arquivo JSONL, um por linha, e cada linha traz o custom_id do pedido de origem. Esse identificador é a única ligação entre o pedido e o resultado, porque a ordem das linhas não é garantida.

As regras que você precisa decorar, segundo a página de lotes, são estas. O custom_id tem de 1 a 64 caracteres, só letras, números, hífen e sublinhado. Cada pedido precisa de max_tokens de pelo menos 1, porque max_tokens: 0, usado para aquecer o cache, não é aceito em lote. Um lote aceita até 100 mil pedidos, e a requisição inteira pode ter até 256 MB. Os resultados ficam disponíveis por 29 dias após a criação. Os tipos de resultado são succeeded, errored, canceled e expired, e os três últimos não são cobrados. Um errored pode ser erro de validação do pedido, que só aparece no fim do processamento. Por isso a documentação recomenda validar o formato de cada pedido com a chamada síncrona antes de montar um lote grande.

Quando usar lote e quando não usar? A pergunta é de latência, não de qualidade. Se um humano está esperando a resposta na tela, o lote é a escolha errada, porque a fila não tem prazo curto. Se a tarefa pode esperar algumas horas e são muitos itens, o lote economiza metade do custo e tira o trabalho do limite de requisições por minuto, que é o assunto da próxima seção. A documentação também define um limite próprio para lotes: um número máximo de requisições por minuto para os endpoints de lote e um máximo de pedidos aguardando na fila de processamento, que varia com o tier.

O código de um lote tem três etapas: criar, consultar até terminar e ler os resultados. A execução abaixo fez as três contra o servidor de teste, que responde in_progress na primeira consulta e ended na segunda:

lote = cliente.messages.batches.create(requests=[
    {"custom_id": "pedido-1", "params": {"model": "claude-haiku-5-5", "max_tokens": 100,
     "messages": [{"role": "user", "content": "ok"}]}},
    {"custom_id": "pedido-2", "params": {"model": "claude-haiku-5-5", "max_tokens": 100,
     "messages": [{"role": "user", "content": "cortar"}]}},
])
print("criado:", lote.id, lote.processing_status)
while True:
    lote = cliente.messages.batches.retrieve(lote.id)
    print("status:", lote.processing_status)
    if lote.processing_status == "ended":
        break
for item in cliente.messages.batches.results(lote.id):
    if item.result.type == "succeeded":
        msg = item.result.message
        print(item.custom_id, "succeeded", msg.stop_reason, tratar(msg))
    else:
        print(item.custom_id, item.result.type)

Saída real:

criado: msgbatch_demo in_progress
status: in_progress
status: ended
pedido-1 succeeded end_turn usar o texto: 'Resposta de teste.'
pedido-2 succeeded max_tokens CORTADA, subir max_tokens ou continuar: 'A frase para no meio da'

Dois pontos desse código merecem atenção. O laço de consulta do exemplo não tem pausa nem limite, e em produção ele deve ter os dois: um intervalo entre consultas e um tempo máximo, porque um lote pode ficar em processamento por horas. E a leitura dos resultados usa custom_id para encontrar o pedido de origem, e nunca a posição na lista. Se você gravar o resultado pelo índice, um lote reordenado vira um lote com dados trocados entre clientes.

Essa é a parte que mais se parece com um sistema de filas comum, e por isso a aula de Idempotência importa aqui. Um consumidor que lê o JSONL e grava o resultado no banco pode ser reiniciado no meio e ler a mesma linha duas vezes. A defesa é a mesma das filas: gravar o resultado com a chave custom_id numa restrição única, para que a segunda gravação seja reconhecida como repetida. A API não garante que você nunca verá o mesmo resultado duas vezes do seu lado, então a unicidade é responsabilidade do seu banco.

8. Limites de taxa

Limite de taxa é o teto de uso que a API impõe para proteger o serviço e distribuir capacidade entre todos os clientes. A página de limites, consultada em 18/10/2026, define três medidas para o endpoint de mensagens, por classe de modelo: requisições por minuto (RPM), tokens de entrada por minuto (ITPM) e tokens de saída por minuto (OTPM). Qualquer uma delas que estourar gera um erro 429 com error.type igual a rate_limit_error, e a mensagem diz qual limite foi atingido.

Os limites são por organização e dependem de um tier de uso. Quem começa entra em um tier inicial, e o tier sobe automaticamente conforme o histórico de uso. Para dar uma escala, na tabela do tier inicial (Start) consultada em 18/10/2026, o Claude Haiku 5.5 aparece com 1.000 RPM, 2.000.000 de ITPM e 400.000 de OTPM. Esses números mudam, e a tabela tem valores diferentes para os tiers Build e Scale. Quem precisa do valor exato consulta a página de limites no console, ou a API de limites de taxa, que devolve a configuração da própria organização e de cada workspace.

A página também avisa que o limite é medido em janelas curtas. Um limite de 60 RPM pode ser aplicado como uma requisição por segundo, e uma rajada pode bater no limite mesmo com a média dentro do esperado. Quem faz o cálculo só por minuto se surpreende. O algoritmo é de balde de fichas (token bucket): a capacidade é reposta continuamente, e não zera num horário fixo. Por isso, esperar um pouco costuma bastar, mas não necessariamente o minuto inteiro.

Como o ITPM é contado é a parte que mais economiza dinheiro e mais confunde. Para a maioria dos modelos, só conta o que não está em cache: input_tokens e cache_creation_input_tokens entram no ITPM, e cache_read_input_tokens não entra. O Haiku 3.5, que a página marca como aposentado exceto no Google Cloud, é uma exceção porque conta a leitura do cache. Um prompt de 200 mil tokens com 80% de acerto no cache consome bem menos ITPM do que o tamanho do prompt sugere. O OTPM, por sua vez, é medido à medida que a saída é gerada, e o max_tokens não entra nessa conta. Então um max_tokens alto não custa limite de saída por si só.

Os cabeçalhos de resposta dão a foto do momento. anthropic-ratelimit-requests-limit, anthropic-ratelimit-requests-remaining e anthropic-ratelimit-requests-reset dizem o teto de requisições, quantas sobraram e quando o balde estará cheio, em RFC 3339. Há um conjunto equivalente para tokens, e também para entrada e saída separadas (anthropic-ratelimit-input-tokens-* e anthropic-ratelimit-output-tokens-*). Quando há mais de um limite ativo, os cabeçalhos genéricos de tokens mostram o mais restrito. O cabeçalho retry-after aparece quando o servidor sabe quanto esperar, e é a informação mais útil de todas, porque é uma instrução e não uma estimativa.

Esse é o ponto que separa quem reage bem de quem reage mal. A página diz que o retry-after é o número de segundos a esperar antes de tentar de novo, e que tentativas antes desse prazo vão falhar. Mas nem todo 429 o traz. O caso mais importante é o limite de gasto mensal: quando a organização atinge o teto do tier, a API responde 429 com rate_limit_error, sem retry-after, e com error.details.error_code igual a enforced_spend_limit_reached. Esse erro só termina quando o acesso volta, no início do mês seguinte, e repetir só gasta tempo. Um limite de gasto que você mesmo configurou responde 400 com invalid_request_error, não 429. A regra prática, portanto, é: se o cabeçalho retry-after existe, espere pelo menos esse valor; se não existe e o erro não é de teto de gasto, use espera exponencial com variação aleatória (jitter).

A figura resume a decisão.

3 04 espera 429
figura 5 · 3 04 espera 429

O caminho de cima trata erro do pedido: um 400 ou 401 não melhora com espera, porque o problema está no pedido ou na chave. O caminho da direita trata o 429. Antes de tudo, o código confere se o erro é de teto de gasto, e nesse caso para. Se não é, procura o retry-after. Quando ele existe, o código espera pelo menos aquele valor. Quando não existe, usa espera exponencial, que dobra o intervalo a cada tentativa (0,5 s, 1 s, 2 s e assim por diante, até um teto), com jitter para que vários clientes não voltem sincronizados. Os dois caminhos desembocam na repetição, e a repetição só é segura se a ação repetida não causar efeito colateral, o que é o tema de Idempotência.

O SDK oficial já faz boa parte disso. Ele repete automaticamente erros de conexão, 429 e 5xx, com espera exponencial e respeitando retry-after, e o parâmetro max_retries configura o número de tentativas. Na execução abaixo, o servidor respondeu 429 com retry-after: 1 na primeira vez, e o SDK repetiu sem intervenção do código do usuário:

antes = time.monotonic()
r = cliente.messages.create(model="claude-haiku-5-5", max_tokens=200,
                            messages=[{"role": "user", "content": "estourar"}])
decorrido = time.monotonic() - antes
tentativas = sum(1 for x in recebidas if x["corpo"]["messages"][-1]["content"] == "estourar")
print(f"requisições que chegaram ao servidor com 'estourar': {tentativas}")
print(f"tempo decorrido: {decorrido:.1f} s (retry-after pedia 1 s)")
print("stop_reason final:", r.stop_reason)

Saída real:

requisições que chegaram ao servidor com 'estourar': 2
tempo decorrido: 1.0 s (retry-after pedia 1 s)
stop_reason final: end_turn

Duas requisições chegaram ao servidor, uma com 429 e outra com sucesso, e a espera foi de um segundo, o valor do cabeçalho. A documentação de erros, que não reli nesta sessão, descreve o número padrão de tentativas e como configurá-lo. Não afirmo aqui o valor padrão.

Há um erro que o próprio SDK não resolve: somar um laço seu por cima do laço do SDK. Se você tem três tentativas no seu código e o SDK faz três por dentro, uma falha pode gerar nove chamadas. Escolha uma camada só. Para a maioria dos casos, deixe o SDK repetir e aplique o seu laço apenas para regras que ele não cobre, como repetir só um subconjunto de erros ou registrar métricas de cada tentativa.

Um último aviso: a página fala de limites de aceleração. Um salto brusco de tráfego pode gerar 429 mesmo com o tier suficiente para a média. A recomendação é subir o volume gradualmente e manter padrões de uso consistentes. Isso é tão importante quanto o retry-after para quem lança uma funcionalidade nova num dia de pico.

Parte 4: chave e versão

9. Chave de API

A chave é o segredo que identifica a sua conta a cada chamada. Quem tem a chave pode gastar o seu orçamento, então ela tem o mesmo peso de uma senha de banco. A documentação de autenticação, consultada em 18/10/2026, é direta: armazene as chaves num gerenciador de segredos, troque-as periodicamente e desative ou apague qualquer chave que suspeite ter vazado.

A chave vai em cada requisição, como Authorization: Bearer <chave> ou no cabeçalho legado x-api-key. O SDK lê a variável de ambiente ANTHROPIC_API_KEY quando você não passa a chave no construtor, e esse é o caminho que mantém o segredo fora do código-fonte. Quando a chave está no código, ela vai parar no histórico do git, em capturas de tela, em logs de depuração e, em algum dia ruim, num repositório público. Por isso a regra é nunca colocá-la no código, e a boa prática é também não colocá-la em arquivos de configuração versionados.

Existem três tipos de chave. A chave pessoal age como você e, se você sair da organização, deixa de funcionar. A chave de conta de serviço pertence a uma identidade que a organização gerencia, e é a escolha para integrações compartilhadas e para CI. A chave de workspace é o tipo legado: ainda funciona, mas a documentação recomenda migrar para chaves de identidade ou para Workload Identity Federation. Esse último mecanismo troca tokens de curta duração emitidos pelo seu provedor de identidade, como AWS, Google Cloud ou GitHub Actions, e elimina a chave de longa duração. Para carga de produção em nuvem, a documentação o prefere.

Ao criar uma chave, você escolhe a expiração: uma predefinição (3 horas, 1 dia, 7 dias ou 30 dias), uma duração personalizada, ou “Nunca” para quem guarda a chave no gerenciador de segredos e a rotaciona por conta própria. A expiração limita o tempo de vida de uma chave vazada, mas não substitui a higiene de segredos. Ela não pode ser alterada depois de criada, e a organização pode impor um prazo máximo. Antes de expirar, a Anthropic avisa o criador por e-mail, com antecedência que depende da duração da chave. Depois de expirada, a chave devolve 401 com authentication_error, e não há como reativá-la. Você precisa criar outra.

Trocar uma chave segue quatro passos, que a documentação descreve para a migração de chaves de workspace e que servem para qualquer rotação. Crie a nova chave, no escopo do workspace da integração. Faça o deploy dela no lugar onde a aplicação lê a variável, geralmente o ANTHROPIC_API_KEY ou uma entrada do gerenciador de segredos. Confirme que as requisições passam a funcionar. Só então apague a chave antiga. A ordem importa: apagar antes de confirmar deixa a aplicação fora do ar.

Duas distinções do console valem guardar. Desativar uma chave é reversível (ela fica “inactive” e pode ser reativada), e apagar é definitivo (a chave vira “archived”). A documentação não define um intervalo recomendado de rotação, então a frequência é uma decisão da sua equipe, que deve escolher uma e registrar. Se a organização tem workspaces, uma chave que não está vinculada a um workspace precisa do cabeçalho anthropic-workspace-id em cada requisição, e a ausência dele gera um 400 invalid_request_error.

Uma prática que a seção de limites reforça: separe as chaves por ambiente e por integração. Se uma chave de testes vazar, o estrago fica restrito ao workspace de testes, e a chave de produção continua intacta.

10. Versão do modelo: alias ou id com data

Um modelo muda com o tempo. A Anthropic lança versões novas, aposenta versões antigas e, mesmo com o mesmo nome, o comportamento pode mudar um pouco. Fixar a versão é a decisão que impede uma mudança de comportamento de chegar à sua produção sem você ter testado. Esta seção existe porque o material de estudo mais antigo sobre o tema trata alias e id com data como uma escolha simples, e a documentação atual muda a regra de uma forma que vale conhecer.

Antes da geração 4.6, o nome do modelo tinha uma data embutida, no formato claude-{nome}-{major}-{minor}-{AAAAMMDD}, como claude-haiku-4-5-20251001. Esses modelos também tinham um alias sem data, como claude-sonnet-4-5, que a própria documentação descreve como um ponteiro para o snapshot datado mais recente daquela versão menor. O alias é conveniente porque você ganha correções automáticas, e é perigoso pelo mesmo motivo: quando um snapshot novo sai, o alias passa a apontar para ele, e o seu sistema muda sem nenhuma alteração de código. Esse é o risco que a regra de fixação quer evitar.

A partir da geração 4.6, o formato mudou para claude-{nome}-{major}[-{minor}], sem data, como claude-sonnet-4-6, claude-opus-5 ou claude-opus-5-5. A documentação diz que, nesses ids, o nome sem data já é o id canônico e aponta para um único snapshot fixo. A Anthropic não altera pesos nem configuração de um id existente, e uma versão nova sai com outro id. Então um id como claude-opus-5-5 não é um alias que muda de endereço. A página acrescenta uma ressalva importante: a infraestrutura de serving (roteador, classificadores de segurança, amostragem) pode mudar, e mudanças dela podem produzir diferenças observáveis mesmo com o mesmo id. Se um comportamento que era estável mudar sem troca de id, a infraestrutura é a causa mais provável, e vale abrir um caso de suporte.

A tabela resume a regra para escolher o que usar.

SituaçãoId a usarPor quê
Modelo da geração 4.6 em dianteO id sem data, como claude-sonnet-4-6É um snapshot fixo. A troca exige escolher outro id, e você verá a mudança no código.
Modelo anterior à 4.6, com aliasO id com data, como claude-sonnet-4-5-20250929O alias pode passar a apontar para um snapshot novo sem aviso no seu código.
Protótipo ou exploraçãoO alias ou o id sem dataVocê aceita mudança em troca de conveniência. Não leve esse hábito para produção.

Repare que o alias do modelo anterior à 4.6 tem data de aposentadoria. A página de Messages lista claude-sonnet-4-5 e o seu id com data como descontinuados, com fim de vida em 30 de novembro de 2026, e recomenda migrar para a linha 5.5. Esse é o ponto prático: escolher o id fixo não basta, porque todo id tem seu próprio cronograma de aposentadoria, e o seu código precisa de um teste que avise quando um id estiver perto do fim. A documentação de modelos lista essas datas.

Vale também uma regra de processo. Quando você trocar o id de um modelo, rode a sua suíte de avaliação com o id novo antes de virar a chave de produção, porque o mesmo prompt pode render respostas diferentes. Num agente, que encadeia muitas chamadas, uma mudança pequena de comportamento se multiplica ao longo do laço, o que torna o teste ainda mais necessário (veja Agents).

Parte 5: erros, prática e glossário

11. Erros comuns e boas práticas

O primeiro erro é tratar só o caminho de sucesso. Uma aplicação que lê content[0].text e nada mais falha de três jeitos: quando a resposta cortou por max_tokens, quando veio uma recusa com status 200 e quando o primeiro bloco era de ferramenta. Todos os três passam sem exceção, e o usuário recebe lixo. A correção é a função da seção 4, que trata cada valor e falha alto em valor desconhecido.

O segundo erro é confiar no status HTTP com streaming. Quem consome um stream já recebeu o 200 antes de qualquer conteúdo, e um erro pode chegar no meio. Por isso o consumidor precisa tratar o evento error como falha e decidir, com regra escrita, o que fazer com o texto parcial.

O terceiro é repetir qualquer chamada sem pensar no efeito. Repetir uma geração de texto é, em geral, seguro do ponto de vista do seu sistema, porque o texto não é uma ação. Mas uma ferramenta acionada por um tool_use não é. Se a chamada que trouxe o tool_use foi repetida, a ferramenta pode rodar duas vezes, e o resultado da segunda tentativa pode ser diferente porque o modelo não é determinístico. A proteção está no código da ferramenta, com chave de idempotência, como a aula Idempotência mostra. Vale notar que a mesma aula registra uma dúvida sobre a API não oferecer desduplicação própria, e a verificação feita nesta sessão não encontrou um mecanismo de chave de idempotência nas páginas de Messages, limites e erros. Trate isso como ausência não confirmada e conte com a sua própria defesa.

O quarto é esperar o minuto inteiro depois de um 429 sem ler o cabeçalho. A espera certa vem do retry-after, e quando não existe, da espera exponencial com jitter. Uma espera fixa de um segundo transforma uma falha de sobrecarga numa fila de retentativas simultâneas.

O quinto é colocar a chave no código, ou pior, no repositório. A regra é o ambiente, o gerenciador de segredos e a rotação. Se uma chave vazar, a primeira ação é desativá-la, não investigar antes.

O sexto é o alias em produção. Ele dá conveniência e cobra em surpresa. Fixe o id, e teste antes de trocá-lo.

O sétimo é confiar no input_tokens para custo. Com cache, ele é só uma parte da entrada. Some cache_creation_input_tokens e cache_read_input_tokens antes de calcular qualquer coisa.

Como prática, vale uma lista curta de hábitos: trate todos os valores de stop_reason, registre id e request_id (o cabeçalho request-id) de cada chamada que falhou, use o SDK para repetir e só escreva o seu laço para o que ele não faz, separe chaves por ambiente, fixe ids de modelo e agende a revisão quando algum tiver data de aposentadoria, e use lote para tudo o que pode esperar.

12. Exercícios

  1. Escreva a função tratar da seção 4 e rode-a contra um servidor falso que devolva os sete valores de stop_reason. Acrescente um valor inventado e confirme que a função falha alto.
  2. Pegue um pedido que gere max_tokens (um max_tokens de 20 com uma pergunta que exija resposta longa). Confirme o stop_reason real e o usage.output_tokens. Depois suba o limite e compare.
  3. Faça uma chamada com stream=True e acumule os text_delta por index. Compare o texto montado com get_final_message(). Depois provoque um erro no meio do stream, se o seu ambiente permitir, e veja o que o seu código faz.
  4. Monte um lote com três pedidos, cada um com um custom_id diferente, e leia os resultados. Depois reordene a lista de entrada e confirme que a associação pelo custom_id continua certa.
  5. Escreva um laço de espera que respeite retry-after e, na falta dele, use espera exponencial com jitter e teto. Teste com um servidor falso que responda 429 três vezes.
  6. Verifique a sua chave. Se ela estiver no código ou num arquivo versionado, mova-a para uma variável de ambiente, rotacione-a e confirme que a versão antiga para de funcionar depois de apagada.
  7. Liste os ids de modelo que o seu projeto usa. Para cada um, diga se é alias ou id com data, e qual a data de aposentadoria, consultando a página de modelos.

13. Glossário

TermoSignificado
Messages APIEndpoint POST /v1/messages, que recebe uma conversa e devolve uma mensagem gerada.
MessageObjeto de resposta com content, stop_reason, stop_sequence, stop_details e usage.
RolePapel de uma mensagem, user ou assistant. O prompt de sistema vai no campo system.
systemCampo de nível superior com as instruções que valem para a conversa inteira.
stop_reasonMotivo pelo qual a geração parou. Sete valores documentados.
stop_sequenceTexto que você definiu para interromper a geração, e que pode ser lido no campo de mesmo nome.
usageContagem de tokens da chamada, com entrada, saída e cache.
Cache de promptReuso de um prefixo já processado, com leitura mais barata e cota separada.
SSEServer-Sent Events. Formato de envio de eventos do servidor para o cliente, usado no streaming.
DeltaPedaço incremental de um bloco, como um text_delta ou um input_json_delta.
Message BatchesAPI de processamento assíncrono de um lote de pedidos, com custo menor.
custom_idIdentificador que você atribui a cada pedido de um lote, para ligar pedido e resultado.
Limite de taxaTeto de requisições e tokens por minuto, medido por organização e por tier.
RPM, ITPM, OTPMRequisições por minuto, tokens de entrada por minuto e tokens de saída por minuto.
Balde de fichasAlgoritmo de limite em que a capacidade é reposta continuamente.
retry-afterCabeçalho com os segundos a esperar antes de repetir. Nem todo 429 o traz.
JitterVariação aleatória na espera, para que clientes não voltem sincronizados.
Chave de APISegredo que identifica a conta. Vai no cabeçalho de autenticação, nunca no código.
Model pinningFixação de um id de modelo específico para o comportamento não mudar sem aviso.
SnapshotVersão fixa de um modelo, identificada por um id.
AliasNome sem data que aponta para um snapshot e pode mudar de destino, usado em modelos anteriores à 4.6.

14. Fontes

Consultadas em 18/10/2026, com os links oficiais. Os fatos desta nota vêm dessas páginas, exceto os pontos marcados como não verificados abaixo.

O que não foi verificado, ou ficou com dúvida:

  • A página de Messages, em uma das leituras, listou system como valor possível do campo role, o que contradiz a orientação de que o prompt de sistema vai só no campo system. Esta nota segue a orientação, e recomendo confirmar na referência viva.
  • O número padrão de tentativas do SDK, e a forma de configurá-lo, estão na documentação de erros, que não foi relida nesta sessão.
  • Não confirmei o nome e a semântica de todos os estados intermediários de um lote de cancelamento. A documentação mostra o campo cancel_initiated_at e a contagem canceled, e não detalhei o endpoint de cancelamento.
  • Não confirmei se a Messages API oferece algum mecanismo de chave de idempotência. Nas páginas lidas não encontrei.
  • Os limites numéricos por tier mudam, e os valores citados são de 18/10/2026.
  • Os exemplos de código rodaram contra um servidor local que imita o formato documentado. Não houve chamada à API real, e nenhuma chave real foi usada.

Relacionados

↑ Voltar ao topo

Atalhos de teclado

Abrir busca
/
Alternar tema claro/escuro
t
Aula anterior na série
←
Próxima aula na série
→
Lista de atalhos
?