XmlPeruDevDocs

Enviar a SUNAT

Manda a SUNAT un XML ya firmado y devuelve su resultado. Es la segunda mitad del flujo generar → enviar.

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

Consideraciones

  • Cierra el flujo: generar firma y enviar manda y te devuelve el resultado.
  • Puedes declarar el entorno en la ruta: /api/pro/demo/cpe/enviar o /api/pro/produccion/cpe/enviar (y el alias enviar-demo). Si no coincide con el de tu empresa respondemos 409 y no se envía nada. Ver generar.
  • Dos formas de identificar el comprobante: por external_id (recomendado, usa el XML ya almacenado) o enviando el nombre_xml_firmado + contenido_xml_firmado (base64) directamente.
  • Espera unos segundos a que SUNAT responda y devuelve 200 con el CDR, el código y el mensaje. Es el comportamiento por defecto.
  • Si SUNAT no contesta a tiempo responde 202, te dice que eso fue lo que pasó y el envío sigue su curso. Ahí recoges el resultado con consultar o por webhook.
  • Los resúmenes, reversiones y guías responden siempre 202: SUNAT los resuelve por ticket y no hay CDR que esperar. Se consultan después, como en cualquier plataforma.
  • Es idempotente: mandar dos veces el mismo comprobante produce un solo envío.
  • El comprobante debe estar registrado (firmado por nosotros). Un XML que nunca pasó por la firma responde 404.
  • Si mandas en lote y no quieres esperar en cada comprobante, la empresa puede desactivarlo con response: immediate.
  • Existe también POST /api/cpe/enviar/external_id como variante explícita.
POSThttps://api.xmlperu.dev/api/cpe/enviarCopiar

Este es el endpoint que cierra la migración. Firmas con generar, mandas aquí, y el resultado viene en la respuesta — igual que en la plataforma de la que vienes.

curl -X POST https://api.xmlperu.dev/api/cpe/enviar   -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json"   -d '{"external_id": "9c2f1b7e-…", "xml_filename": "20123456789-01-F001-777"}'

Los dos desenlaces

200 202
Qué pasó SUNAT respondió SUNAT no contestó a tiempo
Trae cdr no
Qué haces nada más consultas o esperas el webhook

El 202 no es un error y no hay que reenviar. El comprobante está firmado, el envío sigue su curso y el mensaje te dice exactamente eso. Reenviarlo podría duplicarlo.

Un 200 tampoco significa «aceptado»: significa que hay veredicto. Míralo en state_type_id05 aceptado, 07 con observaciones, 09 rechazado. La tabla completa está en Cómo leer la respuesta.

Resúmenes y guías

Responden siempre 202. SUNAT no devuelve un CDR al recibirlos, sino un ticket que hay que consultar más tarde: es así en cualquier plataforma, y por eso ahí ya consultabas.

Headers

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

Body

NameTypeDescription
external_idstringIdentificador devuelto por generar. Si lo envías, se usa el XML firmado ya almacenado (no hace falta reenviar el base64).
xml_filename*stringNombre en formato RUC-TIPO-SERIE-NUMERO (sin extensión). Acepta también nombre_xml_firmado.
contenido_xml_firmadostringXML ya firmado en base64. Solo necesario si no envías external_id. Acepta también xml_signed_base64.

Ejemplo de solicitud

Copiar
curl -X POST https://api.xmlperu.dev/api/cpe/enviar \
  -H "Authorization: Bearer $TOKEN_EMPRESA" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
  "external_id": "162c9e5f-e001-4b31-a1fd-83a4e3a30a51",
  "xml_filename": "20123456789-03-B001-777"
}'

Respuesta

200 Resueltoapplication/json
SUNAT respondió dentro del plazo. Mira state_type_id, no el HTTP: un rechazo también llega como 200.
{
  "success": true,
  "connection": true,
  "sunat_success": true,
  "llego_a_sunat": true,
  "resuelto": true,
  "estado": 200,
  "state_type_id": "05",
  "mensaje": "La Factura numero F001-102, ha sido aceptada",
  "message": "La Factura numero F001-102, ha sido aceptada",
  "code": "0",
  "external_id": "9c2f1b7e-…",
  "cdr": "UEsDBBQAAAgIA…",
  "time": 2.24
}
202 SUNAT no contestó a tiempoapplication/json
El comprobante está firmado y su envío sigue su curso. No lo reenvíes: consúltalo.
{
  "success": true,
  "estado": "en_cola",
  "mensaje": "SUNAT no respondió en 5 segundos. El envío quedó encolado y sigue su curso: consulta el estado con /api/cpe/consultar/20123456789-01-F001-777.",
  "message": "SUNAT no respondió en 5 segundos. El envío quedó encolado y sigue su curso: consulta el estado con /api/cpe/consultar/20123456789-01-F001-777.",
  "external_id": "9c2f1b7e-…",
  "time": 5.02
}
404 No registradoapplication/json
El comprobante no existe en el sistema: fírmalo primero.
{
  "success": false,
  "message": "El comprobante no está registrado: fírmalo primero (/cpe/generar) y vuelve a enviarlo."
}