Seu agente chamou o endpoint de pagamento. A requisição foi processada, a cobrança foi efetuada e, em seguida, a resposta expirou no retorno. O agente nunca viu um 200, então ele fez o que você o instruiu a fazer em caso de falha: ele tentou novamente. Agora o cliente foi cobrado duas vezes, e nada em seus logs parece um erro.
Este é o modo de falha que separa agentes de clientes de API comuns. Um humano clicando em "Pagar" uma vez vê um spinner e espera. Um agente em um loop de repetição vê silêncio e tenta novamente, às vezes três ou quatro vezes seguidas, mais rápido do que qualquer pessoa poderia. Cada política de repetição que você adiciona para tornar o agente mais confiável também torna as gravações duplicadas mais prováveis. A solução é a idempotência: fazer com que uma requisição repetida produza o mesmo resultado que uma única requisição.
Este guia aborda o que a idempotência significa no nível HTTP, como gerar chaves que um agente pode realmente reutilizar, o que o servidor precisa armazenar para honrá-las e como testar tudo antes que um cliente real seja cobrado duas vezes. Se você não leu nosso pilar sobre por que agentes de IA falham em produção, as gravações duplicadas são o modo de falha que se esconde sob a maioria dos relatos de "o agente fez isso duas vezes".
Apidog aparece na parte de testes. Idempotência é algo que você incorpora à sua API e à camada de ferramentas do seu agente. O que você precisa depois é uma maneira de disparar a mesma requisição duas vezes e provar que a segunda não mudou nada, o que é um teste que você pode salvar e executar no CI.
Por que os agentes quebram a idempotência com mais frequência do que as pessoas
Três coisas sobre o tráfego do agente tornam as duplicatas comuns.
A primeira é o volume de repetições. Frameworks de agentes repetem agressivamente por padrão, porque falhas transientes de rede são a causa mais comum de uma execução quebrada. Nosso guia para a recuperação de erros de agentes aborda backoff e circuit breakers, e cada técnica nele aumenta o número de vezes que uma dada requisição atinge seu servidor.
A segunda é a ambiguidade de um timeout. Quando uma requisição expira, o cliente não sabe se o servidor a processou. Um `504` de um proxy pode significar que a gravação nunca aconteceu ou que aconteceu e a resposta foi perdida. Humanos geralmente verificam antes de tentar novamente. Agentes geralmente não o fazem, porque "verificar primeiro" é uma chamada de ferramenta extra que o modelo tem que decidir fazer.
A terceira é o loop. Um agente que falha em uma tarefa pode reiniciar a tarefa inteira, não apenas a etapa que falhou. Se a etapa um cria um pedido e a etapa quatro falha, um reinício ingênuo cria um segundo pedido. É aqui que os agentes multi-etapa diferem drasticamente de um script: o limite de repetição é difuso, e o modelo, não seu código, decide onde ele começa.
Junte isso e você terá a forma do problema. Não é que os agentes enviem requisições ruins. Eles enviam requisições corretas mais de uma vez.
O que a idempotência realmente garante
Uma operação é idempotente quando executá-la várias vezes tem o mesmo efeito que executá-la uma vez. `GET`, `PUT` e `DELETE` são definidos como idempotentes no RFC 9110, a especificação semântica HTTP. `POST` não é, e é exatamente por isso que as operações perigosas tendem a ser chamadas `POST`: criar um pedido, enviar uma mensagem, iniciar uma transferência.
Duas clarificações evitam muita confusão.
Idempotente não é o mesmo que seguro. Um método seguro não altera nada. `DELETE` é idempotente, mas destrutivo: chamá-lo cinco vezes deixa o recurso excluído, o mesmo que chamá-lo uma vez, mas o recurso ainda está desaparecido. Os agentes precisam que ambas as propriedades sejam tratadas separadamente, o que é o argumento que nossa postagem sobre chaves de API de privilégio mínimo para agentes faz do lado das credenciais.
Idempotente também não é o mesmo que resposta idêntica. A segunda chamada pode retornar o resultado armazenado da primeira, e pode retornar um código de status diferente. O que não deve mudar é o estado no servidor. Uma cobrança. Um pedido. Um e-mail.
Chaves de idempotência: o padrão que torna o POST seguro
A correção padrão é uma chave gerada pelo cliente enviada com a requisição. O servidor registra a chave junto com o resultado, e qualquer requisição posterior que contenha a mesma chave retorna o resultado registrado em vez de fazer o trabalho novamente.
A Stripe popularizou o cabeçalho, e a documentação de idempotência da Stripe ainda é a descrição mais clara da semântica. Há também um esforço do IETF para padronizá-lo como o campo de cabeçalho Idempotency-Key, o que vale a pena ler antes de inventar seu próprio nome de cabeçalho.
A requisição se parece com isto:
POST /v1/payments HTTP/1.1
Host: api.yourservice.com
Authorization: Bearer sk_live_...
Idempotency-Key: 9f2b7c14-6d3a-4b18-9d55-1e2a7c0b4f31
Content-Type: application/json
{
"amount": 4900,
"currency": "usd",
"customer_id": "cus_8812",
"description": "Pro plan, August"
}
A chave é um UUID. Ela não tem significado para o servidor além de "esta é a mesma operação lógica". O servidor a armazena, juntamente com uma impressão digital do corpo da requisição e a resposta que ela produziu.
Gerando uma chave que o agente pode reutilizar
É aqui que a maioria das implementações de agentes erra. Se o wrapper da ferramenta gerar um novo UUID a cada chamada, a chave muda a cada tentativa e a idempotência não faz nada. A chave deve estar vinculada à operação lógica, não à tentativa HTTP.
A regra: gere a chave quando o agente decidir executar uma ação e mantenha-a para cada nova tentativa dessa decisão.
import uuid
class PaymentTool:
def __init__(self, client):
self.client = client
self._keys = {}
def charge(self, task_id, step_id, amount, customer_id):
# One key per (task, step). Retries of the same step reuse it.
op = f"{task_id}:{step_id}"
if op not in self._keys:
self._keys[op] = str(uuid.uuid4())
return self.client.post(
"/v1/payments",
headers={"Idempotency-Key": self._keys[op]},
json={"amount": amount, "customer_id": customer_id},
)
Uma chave determinística também funciona, e sobrevive a reinícios de processo, o que um dicionário em memória não faz:
import hashlib
def idempotency_key(task_id: str, step_id: str, payload: dict) -> str:
raw = f"{task_id}|{step_id}|{sorted(payload.items())}"
return hashlib.sha256(raw.encode()).hexdigest()[:32]
Derive a chave da execução da tarefa e da etapa, nunca de um carimbo de data/hora ou de um valor aleatório regenerado por tentativa. Se o agente reiniciar a tarefa inteira e realmente pretende uma nova cobrança, o ID da tarefa muda e o mesmo acontece com a chave. Esse é o comportamento que você deseja.
O que o servidor precisa fazer
Lidar com o cabeçalho corretamente exige mais do que uma simples consulta. Uma implementação funcional faz quatro coisas:
- Ao chegar, tente reivindicar a chave. Insira-a em uma tabela com uma restrição de unicidade antes de fazer qualquer trabalho. Se a inserção falhar, outra tentativa é a proprietária.
- Se a chave existir e a impressão digital da requisição armazenada for diferente, rejeite com `422`. Mesma chave com corpo diferente significa um bug do cliente, e retornar silenciosamente o resultado antigo o esconderia.
- Se a chave existir e a primeira tentativa ainda estiver em andamento, retorne `409` para que o chamador recue em vez de competir.
- Quando o trabalho terminar, armazene o código de status e o corpo junto à chave, e então retorne-os para cada acesso posterior.
CREATE TABLE idempotency_records (
key TEXT PRIMARY KEY,
request_hash TEXT NOT NULL,
state TEXT NOT NULL, -- in_progress | completed
response_status INT,
response_body JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL
);
Defina um prazo de validade. Vinte e quatro horas cobrem qualquer janela de repetição realista, e manter as chaves para sempre transforma a tabela em um passivo. O Stripe expira as chaves após 24 horas, o que é um padrão razoável para copiar.
Testando que a segunda chamada não muda nada
Construir a idempotência é metade do trabalho. Provar que ela funciona é a outra metade, e é a metade que é ignorada, porque o caminho feliz parece idêntico, independentemente de a funcionalidade estar funcionando ou não.
O teste é simples de descrever: envie a requisição, capture o resultado, envie exatamente a mesma requisição novamente e afirme que o servidor não fez o trabalho duas vezes. A parte difícil é a última asserção, porque a resposta sozinha não lhe dirá. Duas cobranças bem-sucedidas retornam `200`.
Então, afirme sobre o estado, não sobre a resposta:
- O corpo da segunda resposta corresponde ao da primeira, incluindo o ID do recurso. Um novo ID significa que um novo recurso foi criado.
- Uma `GET` subsequente na coleção retorna um registro, não dois.
- Qualquer contador ou saldo foi movimentado uma vez.
No Apidog você pode configurar isso como um cenário de teste: o passo um envia o `POST` com uma `Idempotency-Key` fixa, o passo dois o repete, e o passo três lista o recurso e afirma a contagem. Salve o ID da resposta do passo um em uma variável e afirme que o passo dois retorna o mesmo valor. Como todo o cenário é armazenado, ele é executado no CI a cada mudança no caminho de pagamento, que é onde as regressões realmente aparecem. A mesma técnica se estende aos padrões mais amplos em nosso guia de teste de contrato de API.

Mais dois casos que valem a pena cobrir, porque eles pegam bugs reais:
- Mesma chave, corpo diferente. Espere `422`, não um sucesso silencioso.
- Duplicatas concorrentes. Dispare ambas as requisições ao mesmo tempo e confirme que exatamente uma vence. Isso detecta a restrição de unicidade ausente que um teste sequencial nunca revelaria.
Mocks também ajudam aqui. Se você ainda está construindo o agente e a API de pagamento ainda não existe, simule-a com uma resposta ciente da idempotência para que a lógica de repetição do agente seja exercitada cedo. Nossa postagem sobre por que os agentes de IA devem usar mocks em vez de produção apresenta o caso mais amplo para esse hábito.
Quando você não pode adicionar uma chave
Às vezes, a API não é sua e não possui suporte à idempotência. Você ainda tem opções, em ordem aproximada de preferência.
Torne a operação naturalmente idempotente. Um `PUT` para um caminho de recurso que o cliente escolhe é idempotente por construção: `PUT /orders/{client_order_id}`. Se você controla o design da API, prefira isso a `POST` mais um cabeçalho. Não precisa de tabela extra.
Verifique antes de escrever. Peça ao agente para consultar um registro existente com a mesma chave natural antes de criar um. Isso é mais fraco, porque uma corrida entre a verificação e a gravação ainda pode produzir dois registros, mas remove o caso comum de timeout.
Desduplique a jusante. Se a gravação for uma mensagem ou um evento, coloque a desduplicação no consumidor. Anexe um ID de mensagem estável e faça com que o consumidor descarte repetições. Essa é uma prática padrão em sistemas orientados a eventos e se alinha com as orientações em nosso guia de webhooks confiáveis.
Controle a ação. Para operações que são genuinamente irreversíveis e não podem ser tornadas idempotentes, coloque um humano na frente. Esse é o padrão de portão de aprovação de nossa postagem sobre guardrails de agentes de IA, e é a resposta certa quando o custo de uma duplicata é alto o suficiente.
Saiba qual execução fez o quê
A idempotência impede a duplicação. Ela não informa qual tentativa criou o registro, e essa é a pergunta que você recebe após um incidente.
Mantenha a identidade da execução anexada ao trabalho. Quando o agente é seu próprio serviço, isso significa o ID da tarefa e o ID da etapa da derivação da chave acima, registrados em cada tentativa. Quando o agente é um ambiente de execução de código que executa trabalhos atribuídos, a plataforma geralmente o mantém para você: no Sharkly, cada execução é anexada à Tarefa de onde veio, com seu estado de execução e resultado armazenados junto com o thread de comentários, para que uma gravação repetida possa ser rastreada até uma execução específica, em vez de uma tentativa anônima.

Uma lista de verificação antes de você lançar
- Toda ferramenta não idempotente que o agente pode chamar requer uma chave de idempotência, e o wrapper da ferramenta se recusa a enviar sem uma.
- As chaves são derivadas da tarefa e da etapa, não da tentativa.
- O servidor reivindica a chave antes de fazer o trabalho, não depois.
- Mesma chave com um payload diferente retorna um erro em vez da resposta em cache.
- Duplicatas concorrentes são tratadas por uma restrição de banco de dados, não por temporização da aplicação.
- Um teste salvo prova que a segunda chamada não muda nada, e ele é executado no CI.
- As chaves expiram em um cronograma e a tabela é limpa.
Analise essa lista e a história da cobrança dupla deixa de ser possível, o que significa que sua política de repetição pode se tornar mais agressiva, em vez de menos. Esse é o verdadeiro benefício: a idempotência é o que permite tornar um agente resiliente sem torná-lo perigoso.
Perguntas Frequentes
Preciso de chaves de idempotência para ferramentas somente leitura? Não. As requisições `GET` já são idempotentes e seguras, então tentar novamente uma custa um pouco de latência e nada mais. Reserve as chaves para chamadas que criam, cobram, enviam ou de outra forma alteram o estado.
Onde a chave deve ser gerada, no agente ou no wrapper da ferramenta? No wrapper da ferramenta, indexado pelos identificadores de tarefa e etapa do agente. Deixar o modelo gerar a chave é um erro: os modelos regeneram valores em novas tentativas e podem produzir colisões entre tarefas.
Qual código de status uma requisição repetida deve retornar? Retorne o status armazenado da chamada original, para que um segundo `POST` que inicialmente retornou `201` retorne `201` novamente com o mesmo corpo. Algumas APIs adicionam um cabeçalho como `Idempotent-Replay: true` para marcar a repetição, o que é útil para depuração e inofensivo para clientes que o ignoram.
Por quanto tempo as chaves devem ser mantidas? Vinte e quatro horas cobrem quase todas as janelas de repetição. Uma retenção mais longa raramente ajuda e faz a tabela crescer sem limites. Se um cliente tentar novamente após a janela, trate-a como uma nova operação.
Isso substitui as transações? Não. As chaves de idempotência impedem que requisições duplicadas produzam efeitos duplicados. As transações mantêm uma única requisição atômica. Você precisa de ambos, e a reivindicação da chave deve ser escrita na mesma transação que o trabalho, sempre que seu banco de dados permitir.
Como testar isso sem um provedor de pagamento real? Aponte o agente para um mock que implementa a semântica da chave, incluindo o `422` em caso de incompatibilidade de payload. Nosso guia sobre testar agentes de IA contra APIs mockadas aborda a configuração, e baixe o Apidog se você quiser o mock e o teste de repetição vivendo no mesmo projeto.
