SDK para Node.js
El SDK oficial de Factuplan (factuplan en npm, v0.11.0) te permite emitir facturas electrónicas, notas de crédito, guías de remisión y comprobantes de retención autorizados por el SRI del Ecuador desde tu backend, en JavaScript o TypeScript.
- Node.js 18+ · ESM & CJS · cero dependencias.
- Tipado completo en TypeScript.
- Dos entornos: pruebas (
ak_test_*) y producción (ak_live_*). - Compatible con Postman: descarga la colección desde tu cuenta.
Importante: los comprobantes creados con una API key de pruebas se eliminan automáticamente cada hora. Usa pruebas solo para desarrollo.
Instalación
Sección titulada «Instalación»Instala el SDK con el gestor de paquetes que prefieras:
# npmnpm install factuplan
# pnpmpnpm add factuplan
# yarnyarn add factuplanCrear una API key
Sección titulada «Crear una API key»- Inicia sesión en app.factuplan.com.ec.
- Anda a Developer → Create API key.
- Copia la clave generada — solo se muestra una vez.
- Guárdala en una variable de entorno (
FACTUPLAN_API_KEY), nunca la subas al repositorio.
Las API keys son por workspace. El prefijo indica el entorno:
| Prefijo | Entorno | Comportamiento |
|---|---|---|
ak_test_* | Pruebas | Comprobantes ficticios, eliminados cada hora |
ak_live_* | Producción | Comprobantes reales firmados y enviados al SRI |
Autenticación
Sección titulada «Autenticación»El SDK adjunta automáticamente la API key y el RUC del contribuyente en cada petición (x-taxpayer-ruc). Solo tienes que inicializar el cliente:
import { Factuplan } from "factuplan"
const factuplan = new Factuplan(process.env.FACTUPLAN_API_KEY!, { ruc: "0950194407001",})Si vas a usar únicamente operaciones que no requieren contribuyente (por ejemplo, listar tu uso del mes), puedes omitir el ruc:
const factuplan = new Factuplan(process.env.FACTUPLAN_API_KEY!)Seguridad: nunca expongas tu API key en el frontend. Úsala solo desde tu servidor.
Clientes
Sección titulada «Clientes»CRUD de los clientes (receptores) que aparecerán en tus comprobantes.
Crear cliente
Sección titulada «Crear cliente»const cliente = await factuplan.customers.create({ identificationType: "RUC", // RUC | CEDULA | PASSPORT | FINAL_CONSUMER identification: "0993378150001", // RUC=13 dígitos · CEDULA=10 dígitos legalName: "Empresa Demo S.A.", email: "facturas@empresademo.ec", // opcional address: "Guayaquil, Ecuador", // opcional phone: "+593985691039", // opcional})| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
identificationType | enum | Sí | RUC, CEDULA, PASSPORT, FINAL_CONSUMER |
identification | string | Sí | Validamos longitud y dígito verificador |
legalName | string | Sí | Razón social o nombre completo |
email | string | No | Para envío automático de comprobantes |
address | string | No | Aparece en el RIDE |
phone | string | No | Formato libre |
Listar clientes
Sección titulada «Listar clientes»Devuelve resultados paginados con un objeto meta que incluye el total.
const { data: clientes, meta } = await factuplan.customers.list({ page: 1, limit: 20, search: "empresa",})
console.log(meta.total, "clientes encontrados")Obtener, actualizar y eliminar
Sección titulada «Obtener, actualizar y eliminar»const cliente = await factuplan.customers.get("cust_id")
await factuplan.customers.update("cust_id", { email: "nuevo@empresademo.ec",})
await factuplan.customers.delete("cust_id")Productos
Sección titulada «Productos»CRUD del catálogo de ítems facturables. Funciona igual para productos tangibles y servicios.
Crear producto
Sección titulada «Crear producto»const producto = await factuplan.products.create({ code: "SERV-001", name: "Servicio de consultoría", unitPrice: 150.0, type: "SERVICE", // PRODUCT | SERVICE taxType: "IVA_RATE", // IVA_0 | IVA_RATE | NOT_TAXABLE | EXEMPT description: "Hora de consultoría técnica", // opcional})| Campo | Tipo | Notas |
|---|---|---|
code | string | Identificador único en tu workspace |
name | string | Aparece en el RIDE |
unitPrice | number | Hasta 4 decimales de precisión |
type | enum | PRODUCT, SERVICE |
taxType | enum | IVA_0, IVA_RATE, NOT_TAXABLE, EXEMPT |
description | string | Opcional |
Listar, actualizar y eliminar
Sección titulada «Listar, actualizar y eliminar»const { data: productos } = await factuplan.products.list({ search: "consultoría",})
await factuplan.products.update("prod_id", { unitPrice: 175.0 })
await factuplan.products.delete("prod_id")Facturas
Sección titulada «Facturas»Emisión completa de facturas electrónicas: el SDK genera el XML, lo firma con tu certificado, lo envía al SRI, genera el RIDE PDF y, opcionalmente, manda el correo al cliente.
Crear factura
Sección titulada «Crear factura»const factura = await factuplan.invoices.create({ // Punto de emisión: elige UNA de las 3 opciones // 1) Por UUID: emissionPointId: "ep_id", // 2) Por códigos SRI: // establishment: "001", // emissionPoint: "001", // 3) Omitir todo: auto-detecta si solo hay un punto de emisión activo
customer: { identificationType: "RUC", identification: "0993378150001", legalName: "Cliente S.A.", email: "facturas@cliente.ec", saveToContacts: true, }, items: [ { code: "SERV-001", description: "Servicio de consultoría", quantity: 1, unitPrice: 1.0, discount: 0, taxType: "IVA_RATE", tax: 15, // 0, 5, 8, 12, 14, 15 (default: 15) }, ], payments: [ { method: "20", amount: 1.15, term: 30, timeUnit: "dias", }, ], additionalInfo: { Vendedor: "Juan Pérez" }, // sendEmail: false, // opcional, default: true})
console.log(factura.id) // ID internoconsole.log(factura.accessKey) // Clave de acceso SRI (49 dígitos)console.log(factura.sequential) // Secuencial del comprobantePunto de emisión
Sección titulada «Punto de emisión»Tres formas de resolver el punto de emisión, en orden de prioridad:
// 1) Por UUID (referencia directa)factuplan.invoices.create({ emissionPointId: "ep_id", ... })
// 2) Por códigos SRI del establecimiento + punto de emisiónfactuplan.invoices.create({ establishment: "001", emissionPoint: "001", ... })
// 3) Auto-detección: si solo tienes un punto activo, omitilofactuplan.invoices.create({ customer, items, payments })Tipos de impuesto (taxType)
Sección titulada «Tipos de impuesto (taxType)»taxType | Código SRI | Descripción |
|---|---|---|
IVA_RATE | depende del tax | Aplica una tarifa de IVA específica |
IVA_0 | 0 | IVA 0% |
NOT_TAXABLE | 6 | No objeto de IVA |
EXEMPT | 7 | Exento de IVA |
Tarifas de IVA (tax)
Sección titulada «Tarifas de IVA (tax)»Usa tax cuando taxType sea IVA_RATE. Default: 15.
tax | Código SRI | Descripción |
|---|---|---|
0 | 0 | IVA 0% |
5 | 5 | IVA 5% |
8 | 8 | IVA 8% |
12 | 2 | IVA 12% |
14 | 3 | IVA 14% |
15 | 4 | IVA 15% (default) |
// Item con IVA reducido al 5%items: [ { code: "PROD-001", description: "Producto con IVA reducido", quantity: 1, unitPrice: 100.0, taxType: "IVA_RATE", tax: 5, },]Formas de pago (payments)
Sección titulada «Formas de pago (payments)»Cada elemento del arreglo payments lleva el código SRI en method, el amount y, opcionalmente, term + timeUnit. Si omites payments se aplica '01' (Sin utilización del sistema financiero) por defecto.
La suma de los
amountdebe ser exactamente igual al total de la factura con impuestos. De lo contrario el SRI rechaza con400 Bad Request.
| Código | Descripción |
|---|---|
01 | Sin utilización del sistema financiero |
15 | Compensación de deudas |
16 | Tarjeta de débito |
17 | Dinero electrónico |
18 | Tarjeta prepago |
19 | Tarjeta de crédito |
20 | Otros con utilización del sistema financiero |
21 | Endoso de títulos |
timeUnit | Descripción |
|---|---|
dias | Días |
meses | Meses |
anios | Años |
Redondeo y precisión
Sección titulada «Redondeo y precisión»unitPriceadmite hasta 4 decimales — útil para productos con costos unitarios bajos.- Los totales finales (subtotal, IVA, total) se redondean automáticamente a 2 decimales según el estándar del SRI.
Ejemplos:
Cantidad: 1000 × Precio: 0.1234 = Subtotal: 123.40Base imponible: 10.05 × 0.15 = 1.5075 → IVA: 1.51Total a pagar (Base + IVA): 11.56Consultar estado
Sección titulada «Consultar estado»const estado = await factuplan.invoices.getStatus("factura_id")// estado.status: PROCESSING | AUTHORIZED | COMPLETED | ERROR | REJECTED
if (estado.status === "COMPLETED") { console.log(estado.authorizationNumber)}Descargar XML y PDF
Sección titulada «Descargar XML y PDF»URLs pre-firmadas que expiran en 5 minutos.
const { url: xmlUrl } = await factuplan.invoices.downloadXml("id")const { url: pdfUrl } = await factuplan.invoices.downloadPdf("id")Importar por clave de acceso
Sección titulada «Importar por clave de acceso»Importa una factura ya autorizada por el SRI usando su clave de 49 dígitos. Útil para sincronizar comprobantes emitidos fuera de Factuplan.
Solo
ak_live_*. No disponible con claves de pruebas.
const factura = await factuplan.invoices.importByAccessKey({ accessKey: "0104202601099337815000110010010000000011234567890",})Consultar comprobante externo por clave de acceso
Sección titulada «Consultar comprobante externo por clave de acceso»POST /developer/receipts/query — consulta cualquier comprobante autorizado por el SRI (factura, nota de crédito, guía de remisión), sin importar qué sistema lo emitió. Si el contribuyente emisor no existe en tu workspace, se crea automáticamente con firma vacía.
Solo
ak_live_*. No disponible con claves de pruebas.
| Campo | Tipo | Requerido | Notas |
|---|---|---|---|
accessKey | string | Sí | Clave de acceso SRI (49 dígitos) |
save | boolean | No (default true) | Si false, no guarda en BD ni genera PDF |
Cómo se descuenta el contador: el crédito se descuenta cuando el comprobante está autorizado, sin importar el valor de
save. Si no está autorizado, no se descuenta ningún crédito.
// Consultar y guardar (default)const comp = await factuplan.invoices.queryExternalByAccessKey({ accessKey: "0104202601019999999900110010010000000011234567813",})console.log(comp.id, comp.status, comp.xmlBase64)
// Solo verificar sin guardarconst verificado = await factuplan.invoices.queryExternalByAccessKey({ accessKey: "0104202601019999999900110010010000000011234567813", save: false,})console.log(verificado.id) // nullconsole.log(verificado.xmlBase64) // XML autorizadoAnular factura
Sección titulada «Anular factura»Anula un comprobante a nivel del sistema: su estado pasa a VOIDED. Solo se pueden anular comprobantes en estado AUTHORIZED o COMPLETED.
Importante: la anulación es solo dentro de Factuplan. Notificar la anulación ante el SRI es responsabilidad tuya.
const result = await factuplan.invoices.void( "factura_id", "Error en los datos del cliente",)
console.log(result.status) // "VOIDED"console.log(result.voidReason) // Motivo ingresadoconsole.log(result.message) // Confirmación| Campo | Tipo | Descripción |
|---|---|---|
id | string | ID del comprobante anulado |
status | string | VOIDED |
accessKey | string | Clave de acceso del comprobante |
voidReason | string | Motivo de la anulación |
message | string | Mensaje de confirmación |
Secuencial de facturas
Sección titulada «Secuencial de facturas»Establece el próximo número secuencial para facturas en un punto de emisión específico. El nuevo valor entra en efecto en el siguiente comprobante emitido.
const result = await factuplan.invoices.updateSequential( "001", // branchCode — código del establecimiento "001", // emissionCode — código del punto de emisión 100, // nuevo secuencial)
console.log(result.invoiceSequential) // 100console.log(result.updatedAt) // ISO 8601| Campo | Tipo | Descripción |
|---|---|---|
branchCode | string | Código del establecimiento |
emissionCode | string | Código del punto de emisión |
invoiceSequential | number | Nuevo valor del secuencial aplicado |
updatedAt | string | Fecha de actualización (ISO 8601) |
Nota: este método afecta únicamente el secuencial de facturas. El soporte para otros documentos electrónicos llegará en próximas versiones.
Ejemplo completo
Sección titulada «Ejemplo completo»import { Factuplan } from "factuplan"
const factuplan = new Factuplan(process.env.FACTUPLAN_API_KEY!)
async function crearFactura() { // 1. Buscar o crear cliente const { data: clientes } = await factuplan.customers.list({ search: "0993378150001", }) const cliente = clientes[0] ?? (await factuplan.customers.create({ identificationType: "RUC", identification: "0993378150001", legalName: "Mi Cliente S.A.", email: "facturas@cliente.ec", }))
// 2. Crear factura (auto-detecta el punto de emisión) const factura = await factuplan.invoices.create({ customer: { identificationType: cliente.identificationType, identification: cliente.identification, legalName: cliente.legalName, email: cliente.email, }, items: [ { code: "PROD-001", description: "Laptop Dell XPS 15", quantity: 1, unitPrice: 1500.0, taxType: "IVA_RATE", tax: 15, }, ], payments: [ { method: "19", amount: 1725.0, // Total con IVA term: 6, timeUnit: "meses", }, ], })
// 3. Polling hasta que el SRI responda let estado do { await new Promise((r) => setTimeout(r, 3000)) estado = await factuplan.invoices.getStatus(factura.id) } while (!["COMPLETED", "ERROR", "REJECTED"].includes(estado.status))
if (estado.status === "COMPLETED") { const { url } = await factuplan.invoices.downloadPdf(factura.id) console.log("PDF:", url) }}
crearFactura()Firmar XML
Sección titulada «Firmar XML»Si tu sistema ya genera el XML (sin firmar), puedes enviarlo a Factuplan para que lo firme con tu certificado y lo autorice ante el SRI. El SDK se encarga de firmar, transmitir, generar el PDF y notificar al cliente.
Importante: el XML debe ir como string sin firmar. Factuplan se ocupa de la firma electrónica.
Firmar y autorizar
Sección titulada «Firmar y autorizar»const result = await factuplan.invoices.signAndAuthorize({ xml: xmlString, // XML sin firmar como string})
console.log(result.id) // ID del comprobanteconsole.log(result.accessKey) // Clave de acceso SRIconsole.log(result.status) // Estado inicialSolo firmar (modo C)
Sección titulada «Solo firmar (modo C)»Firma el XML con tu certificado sin enviarlo al SRI. Útil cuando quieres controlar el envío al SRI por tu cuenta.
const { signedXml } = await factuplan.invoices.signOnly({ xml: xmlString,})Validar XML
Sección titulada «Validar XML»Valida la estructura de un XML sin procesarlo ni enviarlo al SRI.
const { valid, errors } = await factuplan.invoices.validateXml({ xml: xmlString,})
if (!valid) { console.log("Errores:", errors)}Verificar autorización
Sección titulada «Verificar autorización»Consulta el estado de un comprobante en el SRI usando su clave de acceso.
const result = await factuplan.invoices.verify( "0104202601099337815000110010010000000011234567890",)
console.log(result.authorized) // true | falseconsole.log(result.authorizationNumber)Ejemplo completo: firmar XML
Sección titulada «Ejemplo completo: firmar XML»import { Factuplan } from "factuplan"import { readFileSync } from "fs"
const factuplan = new Factuplan(process.env.FACTUPLAN_API_KEY!)
async function firmarYAutorizar() { const xml = readFileSync("./factura-sin-firmar.xml", "utf-8") const result = await factuplan.invoices.signAndAuthorize({ xml })
console.log("Clave de acceso:", result.accessKey)
let estado do { await new Promise((r) => setTimeout(r, 3000)) estado = await factuplan.invoices.getStatus(result.id) } while (!["COMPLETED", "ERROR", "REJECTED"].includes(estado.status))
if (estado.status === "COMPLETED") { const { url: xmlUrl } = await factuplan.invoices.downloadXml(result.id) const { url: pdfUrl } = await factuplan.invoices.downloadPdf(result.id) console.log("XML firmado:", xmlUrl) console.log("PDF (RIDE):", pdfUrl) }}
firmarYAutorizar()Notas de crédito
Sección titulada «Notas de crédito»import { Factuplan } from 'factuplan';
const factuplan = new Factuplan('ak_live_...', { ruc: '0950194407001' });
// Emitir nota de crédito// Si la factura original no está en el sistema, se importa automáticamente desde el SRIconst creditNote = await factuplan.creditNotes.create({ invoiceAccessKey: '2312202301179214289100110010020000001231234567811', reason: 'Error en datos del cliente', items: [ { code: 'SERV-001', description: 'Servicio de desarrollo web', quantity: 1, unitPrice: 100.00, taxType: 'IVA_RATE', }, ],});
console.log(creditNote.id); // 'clx1234...'console.log(creditNote.accessKey); // '0412202404...'console.log(creditNote.status); // 'PROCESSING'
// Consultar estadoconst status = await factuplan.creditNotes.getStatus(creditNote.id);
// Descargar XML y PDFconst xml = await factuplan.creditNotes.downloadXml(creditNote.id);const pdf = await factuplan.creditNotes.downloadPdf(creditNote.id);
// Anular (solo a nivel del sistema — la anulación ante el SRI es tu responsabilidad)const voided = await factuplan.creditNotes.void(creditNote.id, 'Error en precio');| Método | Descripción |
|---|---|
creditNotes.create(input) | Emite una nota de crédito |
creditNotes.get(id) | Obtiene el detalle |
creditNotes.getStatus(id) | Consulta el estado de procesamiento |
creditNotes.downloadXml(id) | URL pre-firmada del XML (5 min) |
creditNotes.downloadPdf(id) | URL pre-firmada del PDF RIDE (5 min) |
creditNotes.void(id, reason) | Anula a nivel de sistema |
Errores
Sección titulada «Errores»El SDK exporta clases tipadas para cada categoría de error. Esto te permite ramificar el manejo según el tipo:
import { FactuplanError, AuthenticationError, RateLimitError } from "factuplan"
try { await factuplan.invoices.create({ /* ... */ })} catch (error) { if (error instanceof AuthenticationError) { console.error("API key inválida o expirada") } else if (error instanceof RateLimitError) { console.error("Rate limit alcanzado, reintentar") } else if (error instanceof FactuplanError) { console.error(error.code) // ej. "INVOICE_4002" console.error(error.message) console.error(error.statusCode) // ej. 422 console.error(error.details) }}Códigos de error frecuentes
Sección titulada «Códigos de error frecuentes»| Código | HTTP | Descripción |
|---|---|---|
AUTH_ERROR | 401 | API key inválida o expirada |
RATE_LIMIT | 429 | Límite de solicitudes excedido |
API_10001 | 403 | Plan no compatible con la operación |
API_10002 | 429 | Cuota mensual excedida |
INVOICE_4002 | 422 | Datos del cliente inválidos |
INVOICE_4003 | 422 | Detalle de factura vacío |
CERT_3008 | 422 | Certificado expirado |
CUSTOMER_7004 | 409 | Cliente duplicado |
PRODUCT_6002 | 409 | Código de producto duplicado |
Rate Limit
Sección titulada «Rate Limit»Cada API key tiene dos límites:
- Velocidad:
100peticiones por segundo por API key. - Cuota mensual: depende del plan contratado (consulta
/precios).
Si superas cualquiera de los dos, recibes HTTP 429. Implementa reintentos con espera exponencial.
Respuesta al superar el límite
Sección titulada «Respuesta al superar el límite»{ "success": false, "error": { "code": "GENERAL_0003", "message": "Has superado el límite de peticiones. Intenta de nuevo en unos segundos." }}{ "success": false, "error": { "code": "API_10002", "message": "Has alcanzado el límite mensual de 500 documentos.", "details": { "used": 500, "quota": 500 } }}Manejo recomendado con reintentos
Sección titulada «Manejo recomendado con reintentos»import { RateLimitError } from "factuplan"
async function emitirConReintento(datos: any, intentos = 3) { for (let i = 0; i < intentos; i++) { try { return await factuplan.invoices.create(datos) } catch (error) { if (error instanceof RateLimitError && i < intentos - 1) { const espera = Math.pow(2, i) * 500 // 500ms, 1s, 2s... await new Promise((r) => setTimeout(r, espera)) } else { throw error } } }}Webhooks
Sección titulada «Webhooks»Antes de procesar un evento webhook, verifica que el comprobante exista y consulta su estado actual usando su ID. Esto evita que actualices tu base de datos con eventos manipulados o duplicados.
Verificar comprobante
Sección titulada «Verificar comprobante»const receipt = await factuplan.webhooks.verifyReceipt("receipt_id")
console.log(receipt.id)console.log(receipt.status) // PROCESSING | AUTHORIZED | COMPLETED | ERROR | REJECTEDconsole.log(receipt.accessKey) // Clave de acceso SRI (49 dígitos)Tip: llama a este endpoint apenas recibes un evento webhook. Confirma que el comprobante existe antes de actualizar tu BD o disparar notificaciones a tus usuarios.
Uso & Límites
Sección titulada «Uso & Límites»Consulta el consumo del mes en curso y el límite de tu plan.
const uso = await factuplan.usage()
console.log(uso.requestsUsed, "/", uso.requestsLimit)console.log("Costo estimado: $" + uso.estimatedCost)Estructura de respuesta
Sección titulada «Estructura de respuesta»| Campo | Tipo | Descripción |
|---|---|---|
requestsUsed | number | Solicitudes utilizadas en el mes |
requestsLimit | number | Límite del plan vigente |
estimatedCost | number | Costo estimado en USD |
periodStart | string | Inicio del período (ISO 8601) |
periodEnd | string | Fin del período (ISO 8601) |
Certificado
Sección titulada «Certificado»Consulta el estado del certificado P12 cargado en tu cuenta.
const cert = await factuplan.certificateStatus()
console.log(cert.valid) // true = vigenteconsole.log(cert.expiresAt) // Fecha de vencimiento (ISO 8601)console.log(cert.taxpayerId) // RUC del titularconsole.log(cert.commonName) // Nombre registrado en el certificado| Campo | Tipo | Descripción |
|---|---|---|
valid | boolean | true si el certificado está vigente |
expiresAt | string | Fecha de vencimiento (ISO 8601) |
taxpayerId | string | RUC del titular |
commonName | string | Nombre registrado en el certificado |
Si
validesfalse, las facturas no se firmarán hasta que renueves el certificado en Configuración → Certificado desde tu cuenta.
Subir firma electrónica (P12)
Sección titulada «Subir firma electrónica (P12)»Sube un certificado P12 con su contraseña. Si el RUC del certificado no existe aún como contribuyente en tu cuenta y tu plan lo permite, el contribuyente se crea automáticamente y se vincula a tu API key.
import { readFileSync } from "fs"
const p12 = readFileSync("./mi-firma.p12")const result = await factuplan.certificates.upload(p12, "mi-contraseña")
console.log(result.ruc) // RUC extraído del certificadoconsole.log(result.legalName) // Nombre legalconsole.log(result.expiresAt) // Fecha de vencimientoconsole.log(result.created) // true si se creó un nuevo contribuyente| Campo | Tipo | Descripción |
|---|---|---|
hasCertificate | boolean | true si el certificado se subió correctamente |
isExpired | boolean | true si el certificado ya expiró |
daysUntilExpiry | number | Días restantes hasta el vencimiento |
ruc | string | RUC extraído del certificado |
legalName | string | Nombre legal extraído del certificado |
expiresAt | string | Fecha de vencimiento (ISO 8601) |
created | boolean | true si se creó automáticamente un nuevo contribuyente (solo presente cuando se creó) |
¿Necesitas ayuda integrando el API? Escríbenos a info@factuplan.com.ec o desde el Centro de ayuda.
