Saltar al contenido
Tarifa anual desde S/ 37 al mes + IGV. Pide tu demo sin costo. Reserva tu demo
Sistema Facturación Perú
Menú del portal

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

  1. Entra a tu sistema y abre Configuración › Sistema › Integraciones › API.
  2. En Modo pruebas, pulsa Crear clave de pruebas.
  3. 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 serie B001.
  • La fecha va en la hora de Lima (AAAA-MM-DD). Los ejemplos la calculan solos.
  • La cabecera Idempotency-Key hace 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é?

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.

Portal para desarrolladores · ¿Algo no está claro o te falta un ejemplo? Escríbenos y lo mejoramos.