Empezar
Conceptos de la API
Empresa y subdominio, locales y series, modo pruebas y producción, el camino de un comprobante ante SUNAT y la diferencia entre resumen diario y envío directo.
Actualizado el
Antes de integrar, conviene tener claras seis ideas. Con ellas entiendes por qué una boleta queda «registrada» y una factura «aceptada» al instante, o por qué tu clave emite en un local y no en otro.
Empresa y dirección de la API
Cada empresa de Sistema Facturación Perú tiene su propio subdominio y su propia API:
| Entorno | Dirección | Clave |
|---|---|---|
| Producción | https://<tu-subdominio>.sistemafacturacionperu.com/api/v1 |
sfp_live_… |
| Pruebas | https://pruebas.sistemafacturacionperu.com/api/v1 |
sfp_test_… |
Todo lo que emites por la API pasa por las mismas reglas que la web: series, régimen tributario, Ficha RUC, detracción, anticipos, deuda vencida del cliente, control de stock y límite del plan. Lo que la web no deja hacer, la API tampoco.
Las respuestas siempre tienen la misma forma:
{ "success": true, "data": { "…": "…" }, "meta": { "page": 1, "per_page": 20, "total": 87, "last_page": 5 } }
{ "success": false, "code": "serie_invalida", "message": "La serie ingresada es incorrecta…", "errors": { "serie_documento": ["…"] } }
Decide por el code (es estable) y muestra el message (está en español). La lista completa está en el
catálogo de errores.
Locales (sedes) y series
Una clave actúa como el usuario que elegiste al crearla: sus permisos, su local y sus series. Para emitir en otro
local, manda el código SUNAT del local en establecimiento:
{ "establecimiento": "0001", "serie_documento": "F002", "…": "…" }
GET /establecimientos te da cada local con sus series y almacenes:
{
"success": true,
"data": [{
"id": 1, "codigo": "0000", "nombre": "Tienda principal", "direccion": "Av. Lima 123", "ubigeo": "150101",
"series": [{ "tipo": "01", "serie": "F001" }, { "tipo": "03", "serie": "B001" }, { "tipo": "09", "serie": "T001" }],
"almacenes": [{ "id": 1, "nombre": "Almacén Lima", "principal": true }]
}]
}
- Una clave creada con local fijo solo emite en ese local (403
establecimiento_no_permitidoen otro). - Una serie que no es del local o del tipo de comprobante responde 422
serie_invalida. "numero_documento": "#"toma el siguiente correlativo de la serie. Es lo recomendado.
Modo pruebas y producción
| Pruebas | Producción | |
|---|---|---|
| Empresa | Una empresa de pruebas aparte, compartida | La tuya |
| SUNAT | Beta de SUNAT: nada tiene validez tributaria | Producción: comprobantes válidos |
| Lo que ves | Solo lo que emitió tu clave | Todo lo de tu empresa que tu usuario puede ver |
| Clientes y productos | Compartidos entre todas las claves de pruebas: no pongas datos reales | Los tuyos |
| Duración | Se borra a los 7 días | Se guarda los años que pide SUNAT |
| Plan | Las claves de pruebas se crean desde el Plan PRO | Desde el Plan PRO |
El modo pruebas no cambia nada de tu empresa: no consume tus correlativos ni tu stock y no cuenta para el límite de
tu plan. Una clave sfp_test_ contra la dirección de producción responde 401 clave_de_pruebas.
El camino de un comprobante
Cada comprobante tiene un estado con un código SUNAT, una clave legible y una descripción:
{ "estado": { "codigo": "05", "clave": "aceptado", "descripcion": "Aceptado" } }
clave |
codigo |
Qué significa |
|---|---|---|
registrado |
01 | Creado y firmado. Aún no está en SUNAT (o va en el resumen diario). |
enviado |
03 | Enviado; SUNAT todavía no responde (pasa con resúmenes y bajas, que van por ticket). |
aceptado |
05 | SUNAT lo aceptó. Ya tiene CDR. |
observado |
07 | Aceptado con observaciones: es válido, pero revisa la nota de SUNAT. |
rechazado |
09 | SUNAT lo rechazó: no tiene validez. Corrige y emite uno nuevo. |
por_anular |
13 | Pediste la anulación y SUNAT todavía no responde. |
anulado |
11 | Anulado con una comunicación de baja o un resumen de anulación. |
El camino normal es registrado → enviado → aceptado / observado / rechazado, y después, si lo anulas,
por_anular → anulado. La respuesta de SUNAT queda en sunat:
{ "sunat": { "codigo": "0", "descripcion": "La Factura numero F001-120, ha sido aceptada", "notas": [] } }
No hace falta preguntar el estado cada minuto: registra un webhook y te avisamos con
comprobante.aceptado, comprobante.observado, comprobante.rechazado o comprobante.anulado.
Resumen diario frente a envío directo
SUNAT recibe los comprobantes de dos formas, y el sistema elige la correcta por ti:
| Facturas y sus notas | Boletas y sus notas | |
|---|---|---|
| Cómo llegan a SUNAT | Envío directo: uno por uno, al emitir (según la configuración de la empresa) | Dentro del resumen diario |
| Estado al emitir | Normalmente aceptado u observado en la misma respuesta |
registrado |
| Quién arma el envío | La API, en la misma llamada | El sistema, solo: dos veces al día envía las boletas de los días anteriores y consulta los resúmenes enviados |
| Para anular | Comunicación de baja | Resumen de anulación |
Por eso POST /comprobantes/{id}/reenviar-sunat solo sirve para facturas y sus notas: una boleta responde 422
va_en_resumen. Lo explicamos en detalle en la guía del resumen diario.
Orígenes: facturar lo que ya existe
Para facturar una cotización, un pedido, una nota de venta o una guía, manda origen. Queda enlazado como en la web y
la mercadería no sale dos veces del stock:
{ "origen": { "tipo": "cotizacion", "id": 15 }, "serie_documento": "F001", "…": "…" }
tipo puede ser cotizacion, pedido, nota_venta o guia. Si el origen no existe o no se puede facturar responde
422 origen_no_existe, origen_no_permitido o nota_venta_no_facturable.
Productos, lotes, series y almacenes
Las líneas usan tu codigo_interno. Si la empresa tiene control de stock, un código que no existe responde 422
producto_no_existe: créalo antes con POST /productos (nace sin stock). Lotes, números de serie y almacén van en
cada línea:
{
"codigo_interno": "CEL-A15",
"almacen_id": 2,
"series": ["356938035643809", "356938035643817"],
"lotes": [{ "codigo": "L-01", "cantidad": 2 }]
}
El stock por almacén, lote y serie lo consultas con GET /stock (alcance stock.leer).
Cobros
Un comprobante al contado lleva sus pagos al emitirse. Para los cobros posteriores (una factura a crédito, por
ejemplo) usa POST /comprobantes/{id}/cobros con la cuenta de cobro y el número de operación si no es efectivo, y
GET /comprobantes/{id}/cobros para saber cuánto falta cobrar. Es el mismo registro que Por cobrar › Registrar
cobro de la web.
Sigue con
- Autenticación: claves de empresa, de socio y la conexión con un botón.
- Guías por comprobante: el JSON mínimo de cada uno.
- Límites, idempotencia, paginación y versiones.