Ir al contenido

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.

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" por 1).
  • La repetición muestra el documento en su estado actual (puede haber avanzado a accepted), con el mismo id, 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 es 409 idempotency_key_reused.

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.

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
)
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:

Ventana de terminal
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".
  • resubmit (corregir un rechazo) no lleva Idempotency-Key: reenviar algo que ya no está rejected responde 409 invalid_state, así que un reintento no puede reenviar dos veces.
  • retry (reintentar un failed) es idempotente por estado: sobre un documento que ya espera transmisión o se está transmitiendo responde 202 con el documento actual e Idempotent-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).

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.