Guías por comprobante
Factura electrónica por API
JSON mínimo para emitir una factura electrónica SUNAT por API, al contado o a crédito, desde una cotización, y cómo enviarla por correo o WhatsApp.
Actualizado el
La factura (tipo 01) es para vender a empresas y negocios con RUC. Se envía a SUNAT en la misma llamada (según la
configuración de la empresa), así que la respuesta ya suele traer aceptado u observado y la respuesta de SUNAT.
- Endpoint:
POST /comprobantes· alcancecomprobantes.emitir. - Serie: una de factura de tu local (
F001…)."numero_documento": "#"toma el siguiente correlativo.
JSON mínimo
Una factura de S/ 1.180 (40 bolsas de cemento a S/ 29,50 con IGV) pagada en efectivo:
{
"serie_documento": "F001",
"numero_documento": "#",
"fecha_de_emision": "2026-11-04",
"hora_de_emision": "10:15:00",
"codigo_tipo_operacion": "0101",
"codigo_tipo_documento": "01",
"codigo_tipo_moneda": "PEN",
"datos_del_cliente_o_receptor": {
"codigo_tipo_documento_identidad": "6",
"numero_documento": "20601234567",
"apellidos_y_nombres_o_razon_social": "CONSTRUCTORA ANDINA S.A.C.",
"codigo_pais": "PE",
"ubigeo": "150101",
"direccion": "Av. Lima 123",
"correo_electronico": "compras@andina.pe"
},
"totales": {
"total_operaciones_gravadas": 1000,
"total_igv": 180,
"total_impuestos": 180,
"total_valor": 1000,
"total_venta": 1180
},
"items": [{
"codigo_interno": "CEM-42",
"descripcion": "Cemento 42.5 kg",
"unidad_de_medida": "NIU",
"cantidad": 40,
"valor_unitario": 25,
"codigo_tipo_precio": "01",
"precio_unitario": 29.5,
"codigo_tipo_afectacion_igv": "10",
"total_base_igv": 1000,
"porcentaje_igv": 18,
"total_igv": 180,
"total_impuestos": 180,
"total_valor_item": 1000,
"total_item": 1180
}],
"pagos": [{ "codigo_metodo_pago": "01", "codigo_destino_pago": "cash", "monto": 1180 }]
}
Guárdalo como factura.json y envíalo:
curl -X POST https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes \
-H "Authorization: Bearer $SFP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: factura-$(uuidgen)" \
-d @factura.json
<?php
$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',
'Idempotency-Key: ' . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => file_get_contents('factura.json'),
]);
$factura = json_decode(curl_exec($ch), true);
import { readFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
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',
'Idempotency-Key': randomUUID(),
},
body: await readFile('factura.json', 'utf8'),
});
const factura = await res.json();
import json, os, uuid, requests
r = requests.post(
"https://pruebas.sistemafacturacionperu.com/api/v1/comprobantes",
json=json.load(open("factura.json", encoding="utf-8")),
headers={"Authorization": f"Bearer {os.environ['SFP_API_KEY']}", "Idempotency-Key": str(uuid.uuid4())},
timeout=90,
)
factura = r.json()
Respuesta (201):
{
"success": true,
"data": {
"id": 1521,
"external_id": "9a0b1c2d-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"tipo": "01",
"numero_completo": "F001-120",
"total": 1180,
"estado": { "codigo": "05", "clave": "aceptado", "descripcion": "Aceptado" },
"sunat": { "codigo": "0", "descripcion": "La Factura numero F001-120, ha sido aceptada", "notas": [] },
"enlaces": { "pdf": "…/comprobantes/1521/pdf", "xml": "…/comprobantes/1521/xml", "cdr": "…/comprobantes/1521/cdr" }
}
}
Los campos que más se equivocan
| Campo | Qué poner |
|---|---|
datos_del_cliente_o_receptor.codigo_tipo_documento_identidad |
6 (RUC). Una factura a un DNI la rechaza SUNAT (2017: el receptor debe tener RUC). |
codigo_tipo_operacion |
0101 venta interna; 1001 si está sujeta a detracción; 0200 exportación. |
codigo_tipo_afectacion_igv |
10 gravado, 20 exonerado, 30 inafecto, 40 exportación. |
valor_unitario / precio_unitario |
Sin IGV / con IGV. Con codigo_tipo_precio: "01" (precio unitario). |
unidad_de_medida |
Catálogo SUNAT: NIU (unidad, bienes), ZZ (servicios), KGM, MTR, LTR… |
pagos[].codigo_metodo_pago |
01 efectivo; los demás son los medios de pago de tu empresa. |
pagos[].codigo_destino_pago |
cash (caja) o el id de una cuenta de cobro. |
Factura a crédito
Con "codigo_condicion_de_pago": "02" y las cuotas. SUNAT pide al menos una cuota en una venta a crédito (3249):
{
"codigo_condicion_de_pago": "02",
"cuotas": [
{ "fecha": "2026-12-04", "codigo_tipo_moneda": "PEN", "monto": 590 },
{ "fecha": "2027-01-04", "codigo_tipo_moneda": "PEN", "monto": 590 }
]
}
Sin pagos, la factura queda en Por cobrar. Registra cada pago después con POST /comprobantes/{id}/cobros:
{ "fecha": "2026-12-04", "monto": 590, "medio": "005", "cuenta_cobro_id": 2, "referencia": "OP-778812" }
medio es el código del medio de pago de tu empresa; si no es efectivo, va con la cuenta de cobro (cuenta_cobro_id,
un banco o billetera de ese tipo) y el número de operación. Sin cuenta, el cobro entra a la caja. Un cobro mayor que el
saldo responde 422 cobro_excede_saldo. GET /comprobantes/{id}/cobros dice cuánto falta.
Facturar una cotización, un pedido o una nota de venta
Agrega origen y el comprobante queda enlazado, como en la web:
{ "origen": { "tipo": "cotizacion", "id": 15 } }
Enviarla a tu cliente
POST /comprobantes/{id}/enviar la manda por correo o por el WhatsApp de tu empresa (la línea que conectaste en
Canales › WhatsApp; nunca por una línea de la plataforma):
{ "canal": "whatsapp", "destino": "987654321" }
{ "canal": "correo", "destino": "compras@andina.pe, contabilidad@andina.pe" }
Si el proveedor de correo o de WhatsApp falla, responde 502 envio_fallido.
Si SUNAT no responde
Si al emitir SUNAT no contestó, la factura queda registrado. Reenvíala con
POST /comprobantes/{id}/reenviar-sunat (con su propia Idempotency-Key). Una que ya está aceptada responde 409
no_reenviable.
Errores frecuentes
code |
Qué pasó |
|---|---|
serie_invalida |
La serie no es de factura o no es de tu local. |
regimen_no_permite |
Tu régimen (Nuevo RUS) no emite facturas. |
deuda_vencida |
El cliente tiene deuda vencida y tu empresa restringe la venta en ese caso. |
producto_no_existe |
Con control de stock, el codigo_interno no existe: créalo con POST /productos. |
detraccion_minimo |
La detracción no aplica a ese monto (casos especiales). |
limite_plan |
Tu plan ya no permite emitir más este ciclo. |
El resto está en el catálogo de errores.