Guía de inicio rápido — SWSPERU FACT - Facturador SUNAT API
Esta guía te lleva desde “no tengo cuenta” hasta “emití mi primera Boleta de prueba, la notifiqué por WhatsApp y consulté un RUC” contra el ambiente beta de SUNAT en 5 pasos. Para el detalle completo de cada endpoint, catálogos, y códigos de error, ver la Referencia de la API.
Todos los comandos de esta guía fueron ejecutados en vivo contra el ambiente beta de SUNAT el 2026-08-13 — no son ejemplos teóricos.
En todos los ejemplos, reemplaza:
{BASE_URL}porhttps://<tu-dominio-facturador>/api/v1/external(la URL que te entregó tu proveedor).{API_KEY}por tu API key real (formatosak_...).
Paso 0 — Consigue tu API key
Si ya tienes una API key, salta al paso 1. Si no:
- Regístrate en
https://portal.swsperu-fact.comcon los datos de tu empresa (nombre, RUC, email, contraseña). - Tu cuenta queda en estado
pendientehasta que el equipo de SWSPERU FACT la revisa y aprueba — normalmente es rápido, y te llega una notificación por correo (y por WhatsApp, si dejaste un teléfono) en cuanto se aprueba. - Una vez aprobada, entra al portal y genera tu primera API key desde la sección de configuración — se muestra una sola vez en texto plano, cópiala de inmediato a un lugar seguro.
Tu key nueva opera en el ambiente beta de SUNAT (pruebas, sin validez
tributaria real) — perfecto para seguir esta guía. Cuando tu integración esté
validada de punta a punta, coordina con tu proveedor una key de produccion
(ver Referencia de la API, sección 10).
Si prefieres que el equipo de SWSPERU FACT te dé de alta directamente en vez de autoregistrarte, contacta a tu proveedor — ver Referencia de la API, sección 2 para el detalle completo de ambos caminos.
Paso 1 — Verifica tu API key
Antes de emitir nada, confirma que tu key funciona con el endpoint más simple
de toda la API: GET /me. No crea ni modifica ningún dato.
curl
curl -s "{BASE_URL}/me" \
-H "Authorization: Bearer {API_KEY}"
PHP
<?php
$baseUrl = 'https://<tu-dominio-facturador>/api/v1/external';
$apiKey = 'sak_...';
$ch = curl_init("$baseUrl/me");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiKey",
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP $httpCode\n";
echo $response . "\n";
JavaScript
const baseUrl = 'https://<tu-dominio-facturador>/api/v1/external';
const apiKey = 'sak_...';
const response = await fetch(`${baseUrl}/me`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
console.log(response.status);
console.log(await response.json());
Respuesta esperada (200)
{
"tenant": { "id": 15, "name": "Tu Empresa S.A.C.", "slug": "tu-empresa", "status": "active" },
"environment": "beta"
}
environment confirma contra qué ambiente está apuntando la key que acabas
de usar — en este paso debería decir siempre "beta".
Si en cambio recibes 401, revisa que el header Authorization esté bien
formado (Bearer + tu key, sin comillas ni espacios extra) y que tu key no
esté revocada — ver
Referencia de la API, sección 3
para el detalle de los errores de autenticación.
Paso 2 — Emite tu primera Boleta de prueba
POST /invoices con tipo_comprobante: "03" (Boleta). No necesitas enviar
tipo_comprobante explícitamente si prefieres que la API lo infiera del
cliente (ver Referencia de la API, sección 7.1);
en esta guía lo enviamos explícito para que el ejemplo sea autocontenido.
curl
curl -s -X POST "{BASE_URL}/invoices" \
-H "Authorization: Bearer {API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"tipo_comprobante": "03",
"external_reference": "MI-PRIMER-PEDIDO",
"customer": {
"tax_id": "10412345678",
"name": "CLIENTE DE PRUEBA",
"address": "AV. SIEMPRE VIVA 123",
"document_type": "1"
},
"items": [
{ "sku": "DEMO-1", "description": "Producto de prueba", "quantity": 2, "unit_price": 15.90 }
]
}'
PHP
<?php
$baseUrl = 'https://<tu-dominio-facturador>/api/v1/external';
$apiKey = 'sak_...';
$payload = [
'tipo_comprobante' => '03',
'external_reference' => 'MI-PRIMER-PEDIDO',
'customer' => [
'tax_id' => '10412345678',
'name' => 'CLIENTE DE PRUEBA',
'address' => 'AV. SIEMPRE VIVA 123',
'document_type' => '1',
],
'items' => [
['sku' => 'DEMO-1', 'description' => 'Producto de prueba', 'quantity' => 2, 'unit_price' => 15.90],
],
];
$ch = curl_init("$baseUrl/invoices");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$invoice = json_decode($response, true);
echo "HTTP $httpCode — estado_sunat: {$invoice['estado_sunat']}\n";
JavaScript
const baseUrl = 'https://<tu-dominio-facturador>/api/v1/external';
const apiKey = 'sak_...';
const response = await fetch(`${baseUrl}/invoices`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
tipo_comprobante: '03',
external_reference: 'MI-PRIMER-PEDIDO',
customer: {
tax_id: '10412345678',
name: 'CLIENTE DE PRUEBA',
address: 'AV. SIEMPRE VIVA 123',
document_type: '1',
},
items: [
{ sku: 'DEMO-1', description: 'Producto de prueba', quantity: 2, unit_price: 15.9 },
],
}),
});
const invoice = await response.json();
console.log(response.status, invoice.estado_sunat);
Respuesta esperada (201, capturada en vivo)
{
"id": 7,
"environment": "beta",
"external_reference": "MI-PRIMER-PEDIDO",
"tipo_comprobante": "03",
"serie": "B001",
"numero": "00000003",
"fecha_emision": "2026-08-13T00:00:00.000000Z",
"moneda": "PEN",
"subtotal": "26.95",
"total_igv": "4.85",
"total": "31.80",
"customer": {
"tax_id": "10412345678",
"name": "CLIENTE DE PRUEBA",
"address": "AV. SIEMPRE VIVA 123",
"document_type": "1"
},
"estado_sunat": "aceptado",
"codigo_respuesta_sunat": "0",
"descripcion_sunat": "La Boleta numero B001-00000003, ha sido aceptada",
"documents": { "xml_available": true, "cdr_available": true, "pdf_available": true },
"items": [
{ "id": 7, "sku": "DEMO-1", "description": "Producto de prueba", "unit_measure": "NIU", "quantity": 2, "unit_price": "15.90", "tax_affectation_code": "10", "igv_amount": "4.85", "subtotal": "26.95" }
],
"created_at": "2026-08-13T19:40:16.000000Z"
}
estado_sunat: "aceptado" significa que SUNAT ya registró la Boleta.
Guarda el id de la respuesta (7 en este ejemplo) — lo necesitas para el
paso 3.
Si en cambio recibes 422 con error.code: "sunat_not_configured" o
"document_series_not_configured", tu cuenta todavía no tiene credenciales
SUNAT o una serie configurada — contacta a tu proveedor, ese es un paso de
configuración que hace el administrador de la plataforma, no algo que se
resuelva desde tu integración.
Paso 3 — Consulta el comprobante emitido
GET /invoices/{id}, usando el id que devolvió el paso 2.
curl
curl -s "{BASE_URL}/invoices/7" \
-H "Authorization: Bearer {API_KEY}"
PHP
<?php
// ... $baseUrl y $apiKey como en el paso 1 ...
$invoiceId = 7;
$ch = curl_init("$baseUrl/invoices/$invoiceId");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $apiKey"]);
$response = curl_exec($ch);
curl_close($ch);
echo $response . "\n";
JavaScript
const invoiceId = 7;
const response = await fetch(`${baseUrl}/invoices/${invoiceId}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
console.log(await response.json());
Respuesta esperada (200)
Mismo shape que el paso 2, más notes: [] (array de notas de crédito/débito
emitidas, vacío si no hay ninguna) y void: null (o el objeto de la
comunicación de baja, si se solicitó una).
Paso 4 — Descarga el PDF de tu comprobante
Cada comprobante que emites queda con un PDF (representación impresa, con QR)
listo para descargar de inmediato — revisa el bloque documents de la
respuesta del paso 2/3 para confirmar que pdf_available sea true antes de
pedirlo (ver Referencia de la API, sección 6.1).
El mismo mecanismo aplica a /xml y /cdr.
curl
curl -s "{BASE_URL}/invoices/7/pdf" \
-H "Authorization: Bearer {API_KEY}" \
-o comprobante.pdf
PHP
<?php
// ... $baseUrl y $apiKey como en el paso 1 ...
$invoiceId = 7;
$ch = curl_init("$baseUrl/invoices/$invoiceId/pdf");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $apiKey"]);
$pdfBytes = curl_exec($ch);
curl_close($ch);
file_put_contents('comprobante.pdf', $pdfBytes);
JavaScript
const invoiceId = 7;
const response = await fetch(`${baseUrl}/invoices/${invoiceId}/pdf`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const pdfBlob = await response.blob();
Respuesta esperada (200, verificado en vivo)
Content-Type: application/pdf, con el PDF real como body (no JSON) — una
página con los datos del comprobante, el hash del CDR y un código QR. Si tu
cuenta tiene branding configurado (logo/color/contacto, lo configura tu
proveedor por ahora), el PDF lo refleja automáticamente.
Si en cambio recibes 404 con error.code: "document_not_ready", el archivo
todavía no existe — pasa casi siempre con el /cdr de una Guía de Remisión
recién creada (es asíncrona, ver sección 6.1 de la referencia), nunca con el
/pdf o /xml de una Factura/Boleta ya emitida.
Paso 5 — Notifica por WhatsApp y consulta un RUC/DNI
Estos dos son opcionales — la API nunca los llama por ti automáticamente al emitir, los disparas tú cuando los necesites (ver Referencia de la API, sección 8.20-8.29 para el resto del catálogo de consultas).
5a — Avisa a tu cliente que su Boleta ya está lista
POST /invoices/{id}/whatsapp con el id del paso 2 y el teléfono del
cliente. Puedes llamarlo las veces que quieras sobre el mismo comprobante.
curl
curl -s -X POST "{BASE_URL}/invoices/7/whatsapp" \
-H "Authorization: Bearer {API_KEY}" \
-H "Content-Type: application/json" \
-d '{ "phone": "51987654321" }'
PHP
<?php
// ... $baseUrl y $apiKey como en el paso 1 ...
$invoiceId = 7;
$ch = curl_init("$baseUrl/invoices/$invoiceId/whatsapp");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(['phone' => '51987654321']));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $apiKey",
'Content-Type: application/json',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP $httpCode\n$response\n";
JavaScript
const invoiceId = 7;
const response = await fetch(`${baseUrl}/invoices/${invoiceId}/whatsapp`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ phone: '51987654321' }),
});
console.log(response.status, await response.json());
Respuesta esperada (202)
{ "message": "Notificación en camino." }
202, no 200: el envío ocurre en segundo plano (cola), esta respuesta solo
confirma que lo aceptamos. El mismo endpoint sirve para Notas de Crédito/Débito
(.../notes/{note_id}/whatsapp) y Guías de Remisión (/dispatch-guides/{id}/whatsapp)
— mismo body, mismo comportamiento.
5b — Consulta un RUC antes de armar tu siguiente Factura
GET /lookups/ruc/{ruc} te devuelve la razón social y dirección para
autocompletar customer — no cambia nada en tu cuenta, es solo consulta.
curl
curl -s "{BASE_URL}/lookups/ruc/20131312955" \
-H "Authorization: Bearer {API_KEY}"
PHP
<?php
// ... $baseUrl y $apiKey como en el paso 1 ...
$ruc = '20131312955';
$ch = curl_init("$baseUrl/lookups/ruc/$ruc");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $apiKey"]);
$response = curl_exec($ch);
curl_close($ch);
$ruc = json_decode($response, true);
echo "{$ruc['name']} — {$ruc['status']}\n";
JavaScript
const ruc = '20131312955';
const response = await fetch(`${baseUrl}/lookups/ruc/${ruc}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const data = await response.json();
console.log(data.name, data.status);
Respuesta esperada (200, capturada en vivo)
{
"ruc": "20131312955",
"name": "SUPERINTENDENCIA NACIONAL DE ADUANAS Y DE ADMINISTRACION TRIBUTARIA - SUNAT",
"status": "ACTIVO",
"domicile_conditions": "HABIDO",
"address": "AV. GARCILASO DE LA VEGA NRO. 1472 - LIMA LIMA LIMA",
"updated_at": "2026-08-17T15:28:43.000000Z"
}
El equivalente para persona natural es GET /lookups/dni/{dni} (mismo
patrón, headers y forma de llamarlo). Ambos comparten un cupo mensual
propio con el resto de /lookups/* (tipo de cambio, comisiones AFP,
establecimientos/representantes de un RUC, validación de comprobantes de
terceros) — si lo superas, recibes 429 lookup_quota_exceeded en vez del
resultado; contacta a tu proveedor si necesitas subirlo. Ver
Referencia de la API, sección 8.20-8.26
para el detalle completo, incluida la vigencia de caché de cada endpoint.
Ya tienes el flujo básico funcionando
Con estos pasos ya puedes emitir y consultar comprobantes contra el ambiente beta de SUNAT. Antes de pasar a producción:
- Lee la Referencia de la API completa — en particular
la sección 6 (estados posibles de un documento SUNAT:
aceptadono es el único resultado válido,observadotambién lo es) y la sección 7 (catálogos, para armar correctamente Facturas, Notas de Crédito/Débito y Guías de Remisión). - Prueba también los endpoints de Nota de Crédito/Débito
(
POST /invoices/{id}/notes) y Comunicación de Baja (POST /invoices/{id}/void) contra beta. - Prueba
GET /invoices(búsqueda con filtros de fecha/estado/cliente) para reconstruir tu historial sin depender de guardar cadaidtú mismo. - Revisa el resto del catálogo de consultas (
GET /lookups/tipo-cambio,/afp-comisiones,/ruc/{ruc}/establecimientos,/ruc/{ruc}/representantes,POST /lookups/cpe) y confirma tu cupo mensual con tu proveedor si tu integración va a hacer un volumen alto de consultas. - Cuando tu integración esté validada de punta a punta, coordina con tu
proveedor una API key de
produccion— ver Referencia de la API, sección 10.