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.
Comprobar un reintento
Sección titulada «Comprobar un reintento»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:
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.
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.headersEspera el mismo 201, Misma respuesta e Idempotent-Replayed: true. Esa respuesta guardada puede mostrar un estado anterior: consulta el documento para conocer el vigente.
Recuperar una respuesta perdida
Sección titulada «Recuperar una respuesta perdida»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:
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.jsonComprueba 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.
Qué reintentar
Sección titulada «Qué reintentar»| 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.