Idempotencia

Una red se cae, salta un timeout, no recibes la respuesta. ¿Reintentas? Con una Idempotency-Key, sí, sin miedo: la misma operación no se procesa dos veces. Es opcional; si no mandas la llave, todo funciona como siempre.

5 min de lectura

Qué es

La idempotencia garantiza que repetir una petición tenga el mismo efecto que hacerla una sola vez. Tú generas un identificador único por operación y lo envías en el header Idempotency-Key. Si una petición con esa llave ya se procesó, la API devuelve la misma respuesta de la primera vez en lugar de volver a ejecutar la acción.

La llave vive 24 horas. Dentro de esa ventana, cualquier reintento con la misma llave es seguro.

Piensa en la llave como "una por intención de negocio", no "una por reintento". El reintento del mismo envío reúsa la misma llave. Dos envíos distintos llevan dos llaves distintas.

Cómo usarla

  1. Genera un identificador único por operación. Recomendamos un UUID v4.
  2. Envíalo en el header Idempotency-Key de la petición.
  3. Si la operación falla por red o por timeout, reinténtala con la misma llave y el mismo cuerpo.
  4. Para una operación distinta, usa una llave nueva.

La llave acepta de 1 a 255 caracteres del conjunto A-Z a-z 0-9 _ -. El alcance es por organización: tu llave nunca colisiona con la de otra cuenta ni con otro endpoint.

Ejemplos

Solo agregas un header. El cuerpo y el resto de la petición no cambian.

curl -X POST https://developers.hablame.co/api/v6/urlshortener/links \
  -H "Authorization: Bearer hk_TU_API_KEY" \
  -H "Idempotency-Key: 5f3b2c10-9a7e-4b2d-8c1f-0a1b2c3d4e5f" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hablame.co/promo"}'

Respuestas

La API te dice qué pasó con cada llave en el header Idempotency-Status.

Nueva: se ejecutó

La primera vez. La operación corre y se guarda la respuesta.

HTTP
HTTP/1.1 201 Created
Idempotency-Status: created

{ "success": true, "data": { "code": "aZ3kPq1", "domain": "hbl.li" } }

Repetida: replay

Reintento con la misma llave y el mismo cuerpo. No se vuelve a ejecutar: recibes la misma respuesta.

HTTP
HTTP/1.1 201 Created
Idempotency-Status:   replayed
Idempotency-Replayed: true

{ "success": true, "data": { "code": "aZ3kPq1", "domain": "hbl.li" } }

En proceso: 409

Enviaste la misma llave mientras la primera petición todavía se está procesando. Espera y reintenta respetando el header Retry-After.

HTTP
HTTP/1.1 409 Conflict
Retry-After: 2

{ "success": false, "error": { "code": "IDEMPOTENCY_IN_PROGRESS" } }

Reuso con otro cuerpo: 422

Usaste una llave que ya existe pero con un cuerpo distinto. Casi siempre es un error del cliente: una llave identifica una operación, no varias. Usa una llave nueva.

HTTP
HTTP/1.1 422 Unprocessable Entity

{ "success": false, "error": { "code": "IDEMPOTENCY_KEY_REUSED" } }

Llave inválida: 400

La llave está vacía, es muy larga o trae caracteres fuera de A-Z a-z 0-9 _ -.

HTTP
HTTP/1.1 400 Bad Request

{ "success": false, "error": { "code": "IDEMPOTENCY_KEY_INVALID" } }

Recomendaciones para reintentos automáticos

  • Genera la llave antes del primer intento. Guárdala y reúsala en cada reintento del mismo evento. Si generas una llave nueva por intento, pierdes la protección.
  • Trata el 409 como "reintenta luego". Espera el Retry-After y vuelve a intentar con la misma llave.
  • Trata el 422 como un error de tu lado: cambiaste el cuerpo bajo la misma llave. Corrige la lógica, no reintentes en bucle.
  • Usa retroceso exponencial con variación aleatoria para los reintentos de red, igual que con los límites de uso.

Dos detalles importantes. El replay solo aplica a respuestas exitosas: si la primera petición falló con un error (por ejemplo un dato inválido), un reintento se vuelve a ejecutar en lugar de devolver el error guardado. Y la garantía es de mejor esfuerzo: ante una indisponibilidad de nuestro lado, la petición se procesa de todas formas. Para operaciones críticas, combina la llave con tu propia deduplicación.

Dónde aplica hoy

La cabecera está documentada en cada endpoint que la admite. Hoy la aceptan:

  • POST /api/v6/urlshortener/links
  • GET /api/v6/numberinsight/{number}
  • POST /api/v6/numberinsight/batch
  • POST /api/v6/tts/synthesize
  • POST /api/v6/callblasting/calls
  • POST /api/v6/callblasting/calls/audio