Errores, idempotencia y cuotas
El sobre de error, qué significa cada código y qué es seguro reintentar.
El sobre
Todo error de /api/v2 tiene la misma forma:
{
"schemaVersion": "2.0",
"requestId": "…",
"error": {
"code": "INVALID_DOCUMENT",
"message": "…",
"details": []
}
}requestId viaja también en la cabecera X-Request-Id, y es lo que hay que dar al pedir soporte:
identifica la petición en los registros sin exponer nada del cuerpo.
Programa contra code, nunca contra message. El código es el contrato; el mensaje está escrito
para una persona y puede mejorar sin previo aviso.
Qué significa cada familia
| Estado | Qué pasó | ¿Reintentar? |
|---|---|---|
400 | La petición está mal formada | No, sin cambiarla |
401 | Credencial inválida o inservible | No |
403 | Falta un scope | No |
404 | No existe, o no es tuyo | No |
409 | Conflicto con un estado | Depende del código |
422 | Bien formada, inaceptable | No, sin corregir el contenido |
429 | Cuota o límite de frecuencia | Sí, respetando Retry-After |
5xx | Fallo nuestro | Sí, con la misma Idempotency-Key |
Un 404 no siempre significa que el recurso no existe: también responde así un recurso que existe
pero pertenece a otra empresa. Es intencionado — distinguirlos convertiría la ruta en una sonda
para averiguar qué RUC están registrados.
Códigos que verás a menudo
| Código | Estado | Qué hacer |
|---|---|---|
INVALID_TOKEN | 401 | La credencial no se reconoce en este edge |
INSUFFICIENT_SCOPE | 403 | La credencial es válida pero no tiene ese permiso |
INVALID_IDEMPOTENCY_KEY | 400 | Falta o no tiene la forma exigida |
IDEMPOTENCY_CONFLICT | 409 | Misma clave, cuerpo distinto. Ver abajo |
INVALID_DOCUMENT | 422 | El comprobante no pasa las validaciones. Mira details |
SUPPLIER_RUC_MISMATCH | 422 | El emisor del cuerpo no es la empresa de la credencial |
QUOTA_EXCEEDED | 429 | Sin cuota en el periodo. Espera a Retry-After |
DOCUMENT_NOT_SUBMITTABLE | 409 | El estado actual no admite un envío |
SUBMISSION_ALREADY_PENDING | 409 | Ya hay un envío en curso: espera, no lo repitas |
ARTIFACT_NOT_READY | 409 | El artefacto existe pero aún se está escribiendo |
TENANT_CAPABILITY_DISABLED | 403 | Esa capacidad no está habilitada para esta empresa |
Idempotencia
Todas las operaciones que crean o mueven algo exigen la cabecera Idempotency-Key. Las lecturas no
la aceptan, y POST /api/v2/documents/validations tampoco: no crea nada, así que no hay nada que
repetir.
La regla es simple y estricta:
- Misma clave, mismo cuerpo → te devuelve el resultado original. No se crea un segundo documento, no se consume cuota otra vez, no se manda un segundo envío a SUNAT.
- Misma clave, cuerpo distinto →
409 IDEMPOTENCY_CONFLICT. No se toca nada.
Esa segunda regla es lo que hace la cabecera útil. Sin ella, un reintento con un cuerpo modificado sobrescribiría en silencio, y no habría forma de saber cuál de las dos versiones quedó.
Genera la clave antes del primer intento y guárdala junto a tu comprobante. Una clave nueva por reintento no protege de nada: es una petición nueva a todos los efectos.
Cuando no sabes si llegó
Si tu petición se corta sin respuesta, repítela con la misma Idempotency-Key. O bien nunca llegó y
ahora se procesa, o bien llegó y te devuelve el resultado original. Es exactamente el caso para el que
existe la cabecera.
Cuotas
Se cobra una unidad por documento aceptado, en la misma transacción que lo hace durable. No hay una capa de cuota aparte, y eso importa: no existe una ventana en la que el documento esté creado y la cuota no, ni al revés.
De ahí se siguen tres cosas que conviene tener claras:
- Un replay no cobra dos veces. Lo impide una restricción única en la base de datos, no una rama de código.
- El cargo es por documento, no por petición ni por operación. Un mismo documento no puede generar dos cargos aunque se llegue a él por caminos distintos.
- Un comprobante rechazado por SUNAT ya consumió su unidad. Se cobra por aceptar y persistir, no por el veredicto fiscal. Recorrió el sistema entero.
QUOTA_EXCEEDED responde 429 con Retry-After apuntando al cierre del periodo, no a un retraso
fijo: reintentar antes solo puede volver a fallar. Y cuando llega, no se ha persistido nada.
Tu consumo está en GET /api/v2/organizations/usage (scope organizations:read), y en la consola en
la vista de uso de cada empresa.
Límites de frecuencia
POST /api/v2/documents/validations está limitada a 60 peticiones por minuto y credencial, en su
propio espacio de nombres: el uso de una empresa no consume el presupuesto de otra. Es una llamada
barata y sin efectos, pero no es gratis de servir.
El edge tiene además su propio límite, que responde 429 antes de que la petición llegue a la
aplicación.
Validar antes de emitir
La forma más barata de no encontrarse un 422 es no llegar a él:
POST /api/v2/documents/validationsCorre exactamente las mismas validaciones que la creación —no una versión reducida ni parecida:
la misma implementación— y devuelve valid, findings y los totals calculados. No crea documento,
no toma correlativo, no consume cuota y no acepta Idempotency-Key.
Pide el scope documents:validate, que no está implicado por documents:create. Una credencial
de desarrollo puede ensayar sin poder emitir.