Idempotencia
Una red se cae, un timeout salta, no recibes la respuesta. ¿Reintentas?
Con una Idempotency-Key sí, sin miedo: la misma operación no
se procesa dos veces. Nada de operaciones repetidas, dobles envíos ni
recursos duplicados. Es opcional: si no mandas la llave, todo sigue igual que hoy.
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 te 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
- Genera un identificador único por operación. Recomendamos un UUID v4.
- Envíalo en el header
Idempotency-Keyde la petición. - Si la operación falla por red o timeout, reinténtala con la misma llave y el mismo cuerpo.
- 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
curl -X POST https://api.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"}'
PHP
$key = bin2hex(random_bytes(16)); // una por operación; consérvala para reintentos
$ch = curl_init('https://api.hablame.co/api/v6/urlshortener/links');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer hk_tu_api_key',
'Idempotency-Key: ' . $key,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['url' => 'https://hablame.co/promo']),
]);
$response = curl_exec($ch);
JavaScript (Node)
import { randomUUID } from 'node:crypto';
const key = randomUUID(); // una por operación; reúsala en los reintentos
const res = await fetch('https://api.hablame.co/api/v6/urlshortener/links', {
method: 'POST',
headers: {
'Authorization': 'Bearer hk_tu_api_key',
'Idempotency-Key': key,
'Content-Type': 'application/json',
},
body: JSON.stringify({ 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. Header: Idempotency-Status: created.
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: recibís la misma respuesta. Headers: Idempotency-Status: replayed e Idempotency-Replayed: true.
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 todavía se está procesando. Espera y reinténtala: respeta el header Retry-After.
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. Es casi siempre un bug del cliente: una llave identifica una operación, no varias. Usá una llave nueva.
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/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-Aftery vuelve a intentar con la misma llave. - Trata el 422 como un bug. Significa que cambiaste el cuerpo bajo la misma llave: corrige tu lógica, no reintentes en loop.
- Backoff exponencial con jitter para los reintentos de red, igual que con el rate limiting.
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 intentar, no devuelve el error guardado. Y la garantía es de máximo esfuerzo: ante una indisponibilidad de nuestra infraestructura de control, la petición se procesa de todas formas. Para operaciones críticas combina la llave con tu propia deduplicación.