Idempotencia de Agentes IA: Evita Dobles Cobros en Reintentos

Los reintentos de los agentes crean cargos y pedidos duplicados. Aprende cómo funcionan las claves de idempotencia, cómo generarlas por cada paso de la tarea y cómo probar que la segunda llamada no cambia nada.

Ashley Innocent

Ashley Innocent

26 August 2026

Idempotencia de Agentes IA: Evita Dobles Cobros en Reintentos

Apidog para empresas

Despliegue local

SSO & RBAC

Conforme con SOC 2

Explorar Apidog Enterprise

Su agente llamó al endpoint de pago. La solicitud se procesó, el cargo se realizó y luego la respuesta agotó el tiempo de espera al regresar. El agente nunca vio un 200, por lo que hizo lo que le indicó en caso de fallo: lo reintentó. Ahora al cliente se le ha cobrado dos veces, y nada en sus registros parece un error.

Este es el modo de fallo que separa a los agentes de los clientes API ordinarios. Un humano que hace clic en "Pagar" una vez ve un indicador de carga y espera. Un agente en un bucle de reintentos ve silencio y vuelve a intentarlo, a veces tres o cuatro veces seguidas, más rápido de lo que cualquier persona podría. Cada política de reintentos que añade para hacer que el agente sea más fiable también aumenta la probabilidad de escrituras duplicadas. La solución es la idempotencia: hacer que una solicitud repetida produzca el mismo resultado que una sola solicitud.

Esta guía cubre lo que significa la idempotencia a nivel HTTP, cómo generar claves que un agente pueda reutilizar, qué debe almacenar el servidor para respetarlas y cómo probar todo antes de que a un cliente real se le cobre dos veces. Si no ha leído nuestro pilar sobre por qué los agentes de IA fallan en producción, las escrituras duplicadas son el modo de fallo que se esconde detrás de la mayoría de los informes de "el agente lo hizo dos veces".

Apidog aparece en la mitad de prueba de esto. La idempotencia es algo que construye en su API y en la capa de herramientas de su agente. Lo que necesita después es una forma de enviar la misma solicitud dos veces y probar que la segunda no cambió nada, lo cual es una prueba que puede guardar y ejecutar en CI.

Por qué los agentes rompen la idempotencia más a menudo que las personas

Tres aspectos del tráfico de agentes hacen que las duplicaciones sean comunes.

El primero es el volumen de reintentos. Los frameworks de agentes reintentan agresivamente por defecto porque los fallos transitorios de red son la causa más común de una ejecución fallida. Nuestra guía de recuperación de errores de agentes explica el retroceso exponencial (backoff) y los disyuntores (circuit breakers), y cada técnica en ella aumenta el número de veces que una solicitud determinada llega a su servidor.

El segundo es la ambigüedad de un tiempo de espera. Cuando una solicitud agota el tiempo de espera, el cliente no sabe si el servidor la procesó. Un 504 de un proxy podría significar que la escritura nunca ocurrió o que ocurrió y la respuesta se perdió. Los humanos suelen verificar antes de reintentar. Los agentes generalmente no lo hacen, porque "verificar primero" es una llamada a una herramienta adicional que el modelo tiene que decidir realizar.

El tercero es el bucle. Un agente que falla en una tarea puede reiniciar toda la tarea, no solo el paso fallido. Si el paso uno crea un pedido y el paso cuatro falla, un reinicio ingenuo crea un segundo pedido. Aquí es donde los agentes de múltiples pasos difieren marcadamente de un script: el límite de reintento es difuso, y el modelo, no su código, decide dónde comienza.

Junte todo esto y obtendrá la forma del problema. No es que los agentes envíen solicitudes incorrectas. Envían solicitudes correctas más de una vez.

Lo que realmente garantiza la idempotencia

Una operación es idempotente cuando realizarla muchas veces tiene el mismo efecto que realizarla una sola vez. GET, PUT y DELETE se definen como idempotentes en RFC 9110, la especificación de semántica HTTP. POST no lo es, razón por la cual las operaciones peligrosas suelen ser llamadas POST: crear un pedido, enviar un mensaje, iniciar una transferencia.

Dos aclaraciones evitan mucha confusión.

Idempotente no es lo mismo que seguro. Un método seguro no cambia nada. DELETE es idempotente pero destructivo: llamarlo cinco veces deja el recurso eliminado, lo mismo que llamarlo una vez, pero el recurso sigue desaparecido. Los agentes necesitan que ambas propiedades se clasifiquen por separado, que es el argumento que nuestro post sobre claves API de menor privilegio para agentes plantea desde el lado de las credenciales.

Idempotente tampoco es lo mismo que respuesta idéntica. La segunda llamada puede devolver el resultado almacenado de la primera, y puede devolver un código de estado diferente. Lo que no debe cambiar es el estado en el servidor. Un cargo. Un pedido. Un correo electrónico.

Claves de idempotencia: el patrón que hace que POST sea seguro

La solución estándar es una clave generada por el cliente que se envía con la solicitud. El servidor registra la clave junto con el resultado, y cualquier solicitud posterior que lleve la misma clave devuelve el resultado registrado en lugar de realizar el trabajo de nuevo.

Stripe popularizó el encabezado, y la documentación de idempotencia de Stripe sigue siendo la descripción más clara de la semántica. También hay un esfuerzo del IETF para estandarizarlo como el campo de encabezado Idempotency-Key, que vale la pena leer antes de inventar su propio nombre de encabezado.

La solicitud se ve así:

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"
}

La clave es un UUID. No tiene ningún significado para el servidor más allá de "esta es la misma operación lógica". El servidor la almacena, junto con una huella digital del cuerpo de la solicitud y la respuesta que produjo.

Generando una clave que el agente puede reutilizar

Aquí es donde la mayoría de las implementaciones de agentes fallan. Si el envoltorio de la herramienta genera un nuevo UUID en cada llamada, la clave cambia en cada reintento, y la idempotencia no hace nada. La clave debe estar ligada a la operación lógica, no al intento HTTP.

La regla: genere la clave cuando el agente decida realizar una acción, y manténgala para cada reintento de esa decisión.

import uuid

class PaymentTool:
    def __init__(self, client):
        self.client = client
        self._keys = {}

    def charge(self, task_id, step_id, amount, customer_id):
        # Una clave por (tarea, paso). Los reintentos del mismo paso la reutilizan.
        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},
        )

Una clave determinista también funciona, y sobrevive a los reinicios del proceso, lo que un diccionario en memoria no hace:

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 la clave de la ejecución de la tarea y del paso, nunca de una marca de tiempo o un valor aleatorio regenerado por intento. Si el agente reinicia toda la tarea y realmente tiene la intención de realizar un nuevo cargo, el ID de la tarea cambia y también lo hace la clave. Ese es el comportamiento que desea.

Lo que el servidor tiene que hacer

Manejar el encabezado correctamente requiere más que una simple búsqueda. Una implementación funcional hace cuatro cosas:

  1. Al llegar, intente reclamar la clave. Insértela en una tabla con una restricción única antes de realizar cualquier trabajo. Si la inserción falla, otro intento la posee.
  2. Si la clave existe y la huella digital de la solicitud almacenada difiere, rechace con 422. La misma clave con un cuerpo diferente significa un error del cliente, y devolver silenciosamente el resultado antiguo lo ocultaría.
  3. Si la clave existe y el primer intento aún está en curso, devuelva 409 para que el llamante se retire en lugar de competir.
  4. Cuando el trabajo finalice, almacene el código de estado y el cuerpo asociados a la clave, luego devuélvalos para cada acceso posterior.
CREATE TABLE idempotency_records (
  key             TEXT PRIMARY KEY,
  request_hash    TEXT NOT NULL,
  state           TEXT NOT NULL,      -- en_progreso | completado
  response_status INT,
  response_body   JSONB,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  expires_at      TIMESTAMPTZ NOT NULL
);

Establezca un vencimiento. Veinticuatro horas cubren cualquier ventana de reintento realista, y mantener las claves para siempre convierte la tabla en un pasivo. Stripe expira las claves después de 24 horas, lo cual es un valor predeterminado razonable para copiar.

Probando que la segunda llamada no cambia nada

Construir la idempotencia es la mitad del trabajo. Probar que se mantiene es la otra mitad, y es la mitad que se omite, porque el camino feliz parece idéntico funcione o no la característica.

La prueba es sencilla de describir: envíe la solicitud, capture el resultado, envíe exactamente la misma solicitud de nuevo y afirme que el servidor no realizó el trabajo dos veces. La parte difícil es la última afirmación, porque la respuesta por sí sola no se lo dirá. Dos cargos exitosos devuelven 200.

Así que afirme sobre el estado, no sobre la respuesta:

En Apidog puede configurar esto como un escenario de prueba: el paso uno envía el POST con una Idempotency-Key fija, el paso dos lo repite y el paso tres lista el recurso y afirma el recuento. Guarde el ID de respuesta del paso uno en una variable y afirme que el paso dos devuelve el mismo valor. Debido a que todo el escenario está almacenado, se ejecuta en CI en cada cambio en la ruta de pago, que es donde realmente aparecen las regresiones. La misma técnica se extiende a los patrones más amplios en nuestra guía de pruebas de contratos API.

Dos casos más que vale la pena cubrir, porque detectan errores reales:

La simulación (mocking) también ayuda aquí. Si todavía está construyendo el agente y la API de pago aún no existe, simúlela con una respuesta que tenga en cuenta la idempotencia para que la lógica de reintentos del agente se ejercite temprano. Nuestro post sobre por qué los agentes deberían usar simulaciones en lugar de producción presenta un argumento más amplio para ese hábito.

Cuando no se puede añadir una clave

A veces la API no es suya y no tiene soporte para la idempotencia. Aún tiene opciones, en un orden aproximado de preferencia.

Haga que la operación sea naturalmente idempotente. Un PUT a una ruta de recurso que el cliente elige es idempotente por construcción: PUT /orders/{client_order_id}. Si usted controla el diseño de la API, prefiera esto sobre POST más un encabezado. No necesita una tabla adicional.

Verifique antes de escribir. Haga que el agente consulte un registro existente con la misma clave natural antes de crear uno nuevo. Esto es más débil, porque una condición de carrera entre la verificación y la escritura aún puede producir dos registros, pero elimina el caso común de tiempo de espera.

Desduplique en el lado del consumidor. Si la escritura es un mensaje o un evento, coloque la deduplicación en el consumidor. Adjunte un ID de mensaje estable y haga que el consumidor descarte las repeticiones. Esta es una práctica estándar en sistemas impulsados por eventos y se combina con la orientación de nuestra guía de webhooks fiables.

Bloquee la acción. Para operaciones que son genuinamente irreversibles y no pueden hacerse idempotentes, ponga a un humano delante. Ese es el patrón de "puerta de aprobación" de nuestro post sobre barreras de seguridad para agentes de IA, y es la respuesta correcta cuando el costo de una duplicación es lo suficientemente alto.

Sepa qué ejecución hizo qué

La idempotencia detiene el duplicado. No le dice qué intento creó el registro, y esa es la pregunta que se le hace después de un incidente.

Mantenga la identidad de la ejecución asociada al trabajo. Cuando el agente es su propio servicio, eso significa el ID de tarea y el ID de paso de la derivación de clave anterior, registrados con cada intento. Cuando el agente es un tiempo de ejecución de codificación que ejecuta trabajo asignado, la plataforma generalmente lo guarda para usted: en Sharkly, cada ejecución está asociada a la Tarea de la que proviene, con su estado de ejecución y resultado almacenados junto con el hilo de comentarios, de modo que una escritura repetida se remonta a una ejecución específica en lugar de a un reintento anónimo.

Una lista de verificación antes de implementar

Siga esa lista y la historia del doble cargo deja de ser posible, lo que significa que su política de reintentos puede volverse más agresiva en lugar de menos. Esa es la verdadera recompensa: la idempotencia es lo que le permite hacer que un agente sea resiliente sin hacerlo peligroso.

Preguntas frecuentes

¿Necesito claves de idempotencia para herramientas de solo lectura? No. Las solicitudes GET ya son idempotentes y seguras, por lo que reintentar una le cuesta un poco de latencia y nada más. Reserve las claves para las llamadas que crean, cargan, envían o de alguna otra manera cambian el estado.

¿Dónde debe generarse la clave, en el agente o en el envoltorio de la herramienta? En el envoltorio de la herramienta, utilizando los identificadores de tarea y paso del agente. Dejar que el modelo genere la clave es un error: los modelos regeneran valores en los reintentos y pueden producir colisiones entre tareas.

¿Qué código de estado debería devolver una solicitud repetida? Devuelva el estado almacenado de la llamada original, de modo que un segundo POST que primero devolvió 201 devuelva 201 de nuevo con el mismo cuerpo. Algunas API añaden un encabezado como Idempotent-Replay: true para marcar la repetición, lo cual es útil para la depuración e inofensivo para los clientes que lo ignoran.

¿Cuánto tiempo deben conservarse las claves? Veinticuatro horas cubren casi cualquier ventana de reintento. Una retención más larga rara vez ayuda y hace que la tabla crezca sin límite. Si un cliente reintenta después de la ventana, trátelo como una nueva operación.

¿Esto reemplaza las transacciones? No. Las claves de idempotencia evitan que las solicitudes duplicadas produzcan efectos duplicados. Las transacciones mantienen una única solicitud atómica. Necesita ambos, y la reclamación de la clave debe escribirse en la misma transacción que el trabajo siempre que su base de datos lo permita.

¿Cómo pruebo esto sin un proveedor de pagos real? Apunte el agente a un simulacro (mock) que implemente la semántica de la clave, incluyendo el 422 en caso de desajuste de la carga útil. Nuestra guía sobre pruebas de agentes de IA contra API simuladas cubre la configuración, y descargue Apidog si desea que el simulacro y la prueba de reintento vivan en el mismo proyecto.

Practica el diseño de API en Apidog

Descubre una forma más fácil de construir y usar APIs