Empezar
Inicio rápido: tu primera boleta de prueba en 5 minutos
Crea una clave de pruebas, prueba la conexión, emite tu primera boleta electrónica contra la beta de SUNAT y descarga su PDF con cURL, PHP, Node.js o Python.
Actualizado el
En cinco minutos vas a emitir una boleta electrónica de prueba con la API v1 y descargar su PDF. Todo pasa en el modo pruebas: una empresa aparte, conectada a la beta de SUNAT, que no toca tus comprobantes, tu stock ni tus correlativos. Los ejemplos están en cURL, PHP, Node.js y Python: elige tu lenguaje en las pestañas y se queda elegido en toda la página.
Qué necesitas: una cuenta de Sistema Facturación Perú en el Plan PRO o Empresa (la API viene desde PRO) y un usuario administrador o con el permiso «Gestionar tokens de API». Si todavía no tienes cuenta, revisa los planes.
1. Crea tu clave de pruebas
- Entra a tu sistema y abre Configuración › Sistema › Integraciones › API.
- En Modo pruebas, pulsa Crear clave de pruebas.
- Copia la clave
sfp_test_…. Se muestra una sola vez: guárdala en una variable de entorno, nunca en el código ni en el navegador.
export SFP_API_KEY="sfp_test_TU_CLAVE"
Todas las llamadas de esta guía van a https://pruebas.sistemafacturacionperu.com/api/v1 con la cabecera
Authorization: Bearer <tu clave>. La clave nunca va en la URL: si la pones ahí, la API responde 401 clave_en_url.
2. Prueba la conexión
GET /yo te dice con qué clave, usuario, local y permisos estás llamando. Es la mejor forma de saber que todo está
bien conectado.
curl https://pruebas.sistemafacturacionperu.com/api/v1/yo \
-H "Authorization: Bearer $SFP_API_KEY"
<?php
$ch = curl_init('https://pruebas.sistemafacturacionperu.com/api/v1/yo');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SFP_API_KEY'), 'Accept: application/json'],
]);
$yo = json_decode(curl_exec($ch), true);
echo $yo['data']['empresa']['razon_social'], ' · modo ', $yo['data']['modo'], PHP_EOL;
// Node.js 18 o más (fetch viene incluido). Guárdalo como yo.mjs y corre: node yo.mjs
const res = await fetch('https://pruebas.sistemafacturacionperu.com/api/v1/yo', {
headers: { Authorization: `Bearer ${process.env.SFP_API_KEY}`, Accept: 'application/json' },
});
const { data } = await res.json();
console.log(data.empresa.razon_social, '· modo', data.modo);
# pip install requests
import os, requests
r = requests.get(
"https://pruebas.sistemafacturacionperu.com/api/v1/yo",
headers={"Authorization": f"Bearer {os.environ['SFP_API_KEY']}"},
timeout=30,
)
data = r.json()["data"]
print(data["empresa"]["razon_social"], "· modo", data["modo"])
Si todo va bien, la respuesta dice "modo": "pruebas" y "sunat": "beta":
{
"success": true,
"data": {
"empresa": { "ruc": "20000000001", "razon_social": "EMPRESA DE PRUEBAS API S.A.C.", "nombre_comercial": null },
"modo": "pruebas",
"sunat": "beta",
"clave": { "id": 7, "nombre": "Mi tienda · pruebas", "prefijo": "sfp_test_Xy12Ab34", "vence": null },
"legacy": false,
"alcances": ["comprobantes.emitir", "comprobantes.leer", "consultas"],
"usuario": { "id": 1, "nombre": "Ana Torres", "tipo": "admin" },
"establecimiento": { "id": 1, "codigo": "0000", "nombre": "Tienda principal" }
}
}
¿Te respondió 401 o 403? Mira el code de la respuesta en el catálogo de errores.
3. Emite tu primera boleta
Una boleta de S/ 60 (dos polos de S/ 30 con IGV) a una persona con DNI, pagada en efectivo. Fíjate en tres cosas:
"numero_documento": "#"pide el siguiente correlativo de la serieB001.- La fecha va en la hora de Lima (
AAAA-MM-DD). Los ejemplos la calculan solos. - La cabecera
Idempotency-Keyhace que, si la red falla y repites la llamada con la misma clave, la boleta no se emita dos veces. Usa un UUID nuevo por cada comprobante.
curl -X POST https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes \
-H "Authorization: Bearer $SFP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"serie_documento": "B001",
"numero_documento": "#",
"fecha_de_emision": "'"$(TZ=America/Lima date +%F)"'",
"hora_de_emision": "'"$(TZ=America/Lima date +%T)"'",
"codigo_tipo_operacion": "0101",
"codigo_tipo_documento": "03",
"codigo_tipo_moneda": "PEN",
"datos_del_cliente_o_receptor": {
"codigo_tipo_documento_identidad": "1",
"numero_documento": "70123456",
"apellidos_y_nombres_o_razon_social": "Ana Pérez",
"codigo_pais": "PE"
},
"totales": {
"total_operaciones_gravadas": 50.85,
"total_igv": 9.15,
"total_impuestos": 9.15,
"total_valor": 50.85,
"total_venta": 60
},
"items": [{
"codigo_interno": "POLO-01",
"descripcion": "Polo azul",
"unidad_de_medida": "NIU",
"cantidad": 2,
"valor_unitario": 25.42,
"codigo_tipo_precio": "01",
"precio_unitario": 30,
"codigo_tipo_afectacion_igv": "10",
"total_base_igv": 50.85,
"porcentaje_igv": 18,
"total_igv": 9.15,
"total_impuestos": 9.15,
"total_valor_item": 50.85,
"total_item": 60
}],
"pagos": [{ "codigo_metodo_pago": "01", "codigo_destino_pago": "cash", "monto": 60 }]
}'
<?php
$lima = new DateTime('now', new DateTimeZone('America/Lima'));
$boleta = [
'serie_documento' => 'B001',
'numero_documento' => '#',
'fecha_de_emision' => $lima->format('Y-m-d'),
'hora_de_emision' => $lima->format('H:i:s'),
'codigo_tipo_operacion' => '0101',
'codigo_tipo_documento' => '03',
'codigo_tipo_moneda' => 'PEN',
'datos_del_cliente_o_receptor' => [
'codigo_tipo_documento_identidad' => '1',
'numero_documento' => '70123456',
'apellidos_y_nombres_o_razon_social' => 'Ana Pérez',
'codigo_pais' => 'PE',
],
'totales' => [
'total_operaciones_gravadas' => 50.85, 'total_igv' => 9.15, 'total_impuestos' => 9.15,
'total_valor' => 50.85, 'total_venta' => 60,
],
'items' => [[
'codigo_interno' => 'POLO-01', 'descripcion' => 'Polo azul', 'unidad_de_medida' => 'NIU', 'cantidad' => 2,
'valor_unitario' => 25.42, 'codigo_tipo_precio' => '01', 'precio_unitario' => 30,
'codigo_tipo_afectacion_igv' => '10', 'total_base_igv' => 50.85, 'porcentaje_igv' => 18,
'total_igv' => 9.15, 'total_impuestos' => 9.15, 'total_valor_item' => 50.85, 'total_item' => 60,
]],
'pagos' => [['codigo_metodo_pago' => '01', 'codigo_destino_pago' => 'cash', 'monto' => 60]],
];
$idempotencia = bin2hex(random_bytes(16)); // guárdala junto a tu venta para reintentar con la misma
$ch = curl_init('https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 90,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SFP_API_KEY'),
'Content-Type: application/json',
'Accept: application/json',
'Idempotency-Key: ' . $idempotencia,
],
CURLOPT_POSTFIELDS => json_encode($boleta),
]);
$respuesta = json_decode(curl_exec($ch), true);
$estado = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($estado !== 201) {
throw new RuntimeException($respuesta['code'] . ': ' . $respuesta['message']);
}
echo 'Emitida ', $respuesta['data']['numero_completo'], ' (id ', $respuesta['data']['id'], ')', PHP_EOL;
import { randomUUID } from 'node:crypto';
const lima = new Date().toLocaleString('sv-SE', { timeZone: 'America/Lima' }); // "2026-11-04 10:15:00"
const [fecha, hora] = lima.split(' ');
const boleta = {
serie_documento: 'B001',
numero_documento: '#',
fecha_de_emision: fecha,
hora_de_emision: hora,
codigo_tipo_operacion: '0101',
codigo_tipo_documento: '03',
codigo_tipo_moneda: 'PEN',
datos_del_cliente_o_receptor: {
codigo_tipo_documento_identidad: '1',
numero_documento: '70123456',
apellidos_y_nombres_o_razon_social: 'Ana Pérez',
codigo_pais: 'PE',
},
totales: { total_operaciones_gravadas: 50.85, total_igv: 9.15, total_impuestos: 9.15, total_valor: 50.85, total_venta: 60 },
items: [{
codigo_interno: 'POLO-01', descripcion: 'Polo azul', unidad_de_medida: 'NIU', cantidad: 2,
valor_unitario: 25.42, codigo_tipo_precio: '01', precio_unitario: 30, codigo_tipo_afectacion_igv: '10',
total_base_igv: 50.85, porcentaje_igv: 18, total_igv: 9.15, total_impuestos: 9.15,
total_valor_item: 50.85, total_item: 60,
}],
pagos: [{ codigo_metodo_pago: '01', codigo_destino_pago: 'cash', monto: 60 }],
};
const res = await fetch('https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SFP_API_KEY}`,
'Content-Type': 'application/json',
Accept: 'application/json',
'Idempotency-Key': randomUUID(), // guárdala junto a tu venta para reintentar con la misma
},
body: JSON.stringify(boleta),
});
const json = await res.json();
if (res.status !== 201) throw new Error(`${json.code}: ${json.message}`);
console.log('Emitida', json.data.numero_completo, `(id ${json.data.id})`);
import os, uuid, requests
from datetime import datetime
from zoneinfo import ZoneInfo
lima = datetime.now(ZoneInfo("America/Lima"))
boleta = {
"serie_documento": "B001",
"numero_documento": "#",
"fecha_de_emision": lima.strftime("%Y-%m-%d"),
"hora_de_emision": lima.strftime("%H:%M:%S"),
"codigo_tipo_operacion": "0101",
"codigo_tipo_documento": "03",
"codigo_tipo_moneda": "PEN",
"datos_del_cliente_o_receptor": {
"codigo_tipo_documento_identidad": "1",
"numero_documento": "70123456",
"apellidos_y_nombres_o_razon_social": "Ana Pérez",
"codigo_pais": "PE",
},
"totales": {"total_operaciones_gravadas": 50.85, "total_igv": 9.15, "total_impuestos": 9.15,
"total_valor": 50.85, "total_venta": 60},
"items": [{
"codigo_interno": "POLO-01", "descripcion": "Polo azul", "unidad_de_medida": "NIU", "cantidad": 2,
"valor_unitario": 25.42, "codigo_tipo_precio": "01", "precio_unitario": 30,
"codigo_tipo_afectacion_igv": "10", "total_base_igv": 50.85, "porcentaje_igv": 18,
"total_igv": 9.15, "total_impuestos": 9.15, "total_valor_item": 50.85, "total_item": 60,
}],
"pagos": [{"codigo_metodo_pago": "01", "codigo_destino_pago": "cash", "monto": 60}],
}
r = requests.post(
"https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes",
json=boleta,
headers={
"Authorization": f"Bearer {os.environ['SFP_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()), # guárdala junto a tu venta para reintentar con la misma
},
timeout=90,
)
respuesta = r.json()
if r.status_code != 201:
raise RuntimeError(f"{respuesta['code']}: {respuesta['message']}")
print("Emitida", respuesta["data"]["numero_completo"], f"(id {respuesta['data']['id']})")
La API responde 201 con el comprobante:
{
"success": true,
"data": {
"id": 1520,
"external_id": "6f1c7d0e-8f7e-4a1c-9d5b-2a2f8e1c9b10",
"tipo": "03",
"serie": "B001",
"numero": 845,
"numero_completo": "B001-845",
"fecha_emision": "2026-11-04",
"moneda": "PEN",
"total": 60,
"estado": { "codigo": "01", "clave": "registrado", "descripcion": "Registrado" },
"sunat": null,
"enlaces": {
"pdf": "https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes/1520/pdf",
"pdf_ticket": "https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes/1520/pdf?formato=ticket",
"xml": "https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes/1520/xml",
"cdr": null
}
}
}
El estado es registrado porque las boletas no viajan solas a SUNAT: van en el resumen diario, que el sistema
arma y envía por ti. Cuando SUNAT lo acepte, la boleta pasa a aceptado y, si tienes un
webhook, te avisamos con comprobante.aceptado.
4. Descarga el PDF
Con el id (o el external_id) descarga el PDF en A4, A5 o en formato ticket (?formato=ticket, ticket_58 o
ticket_50).
curl https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes/1520/pdf \
-H "Authorization: Bearer $SFP_API_KEY" \
-o B001-845.pdf
<?php
$ch = curl_init('https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes/1520/pdf?formato=ticket');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SFP_API_KEY')],
]);
file_put_contents('B001-845.pdf', curl_exec($ch));
import { writeFile } from 'node:fs/promises';
const res = await fetch('https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes/1520/pdf?formato=ticket', {
headers: { Authorization: `Bearer ${process.env.SFP_API_KEY}` },
});
await writeFile('B001-845.pdf', Buffer.from(await res.arrayBuffer()));
r = requests.get(
"https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes/1520/pdf",
params={"formato": "ticket"},
headers={"Authorization": f"Bearer {os.environ['SFP_API_KEY']}"},
timeout=60,
)
open("B001-845.pdf", "wb").write(r.content)
El XML firmado sale de /comprobantes/{id}/xml y la constancia de SUNAT (CDR) de /comprobantes/{id}/cdr cuando ya
existe. Para tu cliente, la respuesta trae también enlaces_publicos: direcciones de descarga que no piden clave.
5. ¿Y ahora qué?
- Entiende el camino de un comprobante: series, locales, pruebas y producción.
- Emite una factura o una guía de remisión.
- Recibe los cambios de estado con webhooks en vez de preguntar.
- Prueba cada endpoint desde la referencia interactiva o importa la colección de Postman.
- Cuando todo funcione, sigue la lista de pase a producción: creas una clave
sfp_live_y cambias la dirección ahttps://<tu-subdominio>.sistemafacturacionperu.com/api/v1.
Lo emitido en pruebas se borra a los 7 días. Cada clave de pruebas ve solo lo que ella emitió. Los clientes y productos de la empresa de pruebas son compartidos: no pongas datos reales.