SEE del Contribuyente · sin OSE

Factura electrónica peruana, hablada directo con SUNAT.

Una API para emitir facturas, boletas, notas y guías de remisión bajo el Sistema de Emisión Electrónica del Contribuyente — cada emisor firma con su propio certificado y sus propias credenciales SOL. Sin intermediarios, sin costo por comprobante.

Base https://vendacoresunat.com/api/v1

Autenticación

Un solo header, dos formas de credencial.

Cada request lleva la credencial en Authorization: Bearer <clave>. Hay dos tipos, y el formato de la clave indica cuál es:

sk_live_… / sk_test_…clave del tenant
Cubre todos los RUC del tenant. Si el tenant tiene más de un emisor, hace falta además el header X-Issuer-RUC para indicar sobre cuál se opera.
isk_live_… / isk_test_…token de emisor
Ya viene scopeado a un solo RUC — no admite X-Issuer-RUC ni falta hacerlo. Es la credencial recomendada para entregar a un sistema externo: no puede tocar ningún otro RUC del tenant aunque lo intente.
El sufijo _test_ resuelve automáticamente contra el ambiente beta de SUNAT; _live_ contra producción. El ambiente no se elige por parámetro — lo decide la clave.

Modelo asíncrono

Cada envío responde de inmediato; el resultado de SUNAT llega después.

Un POST a cualquier endpoint de emisión devuelve 202 con status: "queued" en segundos — es un acuse de recibo de nuestro lado, no el veredicto de SUNAT. El envío real ocurre en una cola con reintentos automáticos ante caídas del servicio de SUNAT.

Para saber en qué quedó, dos caminos:

  • Consultar — GET /documents/{id} hasta que status deje de ser queued o retrying. En producción normal, resuelve en 5–15 segundos.
  • Webhook — configura una URL de callback por tenant; avisamos con document.accepted o document.rejected, firmado con HMAC (X-Signature) usando el webhook_secret del tenant.
statusSignifica
queuedEncolado, todavía no se intentó enviar.
retryingSUNAT tuvo una falla de sistema (código 100–999); reintentando con backoff.
acceptedSUNAT lo aceptó — con o sin observaciones (código <2000 o ≥4000).
rejectedRechazo definitivo o el comprobante quedó anulado por SUNAT (código 1000–3999). No se reintenta solo.
voidedUn documento antes aceptado, dado de baja después (RA o RC de anulación).

Códigos de respuesta SUNAT

El campo codigo_sunat en cada respuesta cae en uno de estos cinco rangos.

RangoSignificaAcción
0Aceptado sin novedadListo.
100–999Excepción del sistema SUNATSe reintenta solo, con backoff.
1000–1999Excepción del contribuyenteCorregir el payload; no se reintenta solo.
2000–3999El comprobante queda anuladoNo reintentar — hay que reemitir con datos correctos.
4000+ObservaciónAceptado igual — mostrar la nota, no es un error.

Un caso real, para que quede claro por qué esto importa: un comprobante con código 3275 (dentro de 2000–3999) puede llegar con la llamada HTTP en apariencia "exitosa" — el error está adentro del CDR, no en el transporte. Un integrador que solo revisa si el request no falló, sin mirar codigo_sunat, puede terminar tratando como válido un comprobante que SUNAT ya anuló.

Idempotencia

Reenviar nunca duplica ni quema un correlativo.

Todo POST de emisión acepta Idempotency-Key. Usa un identificador único y estable por comprobante del lado de tu sistema — el id del documento en tu base, no el de la venta completa si una venta puede generar más de un comprobante. Reenviar la misma clave devuelve el mismo documento ya creado, sin pedir un correlativo nuevo.

header
Idempotency-Key: a1e4c9f0-6b3d-4e21-9a7f-2d8801c5f3aa

Emisores

Alta en 3 pasos antes de poder emitir nada.

Un emisor no queda listo con un solo request: RUC y datos fiscales primero, certificado digital después, credenciales SOL al final. Los tres pasos son independientes porque en la práctica llegan en momentos distintos — el certificado puede tardar en llegar del lado del cliente.

1. Alta del emisor

POST/issuersclave de tenant
rucrequerido
11 dígitos.
business_namerequerido
Razón social.
addressrequerido
Dirección fiscal.
ubigeorequerido
6 dígitos INEI.
environment
beta (default) o production.

2. Certificado digital

POST/issuers/{id}/certificatemultipart/form-data

Archivo .pfx/.p12 propio del emisor + su contraseña. Se valida que no esté vencido y, si el certificado trae el RUC en el asunto, que coincida con el del emisor.

POST/issuers/{id}/logomultipart/form-data, campo logo
DELETE/issuers/{id}/logo
POST/logocon el token del emisor, sin {id}
DELETE/logo

Imagen PNG o JPG (hasta 1 MB) que aparece en el encabezado del PDF de los comprobantes de ese emisor. Se reduce y se vuelve a guardar como PNG; el archivo original no se conserva. También se puede cargar desde el panel. Sin logo, el PDF sale solo con el nombre de la empresa.

POST /logo es para un sistema (por ejemplo un POS) que solo conoce el token de su emisor: cambia únicamente el logo de ese RUC.

3. Credenciales SOL

POST/issuers/{id}/sol
json
{
  "sol_user": "ASELVA06",
  "sol_password": "••••••••"
}

Tiene que ser un usuario secundario propio, nunca el maestro. El maestro (representante legal) puede loguearse sin problema en el portal SOL, pero SUNAT rechaza el servicio web con código 102 si se usa para el envío automatizado. Crea un usuario secundario en SUNAT con el permiso «SEE - Del Contribuyente y Envío de Documentos» explícitamente asignado.

Token del emisor

POST/issuers/{id}/token

Genera un isk_live_… / isk_test_… scopeado a ese único RUC. Se muestra una sola vez — no queda forma de recuperarlo después, solo de rotarlo (invalida el anterior de inmediato).

Comprobantes

Un contrato: JSON adentro, {document_id, status} afuera. Nunca se manipula XML, ZIP ni SOAP del lado del cliente.

Factura / boleta

POST/invoices
tipo_documentorequerido
01 factura, 03 boleta.
clienterequerido
tipo_documento (catálogo 06: 6 RUC, 1 DNI, 0 sin doc), numero_documento, razon_social, dirección opcional.
items[]requerido
Cada línea trae sus propios montos ya calculados — la API valida que cuadren, no los calcula por ti: valor_unitario, valor_venta, base_igv, igv, total_impuestos, precio_unitario.
totalesrequerido
gravadas, exoneradas, inafectas, igv, valor_venta, subtotal, importe_total.
Si la venta tuvo un descuento aplicado a nivel de venta completa, repártelo entre las líneas antes de armar items[] — el valor de cada ítem debe reflejar ya el descuento, no el precio de lista. La suma de los ítems tiene que cuadrar con totales.valor_venta.
curl
curl -X POST https://vendacoresunat.com/api/v1/invoices \
  -H "Authorization: Bearer isk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: doc-00931" \
  -d '{
    "tipo_documento": "03",
    "moneda": "PEN",
    "cliente": { "tipo_documento": "1", "numero_documento": "45678912",
                 "razon_social": "Willy Fernandez" },
    "items": [{
      "descripcion": "Camisa talla M", "unidad": "NIU", "cantidad": 1,
      "valor_unitario": 8.47, "valor_venta": 8.47, "base_igv": 8.47,
      "porcentaje_igv": 18, "igv": 1.53, "tipo_afectacion_igv": "10",
      "total_impuestos": 1.53, "precio_unitario": 10.00
    }],
    "totales": { "gravadas": 8.47, "igv": 1.53, "total_impuestos": 1.53,
                 "valor_venta": 8.47, "subtotal": 10.00, "importe_total": 10.00 }
  }'
202 · respuesta
{
  "document_id": "01a0446b-a2cd-7072-89f5-14abb2f79143",
  "tipo": "03", "serie": "B007", "numero": 1,
  "nombre": "20612873381-03-B007-1",
  "status": "queued",
  "links": { "pdf": "…", "xml": "…", "cdr": "…" }
}

Notas de crédito / débito

POST/credit-notes
POST/debit-notes

Mismo cuerpo que una factura/boleta, más el documento que corrigen:

documento_afectadorequerido
Serie-número del comprobante original.
codigo_motivorequerido
Catálogo 09 (crédito) / 10 (débito) de SUNAT.
motivorequerido
Descripción libre, máx. 100 caracteres.
Una nota de crédito parcial lleva un ítem propio con la descripción y el monto de la corrección — nunca los ítems completos del documento original si el ajuste es menor al total.

Resumen diario de boletas (RC)

POST/summaries

Referencia boletas ya emitidas por document_id, agrupadas en dos listas:

boletas[]
Se reportan como nuevas (catálogo 19, estado 1). Opcional.
anulaciones[]
Único canal para anular una boleta (estado 3) — la comunicación de baja (RA) no las admite. Opcional, pero al menos uno de los dos debe traer datos.

Comunicación de baja (RA)

POST/voided

Anula factura, nota de crédito o de débito ya aceptada — boletas no, van por /summaries → anulaciones. Para liquidaciones de compra (04) el mismo endpoint genera el resumen de reversiones (RR); no se pueden mezclar con facturas o notas en una misma solicitud.

json
{
  "fecha_generacion": "2026-08-27",
  "documentos": [
    { "document_id": "…", "motivo": "Error en el monto" }
  ]
}

Guía de remisión (GRE 2.0)

POST/despatches

Traslado de bienes por cuenta propia (remitente). A diferencia del resto, no usa el servicio web SOAP tradicional — GRE 2.0 firma localmente y envía por REST + OAuth2. Requiere credenciales GRE separadas en el emisor (POST /issuers/{id}/gre). Shape completo disponible aparte — es protocolo distinto, no cabe en esta página sin volverla ruidosa.

Consulta y descarga

Estado de un documento

GET/documents/{id}
GET/documentslista, filtros: tipo, status, desde, hasta

Archivos

GET/documents/{id}/pdf
GET/documents/{id}/xml
GET/documents/{id}/cdrconstancia de SUNAT, .zip

Webhooks

Alternativa a hacer polling — SUNAT avisa cuando resuelve.

payload · document.accepted
{
  "event": "document.accepted",
  "document_id": "01a0446b-…",
  "codigo_sunat": "0",
  "mensaje": "La Boleta de Venta numero B007-1, ha sido aceptado"
}

Firmado con X-Signature (HMAC-SHA256 sobre el cuerpo crudo, con el webhook_secret del tenant) — verifica la firma antes de confiar en el contenido.

Flujo de punta a punta

  1. Alta del emisor → certificado → SOL → token. Una sola vez por RUC.
  2. POST /invoices con el token del emisor → 202, document_id.
  3. Poll GET /documents/{id} cada 1–2s, o espera el webhook.
  4. status: "accepted" con codigo_sunat: "0" → listo. Con observaciones (≥4000) → aceptado igual, mostrar la nota.
  5. ¿Hay que corregir? Nota de crédito/débito si ya se envió al cliente; RA o anulación por RC si hay que anularlo del todo.

Ambiente de pruebas

Usa un token isk_test_… o sk_test_… — resuelve solo contra el ambiente beta de SUNAT, sin tocar ningún RUC real. El emisor de pruebas oficial de SUNAT:

RUC20000000001
Usuario SOLMODDATOS
Clave SOLmoddatos
Beta es compartido entre muchos desarrolladores y se cae con frecuencia. Un 1033 — registrado previamente ahí es normal: cambia el correlativo e intenta de nuevo, no es un bug de tu integración.