XmlPeruDevDocs

Consultar comprobante

Estado de un comprobante. Es la vía para saber cómo terminó el envío, que corre de forma asíncrona.

Autenticación: token Bearer de empresa (cpe:sign).

Consideraciones

  • Si el comprobante tiene una baja, viene en void con su external_id y su estado. voided es el hecho —SUNAT aceptó la baja—: una baja pedida y aún sin resolver deja voided: false, porque puede ser rechazada y entonces el comprobante sigue declarado.
  • El external_id lo devolvió la emisión.
  • Es solo lectura: no envía ni reenvía nada. El envío corre por su cuenta.
  • resolved dice si SUNAT ya se pronunció. Mientras sea false, el comprobante sigue en camino: 01 es firmado y pendiente de salir, 03 es enviado esperando respuesta.
  • Sirve para todos los tipos: facturas, boletas, notas, resúmenes (RC/RA/RR) y guías (09/31).
  • Los resúmenes y las guías tardan más. SUNAT los responde en dos tiempos, así que pueden pasar minutos en resuelto: false. Es normal, no un fallo. El ticket que aparece es el identificador que SUNAT usa para esa espera — no tienes que hacer nada con él.
  • result trae lo que respondió SUNAT en el último intento: código, mensaje y la clasificación origin/action/reached_sunat. Es null mientras no haya habido intento. reached_sunat viaja siempre dentro de él, también en null — que significa «no se sabe», y es la señal de consultar antes de reintentar.
  • Tipos: number es un entero (no cadena, a diferencia de series y status_code); voided, voided_at y void viajan en todos los comprobantes, con null/false cuando no aplican.
  • Solo puedes consultar comprobantes de la empresa del token: los ajenos responden 403.
GEThttps://api.xmlperu.dev/v1/cpe/{external_id}Copiar

Si prefieres no preguntar: webhook

Consultar es barato (milisegundos), pero sigue siendo tu integración preguntando en bucle. Con el webhook lo invertimos: cuando el comprobante queda resuelto, te hacemos un POST con este mismo objeto document. El webhook es aviso, no contrato — este endpoint sigue siendo la fuente de verdad.

Cómo saber si terminó

Mira resolved, no status_code: es un solo booleano en vez de una lista de códigos que recordar.

status_code status resolved Qué significa
01 registered false Firmado, pendiente de salir
02 to_send false Firmado, esperando a que tú lo mandes
03 sent false Enviado, esperando a SUNAT
04 to_summarize false Boleta esperando el resumen del día
05 accepted true Aceptado
07 observed true Aceptado con observaciones
09 rejected true Rechazado

Un comprobante que se queda en 03 no está perdido: un proceso periódico consulta su estado real ante SUNAT hasta resolverlo. No lo reenvíes.

Por qué falló, cuando falló

result trae lo que respondió SUNAT en el último intento. Con el envío fuera del request, este es el único sitio donde leer el motivo de un rechazo:

"result": {
  "code": "2335",
  "message": "El XML no contiene información en el campo Total valor de venta",
  "errors": ["2335: total del valor de venta descuadrado"],
  "origin": "sunat",
  "action": "correct",
  "reached_sunat": true
}

action te dice qué hacer sin interpretar el texto: correct (arregla y emite de nuevo), retry (vuelve a encolar), review (consulta, no reenvíes). La clasificación completa está en Errores y respuestas.

Resúmenes y guías: el ticket no es asunto tuyo

Los resúmenes (RC/RA/RR) y las guías (09/31) no reciben el CDR al enviarse: SUNAT devuelve un ticket y procesa después. Alguien tiene que ir a preguntar el resultado — y ese alguien somos nosotros.

En cuanto el envío deja un ticket, arranca una consulta en segundo plano que insiste hasta obtener un veredicto y guarda lo que aprende. Tú consultas este endpoint, igual que para una factura, y ves resuelto: false hasta que haya desenlace.

El campo ticket viene en la respuesta solo como dato de trazabilidad, por si hay que cotejar una incidencia con SUNAT. No tienes que hacer nada con él.

Que la consulta salga de nuestra base y no de SUNAT tiene una consecuencia práctica: responde en milisegundos y puedes preguntar tan seguido como quieras. Si cada integración consultara a SUNAT por su cuenta, el servicio que ya es el cuello de botella recibiría muchísimo más tráfico del necesario.

Headers

NameTypeDescription
Acceptstringapplication/json
Content-Typestringapplication/json
AuthorizationstringBearer <token>. Genéralo desde tu panel, en Tokens de API.

Parámetros de URL

NameTypeDescription
external_id*stringIdentificador del comprobante devuelto al emitir.

Ejemplo de solicitud

Copiar
curl -X GET https://api.xmlperu.dev/v1/cpe/9c2f1b7e-0000-0000-0000-000000000000 \
  -H "Authorization: Bearer $TOKEN_EMPRESA" \
  -H "Accept: application/json"

Respuesta

200 OKapplication/json
{
  "success": true,
  "message": "Comprobante encontrado.",
  "data": {
    "document": {
      "external_id": "9c2f1b7e-…",
      "filename": "20123456789-01-F001-123",
      "document_type_id": "01",
      "series": "F001",
      "number": 123,
      "status_code": "05",
      "status": "accepted",
      "environment": "02",
      "environment_name": "production",
      "resolved": true,
      "hash": "a3f1…9e2c",
      "has_signed": true,
      "has_cdr": true,
      "ticket": null,
      "date_of_issue": "2026-07-27",
      "result": {
        "code": "0",
        "message": "La Factura numero F001-123, ha sido aceptada",
        "reached_sunat": true
      },
      "voided": false,
      "voided_at": null,
      "void": null
    }
  }
}
200 Todavía en caminoapplication/json
El comprobante está firmado y su envío en curso. Vuelve a consultar en unos segundos.
{
  "success": true,
  "message": "Comprobante encontrado.",
  "data": {
    "document": {
      "external_id": "9c2f1b7e-…",
      "filename": "20123456789-01-F001-123",
      "document_type_id": "01",
      "series": "F001",
      "number": 123,
      "status_code": "03",
      "status": "sent",
      "environment": "02",
      "environment_name": "production",
      "resolved": false,
      "hash": "a3f1…9e2c",
      "has_signed": true,
      "has_cdr": false,
      "ticket": null,
      "date_of_issue": "2026-07-27",
      "result": null,
      "voided": false,
      "voided_at": null,
      "void": null
    }
  }
}
200 Rechazado por SUNATapplication/json
El comprobante se resolvió, y el veredicto fue rechazo. El motivo está en resultado.
{
  "success": true,
  "message": "Comprobante encontrado.",
  "data": {
    "document": {
      "external_id": "9c2f1b7e-…",
      "filename": "20123456789-01-F001-123",
      "document_type_id": "01",
      "series": "F001",
      "number": 123,
      "status_code": "09",
      "status": "rejected",
      "environment": "02",
      "environment_name": "production",
      "resolved": true,
      "hash": "a3f1…9e2c",
      "has_signed": true,
      "has_cdr": false,
      "ticket": null,
      "date_of_issue": "2026-07-27",
      "result": {
        "code": "2335",
        "message": "El XML no contiene información en el campo Total valor de venta",
        "errors": ["2335: total del valor de venta descuadrado"],
        "origin": "sunat",
        "action": "correct",
        "reached_sunat": true
      },
      "voided": false,
      "voided_at": null,
      "void": null
    }
  }
}
404 No existeapplication/json
{
  "success": false,
  "message": "El comprobante no existe."
}
403 De otra empresaapplication/json
{
  "success": false,
  "message": "El comprobante no pertenece a esta empresa."
}