Idempotencia y reintentos
Una llamada puede fallar sin que sepas si llegó: se corta la red, vence el tiempo, el servidor responde 503. Si reintentas sin cuidado, podrías emitir dos facturas por una venta. La cabecera Idempotency-Key lo evita.
Cómo funciona
Sección titulada «Cómo funciona»Toda emisión exige Idempotency-Key: de 1 a 128 caracteres ASCII visibles (por ejemplo, un UUID o el número interno de tu venta). Sin ella, 400 missing_idempotency_key; con una inválida, 400 invalid_idempotency_key.
| Reenvías… | Resultado |
|---|---|
| La misma clave y el mismo cuerpo | 202 con el mismo documento, la cabecera Idempotent-Replayed: true y el mismo Location. No se emite otro. |
| La misma clave y un cuerpo distinto | 409 idempotency_key_reused. |
| Una clave nueva | Un documento nuevo. |
- El cuerpo se compara por su contenido: reordenar campos o cambiar los espacios no cuenta como cuerpo distinto; cambiar un valor sí (incluso
"1"por1). - La repetición muestra el documento en su estado actual (puede haber avanzado a
accepted), con el mismoid, número y enlaces. - La clave es por empresa y ambiente, y no vence: queda ligada para siempre a su documento. Por eso nunca la reutilices para otra venta.
- En el sandbox, un escenario forzado (
Foliios-Sandbox-Scenario) entra en la huella: la misma clave con otro escenario es409 idempotency_key_reused.
Cuándo se libera la clave
Sección titulada «Cuándo se libera la clave»Si la solicitud falla antes de consumir nada (contenido inválido, resolución desconocida o vencida, número ocupado, empresa sin configurar), foliios libera la clave y puedes corregir el cuerpo y reintentar con la misma. Ante un error técnico (5xx) la clave queda: el documento pudo haberse numerado, y solo el mismo cuerpo puede reintentar.
La clave sale de la venta
Sección titulada «La clave sale de la venta»Genera la clave a partir de la venta, no en cada intento: así cualquier reintento, incluso tras reiniciar tu programa, usa la misma.
from foliios_inicio import EMPRESA, llamar
def emitir(venta, cuerpo): return llamar( "POST", f"/v1/companies/{EMPRESA}/documents", cuerpo, {"Idempotency-Key": f"venta-{venta['id']}"}, # estable entre intentos )Qué reintentar y cómo
Sección titulada «Qué reintentar y cómo»| Respuesta | ¿Reintentar? | Cómo |
|---|---|---|
| Error de red o tiempo agotado sin respuesta | Sí | Misma clave y mismo cuerpo. |
503 (request_timeout, temporarily_unavailable) |
Sí | Misma clave y cuerpo; espera Retry-After. |
429 (rate_limited) |
Sí | Espera Retry-After y reintenta con pausa creciente. |
409 concurrent_modification |
Sí | Otro proceso cambió el documento a la vez. |
400, 422 |
No | Corrige el cuerpo. La clave quedó libre: puedes usar la misma. |
401, 403 |
No | Problema de clave o permisos. |
409 idempotency_key_reused |
No | Usaste la clave con otro cuerpo: es un error de tu programa. |
Los límites de frecuencia y de cuota del sandbox están en escenarios, sección «Límites».
import time
def con_reintentos(hacer, intentos=5): espera = 1 for intento in range(intentos): try: estado, cuerpo, cabeceras = hacer() except OSError: # sin respuesta: la red falló estado, cuerpo, cabeceras = None, None, {} if estado is not None and estado not in (429, 503, 409): return estado, cuerpo, cabeceras if estado == 409 and cuerpo.get("code") != "concurrent_modification": return estado, cuerpo, cabeceras time.sleep(int(cabeceras.get("Retry-After", espera))) espera = min(espera * 2, 30) raise RuntimeError("sigue fallando tras varios intentos")Con curl, sencillamente repite el mismo comando con la misma cabecera:
curl -s -i -X POST "https://api.foliios.com/v1/companies/$FOLIIOS_COMPANY_ID/documents" \ -H "Authorization: Bearer $FOLIIOS_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: venta-1042" -d @factura.json# La segunda vez: 202 e "Idempotent-Replayed: true", con el mismo "id".Otras operaciones
Sección titulada «Otras operaciones»resubmit(corregir un rechazo) no llevaIdempotency-Key: reenviar algo que ya no estárejectedresponde409 invalid_state, así que un reintento no puede reenviar dos veces.retry(reintentar unfailed) es idempotente por estado: sobre un documento que ya espera transmisión o se está transmitiendo responde202con el documento actual eIdempotent-Replayed: true, sin encolar otro trabajo.deliver(reenviar el correo al comprador) no es idempotente: cada llamada es un correo más.- La transcripción de una factura de papel no lleva
Idempotency-Key: el papel es la clave (factura de papel).
Cómo comprobarlo
Sección titulada «Cómo comprobarlo»Emite dos veces la misma venta con la misma clave y verifica que el segundo id es igual al primero y que llega Idempotent-Replayed: true. Esta comprobación la declara tu programa: la verificación de integración no puede comprobarla.