Ir al contenido

Reintentos e idempotencia

Toda emisión, nota y baja lleva la cabecera Idempotency-Key: identifica la operación para reintentarla sin duplicarla. Se recomienda un UUID; admite de 1 a 128 caracteres [A-Za-z0-9._:-].

Situación Respuesta
Clave nueva Se procesa y se guarda la respuesta
Misma clave y misma solicitud, dentro de 24 horas desde la respuesta final La respuesta guardada, con la cabecera Idempotent-Replayed: true
Misma clave con otro método, ruta, empresa o cuerpo 422 idempotency_key_reused
Misma clave, con la primera solicitud aún en curso 409 idempotency_in_progress, también si el cuerpo es otro
Sin cabecera, o con otro formato 400 idempotency_key_required o 400 idempotency_key_invalid
Misma clave después de 24 horas desde la respuesta final Se procesa como una solicitud nueva

La clave vale para toda la cuenta y el ambiente, aunque rotes la API key. Un reintento conserva los mismos bytes del cuerpo: cambiar el orden de las propiedades o la forma de un número cuenta como otro cuerpo.

external_id (opcional, hasta 64 caracteres) identifica la venta en tu sistema y es único por empresa y ambiente entre los documentos no rechazados. Repetirlo responde 409 external_id_conflict, con el document_id del documento existente.

Con la boleta de Primeros pasos, repite solo la llamada, sin regenerar variables ni editar boleta.json. Si cerraste la terminal, repite el bloque de preparar la terminal y recupera la operación guardada:

Ventana de terminal
IDEMPOTENCY_KEY=$(cat boleta-idempotencia.txt)
EXTERNAL_ID=$(campo boleta.json external_id)
COMPANY_ID=$(campo boleta.json company_id)
DOCUMENT_ID=$(campo boleta-respuesta.json id)

El cuerpo conserva la empresa y la identidad, y boleta-idempotencia.txt conserva la clave. No vuelvas a emitir la boleta para recuperar una operación: generaría una identidad nueva. En una aplicación guarda estos datos de forma transaccional junto con la solicitud pendiente; los archivos solo sirven para este ensayo.

Ventana de terminal
curl -sS -D repeticion.headers -o repeticion.json -w '%{http_code}\n' \
-H "Authorization: Bearer $BOLETURA_API_KEY" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H 'Content-Type: application/json' \
--data-binary @boleta.json "$BOLETURA_BASE_URL/v1/documents"
cmp boleta-respuesta.json repeticion.json && echo 'Misma respuesta'
grep -i '^idempotent-replayed' repeticion.headers

Espera el mismo 201, Misma respuesta e Idempotent-Replayed: true. Esa respuesta guardada puede mostrar un estado anterior: consulta el documento para conocer el vigente.

Si una conexión se corta o agota el tiempo, el resultado es desconocido. Consulta primero la identidad de negocio, sin generar otra clave ni otro external_id:

Ventana de terminal
curl -sS -G -o recuperacion.json -w '%{http_code}\n' \
-H "Authorization: Bearer $BOLETURA_API_KEY" \
--data-urlencode "company_id=$COMPANY_ID" \
--data-urlencode "external_id=$EXTERNAL_ID" \
"$BOLETURA_BASE_URL/v1/documents"
python -m json.tool recuperacion.json

Comprueba antes de llamar que $EXTERNAL_ID tiene valor. Un external_id en blanco responde 422 filter_invalid, pero sin el parámetro la consulta es válida y lista documentos de otras ventas; en TypeScript, un undefined lo quita de la URL. Con data no vacío recupera el id y consulta su detalle. El filtro puede devolver el rechazado y su reemplazo: conserva ambos y revisa replaces_document_id; no elijas uno sin mirar su estado. Usa siempre empresa e identidad: la misma identidad puede existir en otro RUC.

Si no aparece, el primer intento puede seguir en curso. Reintenta el mismo método, ruta, empresa, cuerpo byte por byte e Idempotency-Key, con espera creciente. 409 idempotency_in_progress pide esperar; 422 idempotency_key_reused indica que la clave ya pertenece a otra solicitud confirmada, incluso si solo cambiaste espacios u orden del JSON. Para reintentar la original recupera sus bytes exactos. Si era otra operación, concilia primero la anterior y asigna una clave distinta a la nueva; no cambies la clave para eludir el conflicto de una emisión incierta.

La respuesta idempotente se conserva 24 horas desde la respuesta final. Pasado ese plazo, consulta antes de enviar: la clave se puede procesar como nueva. external_id sigue impidiendo duplicados por empresa y ambiente entre documentos no rechazados: 409 external_id_conflict incluye document_id para consultar. Un rechazado se corrige con otro documento, nueva clave y replaces_document_id; puede conservar el external_id, pero nunca reutiliza el número. Un fallo de entrega no exige emitir otro comprobante.

Situación Reintentar Cómo
Falla de red, tiempo de espera o respuesta perdida Sí Con el mismo método, ruta, cuerpo e Idempotency-Key. Si la primera solicitud se completó, llega la respuesta guardada con Idempotent-Replayed: true
409 idempotency_in_progress Sí Con la misma clave, unos segundos después
429 rate_limited Sí Después de los segundos de Retry-After
500 internal_error y 503, como database_unavailable Sí Con la misma clave y una espera creciente
400, 401, 403, 404, 413, 415, 422 y los demás 409 No Se corrige la solicitud. Los errores no se guardan: después de un 422, el cuerpo corregido se envía con la misma clave

La espera recomendada es exponencial con una variación aleatoria: desde 1 segundo hasta 1 minuto entre intentos. Las lecturas y descargas no llevan clave y se reintentan sin más. El envío a SUNAT no se reintenta desde tu sistema: lo reintenta la plataforma, y su resultado llega al documento y por webhook.