PEBheed / SUNAT
API directa · Node.js · JSON

Envía JSON.
El motor resuelve SUNAT.

Guía práctica para emitir facturas, boletas y notas desde Bheed. Incluye los bodies exactos, headers, respuestas, flujo de Resumen Diario, errores y descarga de PDF/XML/CDR.

01
JSON del negocio
Cliente, ítems, moneda y serie
02
Zod + Decimal
Contrato y cálculos monetarios
03
UBL + XMLDSig + XSD
Generación y validación oficial
04
ZIP + SOAP + CDR
Transmisión directa a SUNAT
/api/peru/cpe
Base path
JSON
Entrada pública
Bearer
JWT o API key
Idempotency-Key
Emisión sin duplicados

01 · Acceso

Headers y resolución del tenant

El RUC, certificado, Clave SOL, correlativo y endpoint se obtienen desde la configuración segura del tenant. No deben enviarse en el JSON.

HeaderRequeridoUsoEjemplo
Content-TypeSíContrato JSON.application/json
AuthorizationSí en producciónJWT HS256 temporal o API key permanente y revocable bheed_sk_.... Ambas credenciales determinan el tenant y actor.Bearer bheed_sk_...
X-Tenant-IdSólo desarrolloDisponible únicamente fuera del runtime productivo o con habilitación insegura explícita.empresa-lima-01
Idempotency-KeySí, al emitirClave estable del evento de negocio. Repetirla devuelve el mismo comprobante.order_8f31_invoice
X-Actor-IdSólo desarrolloEn producción el actor proviene del claim sub firmado.usr_finanzas_204

Frontera de confianza. En producción la API ignora cualquier X-Tenant-Id aportado por el cliente. Un JWT valida firma, emisor, audiencia y vigencia. Una API key no vence automáticamente, se almacena sólo como hash SHA-256 y puede revocarse individualmente. Use API keys únicamente desde servidores; nunca en frontend, repositorios o logs.

Flujo previo · No tributario

Crear un borrador

Recibe directamente el mismo JSON válido de una factura. Genera un folio aleatorio claramente marcado como borrador y no consume correlativos tributarios.

POST

/api/peru/cpe/drafts

Requiere Bearer JWT. Envíe el JSON de factura en la raíz, sin documentType, sin payload y sin indicar una factura existente. No requiere Idempotency-Key: cada solicitud crea un borrador y un folio nuevos. No genera XML, ZIP o CDR y no se comunica con SUNAT. El PDF temporal se solicita por separado. La sección de referencias sólo aparece cuando se envían elementos en references[]; purchaseOrder por sí solo no se imprime.

201 DRAFT400 VALIDATION401 AUTH
Solicitud
curl -X POST "https://facturador-peru.bheed.net/api/peru/cpe/drafts" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $BHEED_SUNAT_TOKEN" \
  -d '{
    "series": "F001",
    "currency": "PEN",
    "operationType": "0101",
    "customer": {
      "documentType": "6",
      "documentNumber": "20123456789",
      "legalName": "CLIENTE SAC",
      "addressCode": "0000"
    },
    "payment": { "method": "CONTADO" },
    "items": [{
      "sku": "SAAS-001",
      "description": "Servicio SaaS mensual",
      "quantity": "1",
      "unitCode": "NIU",
      "unitValue": "100.00",
      "tax": { "type": "IGV", "affectationCode": "10", "rate": "18.00" }
    }]
  }'
201 · Borrador creado
{
  "id": "49e8d90a-ff2a-4fc9-8a02-ed8a7afb4f09",
  "folio": "BOR-01-5A67D449C1",
  "documentType": "invoice",
  "documentTypeCode": "01",
  "status": "DRAFT",
  "isDraft": true,
  "tributaryValidity": false,
  "sentToSunat": false,
  "externalReference": null,
  "payload": {
    "series": "F001",
    "currency": "PEN",
    "customer": {
      "documentType": "6",
      "documentNumber": "20123456789",
      "legalName": "CLIENTE SAC"
    },
    "items": [{ "sku": "SAAS-001", "description": "Servicio SaaS mensual" }]
  },
  "createdAt": "2026-09-08T12:00:00.000Z",
  "updatedAt": "2026-09-08T12:00:00.000Z",
  "message": "Borrador creado. No fue emitido ni enviado a SUNAT."
}

Título tributario: cuando todas las líneas usan affectationCode: "20", tanto el borrador como el PDF emitido se identifican como FACTURA ELECTRONICA EXONERADA. En una factura mixta se conserva el título general y se separan los importes en los totales.

Contrato: usa el mismo esquema Zod de POST /api/peru/cpe/invoices. El tipo se fija internamente como factura 01; el prefijo BOR- evita confundir el folio con un número fiscal.

GET

/api/peru/cpe/drafts/:id/pdf

Genera una vista previa desde el JSON de factura almacenado. Pertenece al mismo tenant y siempre muestra que no tiene validez tributaria ni fue enviada a SUNAT.

200 PDF401 AUTH404 NOT FOUND
Descargar PDF temporal
curl "https://facturador-peru.bheed.net/api/peru/cpe/drafts/ID_DEL_BORRADOR/pdf" \
  -H "Authorization: Bearer $BHEED_SUNAT_TOKEN" \
  --output borrador.pdf

Respuesta

  • Content-Type: application/pdf
  • Content-Disposition: inline
  • Nombre: BOR-01-XXXXXXXXXX.pdf

Contenido

  • Marca de agua BORRADOR.
  • Folio interno, emisor, cliente e ítems disponibles.
  • Sin QR, XML, firma digital ni CDR SUNAT.

02 · Inicio rápido

Emite la primera factura

La API reserva el correlativo. Por eso el body lleva la serie, pero no el número ni los datos del emisor.

Solicitud HTTP
curl -X POST "https://facturador-peru.bheed.net/api/peru/cpe/invoices" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $BHEED_SUNAT_TOKEN" \
  -H "Idempotency-Key: order_8f31_invoice" \
  --data-binary @factura.json
factura.json
{
  "series": "F001",
  "currency": "PEN",
  "operationType": "0101",
  "customer": {
    "documentType": "6",
    "documentNumber": "20123456789",
    "legalName": "COMERCIAL ANDINA DEL PACIFICO SAC",
    "addressCode": "0000"
  },
  "payment": { "method": "CONTADO" },
  "items": [
    {
      "sku": "SAAS-PRO-01",
      "description": "Suscripción mensual Bheed Pro",
      "quantity": "1",
      "unitCode": "NIU",
      "unitValue": "100.00",
      "tax": {
        "type": "IGV",
        "affectationCode": "10",
        "rate": "18.00"
      }
    }
  ]
}
1. Recibe JSONZod rechaza campos o formatos inválidos antes de construir XML.
→
2. Genera y transmiteReserva número, calcula, firma, valida, empaqueta y envía.
→
3. Devuelve estadoLa CDR determina aceptación, observaciones o rechazo.

03 · Modelo

Contrato JSON común

Los montos pueden enviarse como strings decimales. Es la forma recomendada para evitar conversiones binarias accidentales.

CampoTipoReglaEjemplo
seriesstringFxxx para factura; Bxxx para boleta.F001
issuedAtISO dateOpcional. Si falta, usa fecha/hora actual en America/Lima.2026-08-25T10:20:00-05:00
currencyenumPEN o USD.PEN
operationTypestringCatálogo SUNAT. Venta interna por defecto: 0101.0101
customerobjectDocumento, número, razón social y código de dirección.Ver ejemplos
itemsarrayEntre 1 y 500 líneas.Ver ejemplos
paymentobjectCONTADO o CREDITO con cuotas.{"method":"CONTADO"}
globalDiscountobjectOpcional: amount o percent.{"percent":"5"}
globalChargeobjectOpcional: importe del cargo global.{"amount":"3.50"}
legendsarrayLeyendas SUNAT, por ejemplo importe en letras.1000
purchaseOrderstringOrden de compra opcional, máximo 20 caracteres. Se conserva como referencia UBL y externa, pero no se imprime automáticamente.OC-84731
referencesarrayHasta 20 referencias comerciales con fecha, tipo, folio, código y razón. Sólo los elementos enviados aquí se muestran en el borrador y PDF emitido.Ver ejemplo

Estructura de cada ítem

CampoRequeridoDescripción
skuSíCódigo interno, hasta 30 caracteres.
descriptionSíDescripción de 1 a 500 caracteres.
glossNoGlosa visible principal. Se combina con la descripción en el UBL.
detailNoDetalle complementario visible en ambos PDF y combinado en el UBL.
quantitySíDecimal mayor que cero. En los PDFs se presenta siempre con dos decimales; el XML conserva el valor validado.
unitCodeNoUnidad SUNAT/UNECE de 3 caracteres. Default NIU.
unitValueSíValor unitario sin impuesto.
discountNoamount o percent, más reasonCode.
chargeNoamount y código de motivo.
tax.typeNoAdmite únicamente IGV; si se omite usa IGV. La afectación se determina con tax.affectationCode, no con este campo.
tax.affectationCodeSí10 gravada, 20 exonerada, 30 inafecta; códigos gratuitos según catálogo 07.
tax.rateSíTasa decimal. IGV común: 18.00.

Mapeo tributario generado por la API: gravada 10 → 1000 / IGV / VAT; exonerada 20 → 9997 / EXO / VAT; inafecta 30 → 9998 / INA / FRE. No envíes EXO ni INA en tax.type.

04 · Documento 01

Factura electrónica

Se transmite de forma síncrona con sendBill. La respuesta incluye la CDR cuando SUNAT termina la validación.

POST

/api/peru/cpe/invoices

Emite una factura gravada en PEN o USD. El ejemplo también muestra descuento por línea, descuento global, dirección del cliente y leyenda.

201 ACCEPTED201 MOCK_VALIDATED400 VALIDATION422 SUNAT
JSON completo de factura
{
  "series": "F001",
  "issuedAt": "2026-08-25T11:40:00-05:00",
  "dueAt": "2026-08-25",
  "currency": "PEN",
  "operationType": "0101",
  "customer": {
    "documentType": "6",
    "documentNumber": "20123456789",
    "legalName": "COMERCIAL ANDINA DEL PACIFICO SAC",
    "tradeName": "Andina",
    "addressCode": "0000",
    "address": {
      "ubigeo": "150101",
      "line": "Av. Javier Prado Este 1840",
      "district": "San Isidro",
      "province": "Lima",
      "department": "Lima",
      "countryCode": "PE"
    }
  },
  "payment": { "method": "CONTADO" },
  "purchaseOrder": "OC-84731",
  "references": [{
    "date": "2026-08-25",
    "documentType": "Orden de compra",
    "folio": "OC-84731",
    "code": "Referencia cliente",
    "reason": "Orden asociada al servicio"
  }],
  "items": [
    {
      "sku": "SAAS-PRO-01",
      "sunatProductCode": "81112501",
      "gloss": "Suscripción Bheed Pro",
      "description": "Suscripción mensual Bheed Pro",
      "detail": "Periodo agosto 2026",
      "quantity": "2",
      "unitCode": "NIU",
      "unitValue": "50.00",
      "discount": {
        "percent": "5.00",
        "reasonCode": "00"
      },
      "tax": {
        "type": "IGV",
        "affectationCode": "10",
        "rate": "18.00"
      }
    }
  ],
  "globalDiscount": { "amount": "5.00" },
  "legends": [
    {
      "code": "1000",
      "value": "SON CIENTO SEIS CON 20/100 SOLES"
    }
  ]
}

Factura a crédito

La suma de las cuotas debe ser exactamente igual al total calculado del documento.

Fragmento payment
{
  "payment": {
    "method": "CREDITO",
    "installments": [
      { "amount": "59.00", "dueDate": "2026-09-25" },
      { "amount": "59.00", "dueDate": "2026-10-25" }
    ]
  }
}

Caso especial · SPOT

Factura con detracción general

El consumidor envía JSON con el tipo de operación, código del catálogo 54, cuenta, medio de pago y porcentaje. La factura puede estar en PEN o USD, pero la API calcula y expresa la detracción siempre en PEN.

POST

/api/peru/cpe/invoices

201 ACCEPTED201 MOCK_VALIDATED400 VALIDATION422 SUNAT
JSON completo · Factura con detracción
{
  "series": "F001",
  "issuedAt": "2026-09-14T10:00:00-05:00",
  "currency": "USD",
  "operationType": "1001",
  "customer": {
    "documentType": "6",
    "documentNumber": "20123456789",
    "legalName": "CLIENTE SAC",
    "addressCode": "0000"
  },
  "payment": { "method": "CONTADO" },
  "detraction": {
    "goodsServiceCode": "037",
    "bankAccount": "0012345678901",
    "paymentMeansCode": "001",
    "percent": "12.00",
    "legend": "Operación sujeta al Sistema de Pago de Obligaciones Tributarias con el Gobierno Central",
    "exchangeRate": {
      "rate": "3.7500",
      "date": "2026-09-14",
      "source": "SBS"
    }
  },
  "items": [{
    "sku": "SERV-001",
    "description": "Servicio gravado sujeto a detracción",
    "quantity": "1",
    "unitCode": "NIU",
    "unitValue": "100.00",
    "tax": {
      "type": "IGV",
      "affectationCode": "10",
      "rate": "18.00"
    }
  }]
}
CampoReglaEjemplo
operationTypeDebe ser 1001 para la detracción general.1001
currencyAdmite PEN o USD; el monto SPOT del XML y PDF siempre queda en soles.USD
detraction.goodsServiceCodeCatálogo SUNAT 54. 037 corresponde a demás servicios gravados con IGV.037
detraction.bankAccountCuenta del proveedor en el Banco de la Nación.0012345678901
detraction.paymentMeansCodeCatálogo 59; por defecto 001, depósito en cuenta.001
detraction.percentDecimal mayor que cero y hasta 100. Debe corresponder al bien o servicio.12.00
detraction.legendOpcional. Si falta se usa la leyenda SPOT estándar.Operación sujeta...
detraction.exchangeRateObligatorio sólo para USD: tipo de cambio promedio ponderado venta SBS, fecha legalmente aplicable y fuente SBS. Se usa la fecha de nacimiento de la obligación tributaria o la del depósito, la que ocurra primero; sin publicación se usa el último tipo publicado. Se omite para PEN.{"rate":"3.7500","date":"2026-09-14","source":"SBS"}

El monto no se envía. La API lo calcula con Decimal y dos decimales. Para USD convierte primero el total con la tasa venta SBS y devuelve sourceAmount, baseAmountPen, amount y currency: PEN. Además agrega la leyenda 2006 y genera PaymentMeans/PaymentTerms con monto en PEN.

PDF consistentes: tanto el borrador como el documento emitido muestran por separado Porcentaje de detracción y Monto de detracción. Debajo incluyen Monto Neto a Pagar al Proveedor = Importe total - Monto de detracción en la moneda de la factura. En USD, el bloque SPOT conserva la detracción oficial en PEN, mientras el cuadro neto convierte esa detracción con el mismo tipo de cambio y muestra la resta y el resultado en USD. Si payment.method es CREDITO, también se agrega un Cronograma de cuotas con número, fecha de vencimiento y monto de cada elemento de payment.installments; no aparece para CONTADO. El borrador no firma, no reserva correlativo SUNAT y no se transmite.

Alcance: están soportadas las detracciones generales 1001. Los casos 1002 de recursos hidrobiológicos y 1004 de transporte de carga requieren campos especializados y se rechazan localmente. La empresa emisora sigue siendo responsable de determinar el umbral, código 54 y porcentaje legal aplicable.

Fuentes: Anexo de factura y detracciones, Resolución 183-2004/SUNAT, art. 20 y catálogos SUNAT 54 y 59.

05 · Documento 03

Boleta electrónica

La boleta se genera y firma, pero no se trata como factura: queda pendiente hasta incluirla en un Resumen Diario.

POST

/api/peru/cpe/receipts

202 PENDING_SUMMARY400 VALIDATION
JSON de boleta
{
  "series": "B001",
  "currency": "PEN",
  "operationType": "0101",
  "customer": {
    "documentType": "1",
    "documentNumber": "74291836",
    "legalName": "Mariana Quispe Rojas",
    "addressCode": "0000"
  },
  "payment": { "method": "CONTADO" },
  "items": [
    {
      "sku": "PLAN-MENSUAL",
      "description": "Plan mensual Bheed",
      "quantity": "1",
      "unitCode": "NIU",
      "unitValue": "40.00",
      "tax": {
        "type": "IGV",
        "affectationCode": "10",
        "rate": "18.00"
      }
    }
  ]
}

Guarda el id devuelto. Ese identificador se envía luego en documentIds al endpoint de Resumen Diario.

06 · Documentos 07 y 08

Notas de crédito y débito

Ambas requieren referencia al comprobante afectado, código oficial de motivo, descripción del motivo e ítems.

POST

/api/peru/cpe/credit-notes

JSON completo · Nota de crédito
{
  "series": "FC01",
  "currency": "PEN",
  "customer": {
    "documentType": "6",
    "documentNumber": "20123456789",
    "legalName": "COMERCIAL ANDINA DEL PACIFICO SAC",
    "addressCode": "0000"
  },
  "reference": {
    "documentType": "01",
    "series": "F001",
    "number": 84731
  },
  "reasonCode": "01",
  "reason": "Anulación de la operación",
  "items": [
    {
      "sku": "SAAS-PRO-01",
      "description": "Reverso de suscripción mensual",
      "quantity": "1",
      "unitCode": "NIU",
      "unitValue": "100.00",
      "tax": {
        "type": "IGV",
        "affectationCode": "10",
        "rate": "18.00"
      }
    }
  ]
}
DocumentoSeries y referenciaMotivos admitidos
Nota de créditoFCxx → factura 01
BCxx → boleta 03
01 anulación; 02 error RUC; 03 descripción/producto; 04 descuento global; 05 descuento por ítem; 06 devolución total; 07 devolución por ítem; 08 bonificación; 09 disminución; 10 otros; 11 exportación; 12 IVAP; 13 cuotas.
Nota de débitoFDxx → factura 01
BDxx → boleta 03
01 intereses; 02 aumento de valor; 03 penalidades/otros; 11 exportación; 12 IVAP; 13 penalidades.

Los códigos de motivo están validados por Zod contra los catálogos SUNAT centralizados. El comprobante referenciado debe existir en el mismo tenant/ambiente y coincidir en cliente y moneda. Una nota asociada a una boleta queda PENDING_SUMMARY; una nota de factura usa sendBill. Cada nota requiere su propia Idempotency-Key.

07 · Flujo RC

Resumen Diario

Agrupa boletas y notas de boleta ya generadas. El servicio firma el SummaryDocuments, llama a sendSummary, guarda el ticket y consulta getStatus.

POST

/api/peru/cpe/daily-summaries

201 ACCEPTED202 PROCESSING201 MOCK_VALIDATED
JSON de Resumen Diario
{
  "referenceDate": "2026-08-25",
  "documentIds": [
    "8c179db1-4ebf-41da-8c7b-37d95bf340f4",
    "d4771bc0-0ccb-4a70-a213-58e780f4385d"
  ],
  "poll": true
}

poll: true

  • Consulta con backoff y máximo de intentos.
  • Devuelve estado final si la CDR llega dentro del límite.
  • No hace polling agresivo contra SUNAT.

poll: false

  • Devuelve PROCESSING.
  • Incluye el ticket.
  • El worker durable continúa tras reinicios; también se puede consultar con POST /async/:id/status.
POST

/api/peru/cpe/daily-summaries

Para anular boletas ya reportadas use VOID (ConditionCode 3). Para anular una boleta del día aún no informada use VOID_SAME_DAY (ConditionCode 4).

JSON de anulación de boleta
{
  "referenceDate": "2026-08-25",
  "items": [
    { "documentId": "8c179db1-4ebf-41da-8c7b-37d95bf340f4", "action": "VOID" }
  ],
  "poll": true
}

08 · Flujo RA

Comunicación de Baja

Utiliza el flujo asíncrono para documentos elegibles. Las boletas no se dan de baja con RA; se comunican mediante el mecanismo aplicable del Resumen Diario.

POST

/api/peru/cpe/voided-documents

JSON de Comunicación de Baja
{
  "referenceDate": "2026-08-25",
  "documentIds": [
    "f70b53f4-fbff-4170-b2a9-3189dffcaa11"
  ],
  "reason": "ERROR EN DATOS DEL COMPROBANTE",
  "poll": true
}

09 · Salida

Cómo se ven las respuestas

La forma es estable entre ambientes. Cambian el estado, la validez tributaria y los datos normalizados de SUNAT.

201 · Factura validada en MOCK
{
  "id": "1b978fc2-3301-425f-93ab-2228f6362812",
  "documentNumber": "F001-1",
  "environment": "mock",
  "status": "MOCK_VALIDATED",
  "tributaryValidity": false,
  "sunat": {
    "code": "0",
    "description": "MOCK - NO VÁLIDO TRIBUTARIAMENTE",
    "notes": []
  },
  "artifacts": {
    "domain": { "key": ".../domain.json", "sha256": "..." },
    "signedXml": { "key": ".../F001-1.XML", "sha256": "..." },
    "zip": { "key": ".../F001-1.ZIP", "sha256": "..." },
    "cdrXml": { "key": ".../R-F001-1.XML", "sha256": "..." }
  }
}

10 · Lectura

Consultas, XML y CDR

Todas las lecturas conservan el aislamiento por tenant. El XML entregado es exactamente el XML firmado y enviado.

GET

/api/peru/cpe/:id

Obtiene estado normalizado, respuesta SUNAT y metadata de artefactos.

GET

/api/peru/cpe/:id/xml

Devuelve application/xml con el comprobante firmado.

GET

/api/peru/cpe/:id/cdr

Devuelve application/xml con la constancia de recepción cuando ya existe.

POST

/api/peru/cpe/:id/access-tokens

Genera un acceso privado, aleatorio y revocable para el receptor. El plazo predeterminado es 366 días.

Solicitud y respuesta
// body
{ "ttlDays": 366 }

// 201
{
  "token": "KX...43-caracteres...",
  "expiresAt": "2027-08-26T15:00:00.000Z",
  "urlPath": "/cpe/PE/KX...43-caracteres..."
}
GET

/cpe/PE/:token

Portal privado sin header tenant. Devuelve datos esenciales y enlaces protegidos a /pdf y /xml. La CDR sólo se expone con SUNAT_PORTAL_EXPOSE_CDR=true. Los PDF incluyen QR SUNAT con el DigestValue del XML firmado.

GET

/api/peru/cpe/:id/pdf

Genera application/pdf desde el mismo modelo canónico del XML. BETA y MOCK incluyen una marca visible de prueba.

POST

/api/peru/cpe/:id/reconcile

Consulta el servicio oficial CDR para resolver timeouts o estados ambiguos. No genera otro correlativo.

POST

/api/peru/cpe/async/:id/status

Consulta una operación RC/RA que tiene ticket.

Ejemplo de consulta
curl "https://facturador-peru.bheed.net/api/peru/cpe/f70b53f4-fbff-4170-b2a9-3189dffcaa11" \
  -H "Authorization: Bearer $BHEED_SUNAT_TOKEN"

curl "https://facturador-peru.bheed.net/api/peru/cpe/f70b53f4-fbff-4170-b2a9-3189dffcaa11/xml" \
  -H "Authorization: Bearer $BHEED_SUNAT_TOKEN" \
  --output factura-firmada.xml

curl "https://facturador-peru.bheed.net/api/peru/cpe/f70b53f4-fbff-4170-b2a9-3189dffcaa11/pdf" \
  -H "Authorization: Bearer $BHEED_SUNAT_TOKEN" \
  --output factura.pdf

11 · Máquina de estados

Qué significa cada estado

HTTP 200 no determina validez. El resultado de la CDR y el ambiente son los que definen el estado final.

DRAFT → ZIPPED

Etapas locales: dominio, generación, validación, firma y ZIP.

SENDING / SENT

Transmisión iniciada o recibida por el servicio HTTP.

PENDING_SUMMARY

Boleta o nota de boleta que debe incluirse en Resumen Diario.

PROCESSING

SUNAT entregó ticket y todavía procesa el RC/RA.

ACCEPTED

CDR código 0 sin observaciones.

ACCEPTED_WITH_OBSERVATIONS

CDR código 0 con notas u observaciones.

REJECTED

La CDR rechazó el documento; no se reintenta ciegamente.

TRANSPORT_ERROR

Falla recuperable de red/HTTP; conservar idempotencia y reconciliar.

UNKNOWN

No hay certeza de recepción. Consultar CDR antes de cualquier acción.

MOCK_VALIDATED

Pipeline local completo, siempre sin validez tributaria.

VOIDED

Comunicación de Baja aceptada en producción.

12 · Fallos

Respuestas de error

Los errores son objetos estructurados. La capa consumidora no debe interpretar strings SOAP.

400 · JSON inválido
{
  "error": {
    "code": "PE_DOMAIN_VALIDATION_FAILED",
    "message": "El documento no cumple el contrato JSON de SUNAT",
    "details": [
      {
        "field": "customer.documentNumber",
        "code": "PE_CUSTOMER_DOCUMENTNUMBER_INVALID",
        "message": "Required"
      }
    ]
  }
}

No cambies la idempotency key después de un timeout. Conserva el mismo documento y usa /:id/reconcile. Emitir otro correlativo podría duplicar la operación.

13 · Operación

MOCK, BETA y producción

El ambiente se configura por tenant. Un cliente HTTP no puede sobrescribir endpoints ni credenciales.

mock

No requiere secretos ni red. Genera firma, XSD, ZIP, SOAP y CDR simulada. tributaryValidity: false.

beta

Servicio oficial de pruebas. CDR 0 produce ACCEPTED, pero sin validez fiscal de producción.

production

Requiere MongoDB, S3, gestor de secretos, JWT tenant, portal privado, certificado/RUC reales, series autorizadas y SUNAT_ALLOW_REAL_EMISSION=true.

Comandos de verificación
npm test
npm run sunat:test:mock

# BETA requiere habilitación explícita
set SUNAT_RUN_BETA_TESTS=true
set SUNAT_TEST_RUC=20000000001
set SUNAT_TEST_CERT_PATH=D:\certificados\beta-self-signed.pem
npm run sunat:test:beta
Variables mínimas del runtime productivo
NODE_ENV=production
SUNAT_RUNTIME_MODE=production
MONGODB_URI=mongodb+srv://...
SUNAT_SECRETS_BACKEND=aws
SUNAT_ARTIFACTS_S3_BUCKET=bheed-sunat-production
SUNAT_AUTH_HMAC_SECRET=<minimo-32-caracteres>
SUNAT_PORTAL_TOKEN_SECRET=<minimo-32-caracteres>
SUNAT_ASYNC_WORKER_ENABLED=true
SUNAT_ALLOW_REAL_EMISSION=true

El RUC 20000000001 se utiliza sólo en la prueba BETA controlada. SUNAT no lo publica como cuenta sandbox universal y nunca debe convertirse en configuración de producción.

14 · Diagnóstico seguro

Salud del servicio SUNAT

Comprueba la configuración del tenant, vigencia del certificado y capacidad real de generar un XML UBL 2.1 en memoria. No reserva correlativos, no guarda artefactos y no transmite a SUNAT.

GET

/internal/health/sunat

200 VALID401 AUTH
Consulta
curl "https://facturador-peru.bheed.net/internal/health/sunat" \
  -H "Authorization: Bearer $BHEED_SUNAT_TOKEN"
Respuesta
{
  "ok": true,
  "environment": "beta",
  "certificateStatus": "VALID",
  "configuration": "VALID",
  "xmlGeneration": {
    "status": "VALID",
    "documentType": "01",
    "ublVersion": "2.1",
    "bytes": 5723
  },
  "networkProbePerformed": false
}

networkProbePerformed: false es intencional: el health check nunca emite comprobantes ni usa SUNAT como ping. Las pruebas reales BETA se ejecutan sólo mediante los scripts controlados y con habilitación explícita.