API de facturación electrónica · SUNAT (Perú)

La factura electrónica, en una petición.

POST /v1/invoicesSUNAT: accepted

Envías lo que vendiste. Emitay calcula los impuestos, numera, firma, envía a SUNAT y te avisa del resultado. El certificado, el diseño de la factura, el correo y los avisos siguen siendo tuyos, y se configuran sin código.

Con una llave de prueba funciona de inmediato, sin certificado ni configuración. Se entrega al registrar tu empresa: RUC, razón social y dirección.

POST /v1/invoicesTu petición
curl https://api.emitay.com/v1/invoices \
  -H "Authorization: Bearer $EMITAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "series": "F001",
    "customer": {
      "document_number": "20100066603",
      "name": "CLIENTE S.A."
    },
    "items": [
      {
        "description": "Servicio de consultoría",
        "quantity": 2,
        "unit_price": "50.00"
      }
    ]
  }'
POST /v1/invoicesTu petición, con espera
curl https://api.emitay.com/v1/invoices \  -H "Authorization: Bearer $EMITAY_API_KEY" \  -H "Content-Type: application/json" \  -H "Prefer: wait=10" \  -d '{    "series": "F001",    "customer": {      "document_number": "20100066603",      "name": "CLIENTE S.A."    },    "items": [      {        "description": "Servicio de consultoría",        "quantity": 2,        "unit_price": "50.00"      }    ]  }'
201 CreatedLa respuesta, antes de SUNAT
{  "id": "doc_…",  "object": "document",  "type": "01",  "series": "F001",  "number": 1,  "status": "pending",  "currency": "PEN",  "total": "118.00",  "amounts": {    "taxed": "100.00",    "exempt": "0.00",    "unaffected": "0.00",    "igv": "18.00"  },  "sunat": null,  "links": {    "pdf": "https://files.emitay.com/files/…/F001-1.pdf",    "xml": "https://files.emitay.com/files/…/F001-1.xml",    "cdr": null  }}
GET /v1/documents/doc_…El comprobante, con el veredicto
{  "id": "doc_…",  "object": "document",  "type": "01",  "series": "F001",  "number": 1,  "status": "accepted",  "currency": "PEN",  "total": "118.00",  "amounts": {    "taxed": "100.00",    "exempt": "0.00",    "unaffected": "0.00",    "igv": "18.00"  },  "sunat": {    "code": "0",    "description": "La Factura numero F001-1, ha sido aceptada"  },  "links": {    "pdf": "https://files.emitay.com/files/…/F001-1.pdf",    "xml": "https://files.emitay.com/files/…/F001-1.xml",    "cdr": "https://files.emitay.com/files/…/R-F001-1.zip"  }}
201 CreatedLa respuesta, con el veredicto de SUNAT
{  "id": "doc_…",  "object": "document",  "type": "01",  "series": "F001",  "number": 1,  "status": "accepted",  "currency": "PEN",  "total": "118.00",  "amounts": {    "taxed": "100.00",    "exempt": "0.00",    "unaffected": "0.00",    "igv": "18.00"  },  "sunat": {    "code": "0",    "description": "La Factura numero F001-1, ha sido aceptada"  },  "links": {    "pdf": "https://files.emitay.com/files/…/F001-1.pdf",    "xml": "https://files.emitay.com/files/…/F001-1.xml",    "cdr": "https://files.emitay.com/files/…/R-F001-1.zip"  }}
POST /webhooks/emitayEl evento que recibe tu servidor
{  "id": "evt_…",  "object": "event",  "type": "document.accepted",  "created_at": "2026-10-08T22:32:56.000Z",  "livemode": false,  "data": {    "object": {      "id": "doc_…",      "object": "document",      "series": "F001",      "number": 1,      "status": "accepted",      "sunat": {        "code": "0",        "description": "La Factura numero F001-1, ha sido aceptada"      }    }  }}

El recorrido de una factura, paso a paso: quién habla con quién y qué viaja en cada momento. La API responde sin esperar a SUNAT; Prefer: wait=10 espera su veredicto en la misma llamada. De cada respuesta se muestran los campos principales.

Lo que deja de ser tu problema

  • CalculaEl IGV, los totales, los descuentos y las detracciones, con decimales exactos.
  • NumeraToma el siguiente número de la serie. Un comprobante inválido no consume correlativo.
  • FirmaArma el XML UBL 2.1 y lo firma con el certificado de tu empresa.
  • EnvíaA los servicios de SUNAT, y reintenta por su cuenta si SUNAT no responde.
  • AvisaCon un webhook firmado cuando SUNAT acepta, observa o rechaza.
  • EntregaEl PDF, el XML firmado y el CDR, con enlaces listos para tu cliente.

Errores

Un error que dice qué campo, por qué y cómo arreglarlo

Lo que SUNAT rechazaría, la API lo niega antes de numerar, en español y con la ruta del campo. Un nombre en camelCase recibe su forma correcta. Un 422 nunca consume correlativo: corriges y vuelves a enviar.

Cada error enlaza a una página que explica qué significa y cómo resolverlo.

Cómo responde la API a un error
422 Unprocessable ContentRespuesta real de la API
{  "type": "https://docs.emitay.com/errors/invalid_request",  "title": "La petición no es válida",  "status": 422,  "detail": "Revisa 3 campos: customer.document_type, items.0.unit_price, items.0.unitPrice. El motivo de cada uno está en errors",  "errors": [    {      "field": "customer.document_type",      "message": "Indica el tipo de documento (catálogo 06 de SUNAT: \"1\" DNI, \"4\" carné de extranjería, \"7\" pasaporte, \"0\" sin documento…): solo se deduce cuando el número es un RUC válido"    },    {      "field": "items.0.unit_price",      "message": "Es obligatorio"    },    {      "field": "items.0.unitPrice",      "message": "Campo no reconocido. Los campos de la API se escriben en snake_case: prueba con unit_price"    }  ]}

Webhooks

El veredicto llega firmado, y lo pruebas en localhost

SUNAT responde cuando responde. Emitay llama a tu servidor con el comprobante completo y una firma Standard Webhooks, y lo intenta hasta ocho veces si tu servidor no contesta.

emitay listen reenvía cada evento a tu máquina, firmado igual que una entrega real. Sin túnel y sin URL pública.

Recibir y verificar webhooks
app/webhooks/emitay/route.tsTu receptor
import { verifyWebhook } from "emitay";

export async function POST(request: Request) {
  const event = await verifyWebhook({
    payload: await request.text(),
    headers: request.headers,
    secret: process.env.EMITAY_WEBHOOK_SECRET!,
  });
  if (event.type === "document.accepted") {
    const factura = event.data.object; // un Document, con su tipo
    console.log(factura.series, factura.number, factura.links.pdf);
  }
  return new Response(null, { status: 204 });
}
Terminal
$ npx emitay listen --forward-to http://localhost:3000Escuchando los eventos de MI EMPRESA S.A.C. (RUC 20…), en modo de pruebaReenviando a http://localhost:3000/Secreto de firma: whsec_… 17:32:56  document.accepted       Factura F001-2 (doc_…) — SUNAT 0          →  204 No Content  (16 ms)

Modo de prueba

Tu primera factura, antes de tener certificado

Una llave sk_test_… no pide nada más: ni certificado digital ni usuario SOL. Emitay firma con un certificado de prueba y envía al ambiente beta de SUNAT, que valida de verdad la estructura, la firma y los cálculos.

Lo que no se puede probar contra SUNAT, se dice: las guías de remisión no tienen ambiente de pruebas, así que su aceptación en modo de prueba llega marcada como simulada.

Qué hace el modo de prueba
sk_test_…
Certificado de prueba · SUNAT beta · sin valor tributario
sk_live_…
El certificado y el usuario SOL de tu empresa · SUNAT producción

El código no cambia: cambia la llave.

Lo que recibe tu cliente

Tu factura, tu correo y tu servidor de correo

El comprobante sale con el nombre y el logo de tu empresa. El diseño del PDF, la redacción del correo y el servidor que lo envía se configuran en el panel, sin código. Prueba aquí lo que cambia.

Configura tu correo

Emitay envía el comprobante al correo de tu cliente con el PDF, el XML firmado y el CDR adjuntos. Tú decides desde qué dirección sale y qué dice.

Sale desde

Con el nombre de tu empresa. Las respuestas de tu cliente llegan al correo de tu empresa.

Desde tu propia dirección, con los datos que da tu proveedor: Google Workspace, Microsoft 365, Amazon SES, tu hosting… La contraseña se guarda cifrada, y desde el panel te envías un mensaje de prueba para comprobarlo.

Enviar por correo
De
MI EMPRESA S.A.C. desde el correo de EmitayMI EMPRESA S.A.C. <[email protected]> por smtp.miempresa.pe
Asunto
Factura electrónica F001-1 de MI EMPRESA S.A.C.

MI EMPRESA S.A.C.RUC 20123456786

Factura electrónica F001-1

Hola CLIENTE S.A.:

Adjuntamos tu comprobante por S/ 118.00.

Comprobante
Factura electrónica F001-1
Fecha de emisión
08/10/2026
Cliente
CLIENTE S.A.
Importe total
S/ 118.00
Ver comprobante
  • 20123456786-01-F001-1.pdf
  • 20123456786-01-F001-1.xml
  • R-20123456786-01-F001-1.zip
Sale solo, en cuanto SUNAT acepta el comprobante.Sale cuando tu sistema lo pide: POST /v1/documents/{id}/emails.

Configura tu factura

Cada comprobante tiene su PDF en A4 y en ticket de 80 y de 58 mm. Si quieres otro diseño, lo armas en un editor visual, colocando cada dato donde va: sin código y sin plantillas HTML.

El mismo motor dibuja la vista previa y el PDF final, y una plantilla no se puede guardar sin los datos que SUNAT exige en la representación impresa.

Formato

Hoja A4, las que haga falta. Es el formato por defecto.

El mismo enlace los sirve todos:…/F001-1.pdf?format=a4

PDF y layouts
Ilustración de una plantilla. Cada elemento se enlaza a un dato del comprobante:document_idSerie y número

Cobertura

Todos los comprobantes, directo a SUNAT

Cada empresa emite con su propio certificado digital y su propio usuario SOL, y Emitay envía sus comprobantes directamente a los servicios de SUNAT. Emitay no es un PSE ni un OSE: es la API con la que tu sistema emite.

Casos tributarios

  • Operaciones gratuitas
  • Descuentos por línea y globales
  • Exoneradas e inafectas
  • Exportación
  • Detracción
  • Anticipos
  • Venta al crédito y cuotas
  • ICBPER
  • ISC e IVAP
  • Percepción
  • Otras monedas

Y después de emitir

  • Anulación con una sola llamada: baja, resumen diario o reversión, según el comprobante.
  • Envío por correo con PDF, XML y CDR, a pedido o automático al aceptar SUNAT.
  • Idempotencia en toda creación: un reintento no emite dos veces.
  • SDK de TypeScript sin dependencias, con tipos generados del contrato.

Lo que aún no se ofrece está escrito en la documentación de cada comprobante, no escondido.

Casos de uso

Para el sistema que ya tienes

¿Dudas antes de integrar? Están en las preguntas frecuentes.

Cómo se empieza

De la cuenta a producción, en tres pasos

  1. Crea tu cuenta

    Registra tu empresa con su RUC, razón social y dirección fiscal. El panel te entrega una llave de prueba en ese momento.

  2. Emite en modo de prueba

    Con curl o con el SDK. La factura va al ambiente beta de SUNAT y vuelve con su respuesta. Integra webhooks con emitay listen.

  3. Pasa a producción

    Carga el certificado digital y el usuario secundario SOL de tu empresa, crea una llave de producción y cámbiala en tu configuración.