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
Tenant autenticado
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-TypeContrato JSON.application/json
AuthorizationSí en producciónJWT HS256 firmado por Bheed; contiene tenantId y sub.Bearer eyJ...
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 y utiliza exclusivamente el tenantId firmado en el Bearer JWT. Los tokens validan firma, emisor, audiencia, expiración y fecha de activación.

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.OC-84731

Estructura de cada ítem

CampoRequeridoDescripción
skuCódigo interno, hasta 30 caracteres.
descriptionDescripción de 1 a 500 caracteres.
quantityDecimal mayor que cero.
unitCodeNoUnidad SUNAT/UNECE de 3 caracteres. Default NIU.
unitValueValor unitario sin impuesto.
discountNoamount o percent, más reasonCode.
chargeNoamount y código de motivo.
tax.affectationCode10 gravada, 20 exonerada, 30 inafecta; códigos gratuitos según catálogo 07.
tax.rateTasa decimal. IGV común: 18.00.

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",
  "items": [
    {
      "sku": "SAAS-PRO-01",
      "sunatProductCode": "81112501",
      "description": "Suscripción mensual Bheed Pro",
      "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" }
    ]
  }
}

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"
      }
    }
  ]
}

Los códigos de motivo están validados por Zod contra los catálogos SUNAT centralizados. Una nota asociada a una boleta sigue el flujo de Resumen Diario.

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.