SDKs oficiales
SDKs para PHP, Node.js, Python, Java y C#
Los cinco se usan igual: mismos nombres de métodos (en español, como los campos del API), mismo comportamiento y mismos errores. Si cambias de lenguaje, no reaprendes nada. Los datos que mandas y recibes son exactamente los del API, sin traducir.
PHP, Node.js y Python: publicados y de código abierto (MIT)
Java y C#: se instalan desde GitHub (pronto en Maven Central / NuGet)
Ya están listos y publicados como descargable en GitHub. Java 11+ (sin dependencias): factusmart-0.1.0.jar · repo. C# (netstandard2.0 y net8.0): FactuSmart.0.1.0.nupkg · repo. Descarga el archivo y sigue los pasos de la pestaña Java o C# de abajo.
Instalar
npm install factusmartRequisitos: Node.js 18+ (sin dependencias), PHP 8.1+ con ext-curl y ext-json, Python 3.8+ (solo librería estándar), Java 11+ (sin dependencias), .NET Framework 4.6.1+ / .NET Core 2.0+ / .NET 8 (netstandard2.0 y net8.0).
Tu primera factura en 10 líneas
Reemplaza TU_API_KEY y TU_URL_BASE por los de tu panel (pestaña Configuración). No hay un dominio único: cada cuenta vive en su servidor.
import { FactuSmart } from 'factusmart'
const fs = new FactuSmart({ apiKey: 'TU_API_KEY', baseUrl: 'https://TU_URL_BASE/api/v1/issuer', ruc: '20100070970' })
const { items, totales } = FactuSmart.calcularComprobante([
{ descripcion: 'Servicio de consultoría', cantidad: 1, valor_unitario: 100, afectacion: '10' },
])
const r = await fs.comprobantes.emitir({
codigo_tipo_documento: '01',
datos_del_cliente_o_receptor: { codigo_tipo_documento_identidad: '6', numero_documento: '20131312955' },
items, totales,
})
console.log(r.aceptado_por_sunat, r.serie_numero)calcularComprobante(calcular_comprobanteen Python) armaitemsytotalescon IGV 18% y redondeo a 2 decimales. Afectación10gravado,20exonerado,30inafecto,40exportación.- Si no los mandas, el SDK pone
numero_documento: "#"(correlativo automático), la fecha y hora de Perú,codigo_tipo_operacion: "0101"ycodigo_tipo_moneda: "PEN". - El
rucdel constructor es el emisor por defecto; lo puedes cambiar en cada llamada con la opciónruc. - En C# todos los métodos son
async(terminan enAsync) y devuelven unJsonElement; en Java devuelven unMap. Los datos que mandas usan las mismas claves del API.
Métodos y endpoints
Un método por cada endpoint del API. Node.js, PHP y Java usan camelCase; Python, snake_case; C#, PascalCase con Async al final. El detalle de cada campo está en la Referencia del API.
| Método | Endpoint | Para qué |
|---|---|---|
| ping()C#: PingAsync() | GET/ping | Probar la API key y ver el saldo |
| cuenta.saldo()C#: Cuenta.SaldoAsync() | GET/account | Saldo, plan y RUCs de la cuenta |
| series.listar()C#: Series.ListarAsync() | GET/series | Series del RUC y siguiente número |
| establecimientos.listar()C#: Establecimientos.ListarAsync() | GET/establecimientos | Locales (anexos) del RUC con sus series |
| establecimientos.crear(datos)C#: Establecimientos.CrearAsync(datos) | POST/establecimientos | Crear un local (anexo) y sus series |
| catalogos.listar()C#: Catalogos.ListarAsync() | GET/catalogos | Catálogos SUNAT disponibles (gratis) |
| catalogos.consultar(nombre, opciones?)Python: catalogos.consultar(nombre, q=None)C#: Catalogos.ConsultarAsync(nombre) | GET/catalogos/{nombre} | Filas de un catálogo: unidades, afectación IGV, motivos… (gratis) |
| catalogos.buscarProductoSunat(q, opciones?)Python: catalogos.buscar_producto_sunat(q, limite=None)C#: Catalogos.BuscarProductoSunatAsync(q) | GET/catalogos/productos-sunat?q= | Buscar el código de producto SUNAT (UNSPSC) |
| comprobantes.calcular(items, opciones?)Python: comprobantes.calcular(items)C#: Comprobantes.CalcularAsync(items) | POST/calcular | FactuSmart calcula items y totales listos para emitir (gratis) |
| comprobantes.emitir(datos)C#: Comprobantes.EmitirAsync(datos) | POST/documents | Emitir factura, boleta o nota (1 crédito) |
| comprobantes.listar(filtros)C#: Comprobantes.ListarAsync(filtros) | GET/documents | Listar comprobantes con filtros |
| comprobantes.consultar(id)C#: Comprobantes.ConsultarAsync(id) | GET/documents/{external_id} | Estado guardado y links |
| comprobantes.descargar(id, tipo)C#: Comprobantes.DescargarAsync(id, tipo) | GET/documents/{external_id}/{type} | PDF, XML o CDR (devuelve bytes) |
| comprobantes.descargar(id, 'pdf', { formato: 'ticket' })Python: comprobantes.descargar(id, 'pdf', formato='ticket')C#: Comprobantes.DescargarAsync(id, "pdf", formato: "ticket") | GET/documents/{external_id}/pdf?formato= | PDF en ticket 80/58/50 mm o A5 |
| comprobantes.reenviar(id)C#: Comprobantes.ReenviarAsync(id) | POST/documents/{external_id}/reenviar | Volver a enviar a SUNAT |
| comprobantes.consultarSunat(id)Python: comprobantes.consultar_sunat(id)C#: Comprobantes.ConsultarSunatAsync(id) | POST/documents/{external_id}/consultar-sunat | Preguntar a SUNAT en vivo |
| comprobantes.historial(id)C#: Comprobantes.HistorialAsync(id) | GET/documents/{external_id}/historial | Eventos del comprobante |
| anulaciones.crear(datos)C#: Anulaciones.CrearAsync(datos) | POST/anulaciones | Comunicación de baja (1 crédito) |
| anulaciones.estado(ticket)C#: Anulaciones.EstadoAsync(ticket) | GET/anulaciones/{ticket}/status | Resultado de la baja |
| guias.emitir(datos)C#: Guias.EmitirAsync(datos) | POST/guias | Guía de remisión GRE (1 crédito) |
| guias.estado(id)C#: Guias.EstadoAsync(id) | GET/guias/{external_id}/status | Resultado de la guía |
| rucs.listar()C#: Rucs.ListarAsync() | GET/rucs | RUCs de la cuenta |
| rucs.crear(datos)C#: Rucs.CrearAsync(datos) | POST/rucs | Dar de alta un RUC (nace en prueba) |
| rucs.activar(ruc, activo)C#: Rucs.ActivarAsync(ruc, activo) | PATCH/rucs/{ruc}/activation | Activar o desactivar un RUC |
En PHP se llaman con flecha: $fs->comprobantes->emitir(...). formato solo aplica al PDF: a4, a5, ticket (80 mm), ticket58, ticket50; sin él, el A4 de siempre. Ver Descargar PDF y Varios locales (anexos).
Errores
Todos heredan de FactuSmartError, que trae status (HTTP), mensaje (el message del API) y respuesta (el cuerpo completo). En PHP viven en FactuSmart\Excepciones; en C# la base se llama FactuSmartException.
| HTTP | Excepción | Cuándo |
|---|---|---|
| 401 | ErrorDeAutenticacion | API key faltante, inválida o de otro servidor |
| 402 | SinCreditos | Te quedaste sin créditos: no se emitió nada |
| 403 | RucNoAutorizado | El RUC no es de tu cuenta o está desactivado |
| 404 | NoEncontrado | No existe ese comprobante, ticket o archivo |
| 422 | ErrorDeValidacion | Datos inválidos o falta indicar el RUC (el crédito no se cobra) |
| 429 | LimiteDePeticiones | Superaste el límite de peticiones; trae segundosDeEspera si se pudo leer |
| otros / red | FactuSmartError | Base de todas: status, mensaje y respuesta completa |
Regla de oro: aceptado_por_sunat = false NO es una excepción
SUNAT puede rechazar un comprobante o no responder a tiempo. Eso es un resultado normal: el API responde 200 y el SDK te devuelve la respuesta tal cual, sin lanzar error. Revisa siempre aceptado_por_sunat, guarda el external_id y sigue Si no sale "Aceptado".
import { FactuSmart, SinCreditos, ErrorDeValidacion, FactuSmartError } from 'factusmart'
try {
const r = await fs.comprobantes.emitir(datos, { idempotencyKey: 'venta-000123' })
if (!r.aceptado_por_sunat) {
// No es excepción: el comprobante existe. Revisa r.estado, r.sunat y r.accion_requerida.
}
} catch (e) {
if (e instanceof SinCreditos) { /* sube de plan o pide créditos extra */ }
else if (e instanceof ErrorDeValidacion) { console.error(e.mensaje) }
else if (e instanceof FactuSmartError) { console.error(e.status, e.mensaje, e.respuesta) }
else throw e
}Decide por codigo y reintentable
Toda excepción trae también codigo (el código estable del API, ej. SIN_CREDITOS, DATOS_INVALIDOS) y reintentable (true si repetir la misma petición más tarde puede funcionar). Úsalos en vez de comparar mensaje, que puede cambiar de redacción. Si te llega un codigo que no conoces, guíate por reintentable. Catálogo completo en Códigos de error.
import { FactuSmartError } from 'factusmart'
try {
await fs.comprobantes.emitir(datos, { idempotencyKey: 'venta-000123' })
} catch (e) {
if (!(e instanceof FactuSmartError)) throw e
if (e.codigo === 'SIN_CREDITOS') { /* sube de plan o pide créditos extra */ }
else if (e.reintentable) { /* encola y vuelve a intentar más tarde */ }
else { console.error(e.codigo, e.mensaje) /* corrige los datos */ }
}Verificar webhooks
Si configuraste un webhook, cada aviso llega firmado en el header X-FactuSmart-Firma (HMAC-SHA256 del cuerpo con tu secreto whsec_…). Los SDK lo verifican con una sola función que devuelve true/false:
- Node.js:
FactuSmart.verificarFirmaWebhook(cuerpoCrudo, firma, secreto) - PHP:
\FactuSmart\Cliente::verificarFirmaWebhook($cuerpoCrudo, $firma, $secreto) - Python:
factusmart.verificar_firma_webhook(cuerpo_crudo, firma, secreto) - Java:
FactuSmart.verificarFirmaWebhook(cuerpoCrudo, firma, secreto) - C#:
FactuSmartClient.VerificarFirmaWebhook(cuerpoCrudo, firma, secreto)
import express from 'express'
import { FactuSmart } from 'factusmart'
app.post('/webhooks/factusmart', express.raw({ type: 'application/json' }), (req, res) => {
const firma = req.get('X-FactuSmart-Firma') || ''
if (!FactuSmart.verificarFirmaWebhook(req.body, firma, process.env.FACTUSMART_WEBHOOK_SECRET)) {
return res.sendStatus(401)
}
const aviso = JSON.parse(req.body.toString('utf8'))
res.sendStatus(200)
})Pásale el cuerpo crudo, no el JSON ya convertido
Si vuelves a serializar el JSON, la firma no coincide. Si no es válida, responde 401. Y deduplica por el id del aviso: es el mismo en todos los reintentos.
Reintentos e idempotencia
comprobantes.emitirsiempre mandaIdempotency-Key: la tuya si la pasas (idempotencyKey/idempotency_key, recomendado: el id de tu venta) o una generada. La misma clave se reusa en todos los reintentos, así un reintento nunca emite dos veces ni te cobra dos veces.- Se reintenta solo ante error de red o timeout, límite de peticiones y respuestas
502,503,504, con espera creciente (1 s, 2 s, 4 s…). Por defecto hasta 3 veces (maxReintentos/max_reintentos). - Un
409al emitir («ya hay una emisión en curso con esa Idempotency-Key») también se reintenta con la misma clave. - Nunca se reintentan solos:
anulaciones.crear,guias.emitir,comprobantes.reenviar,comprobantes.consultarSunatyrucs.activar. Las consultas (GET) sí. - Timeout por defecto: 60 segundos (SUNAT a veces tarda).
Más sobre el comportamiento del API en Evitar duplicados y Códigos de error.
¿Listo para integrar?
Crea tu cuenta gratis y recibe tu API key al instante, con 50 créditos de regalo para empezar a emitir en producción. El modo prueba es gratis y no los consume.
Crear cuenta gratis