Ir al contenido
Factuplan

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.

Instala el SDK con el gestor de paquetes que prefieras:

Ventana de terminal
# npm
npm install factuplan
# pnpm
pnpm add factuplan
# yarn
yarn add factuplan
  1. Inicia sesión en app.factuplan.com.ec.
  2. Anda a Developer → Create API key.
  3. Copia la clave generada — solo se muestra una vez.
  4. 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:

PrefijoEntornoComportamiento
ak_test_*PruebasComprobantes ficticios, eliminados cada hora
ak_live_*ProducciónComprobantes reales firmados y enviados al SRI

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.

CRUD de los clientes (receptores) que aparecerán en tus comprobantes.

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
})
CampoTipoRequeridoNotas
identificationTypeenumRUC, CEDULA, PASSPORT, FINAL_CONSUMER
identificationstringValidamos longitud y dígito verificador
legalNamestringRazón social o nombre completo
emailstringNoPara envío automático de comprobantes
addressstringNoAparece en el RIDE
phonestringNoFormato libre

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")
const cliente = await factuplan.customers.get("cust_id")
await factuplan.customers.update("cust_id", {
email: "nuevo@empresademo.ec",
})
await factuplan.customers.delete("cust_id")

CRUD del catálogo de ítems facturables. Funciona igual para productos tangibles y servicios.

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
})
CampoTipoNotas
codestringIdentificador único en tu workspace
namestringAparece en el RIDE
unitPricenumberHasta 4 decimales de precisión
typeenumPRODUCT, SERVICE
taxTypeenumIVA_0, IVA_RATE, NOT_TAXABLE, EXEMPT
descriptionstringOpcional
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")

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.

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 interno
console.log(factura.accessKey) // Clave de acceso SRI (49 dígitos)
console.log(factura.sequential) // Secuencial del comprobante

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ón
factuplan.invoices.create({ establishment: "001", emissionPoint: "001", ... })
// 3) Auto-detección: si solo tienes un punto activo, omitilo
factuplan.invoices.create({ customer, items, payments })
taxTypeCódigo SRIDescripción
IVA_RATEdepende del taxAplica una tarifa de IVA específica
IVA_00IVA 0%
NOT_TAXABLE6No objeto de IVA
EXEMPT7Exento de IVA

Usa tax cuando taxType sea IVA_RATE. Default: 15.

taxCódigo SRIDescripción
00IVA 0%
55IVA 5%
88IVA 8%
122IVA 12%
143IVA 14%
154IVA 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,
},
]

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 amount debe ser exactamente igual al total de la factura con impuestos. De lo contrario el SRI rechaza con 400 Bad Request.

CódigoDescripción
01Sin utilización del sistema financiero
15Compensación de deudas
16Tarjeta de débito
17Dinero electrónico
18Tarjeta prepago
19Tarjeta de crédito
20Otros con utilización del sistema financiero
21Endoso de títulos
timeUnitDescripción
diasDías
mesesMeses
aniosAños
  • unitPrice admite 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.40
Base imponible: 10.05 × 0.15 = 1.5075 → IVA: 1.51
Total a pagar (Base + IVA): 11.56
const estado = await factuplan.invoices.getStatus("factura_id")
// estado.status: PROCESSING | AUTHORIZED | COMPLETED | ERROR | REJECTED
if (estado.status === "COMPLETED") {
console.log(estado.authorizationNumber)
}

URLs pre-firmadas que expiran en 5 minutos.

const { url: xmlUrl } = await factuplan.invoices.downloadXml("id")
const { url: pdfUrl } = await factuplan.invoices.downloadPdf("id")

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.

CampoTipoRequeridoNotas
accessKeystringClave de acceso SRI (49 dígitos)
savebooleanNo (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 guardar
const verificado = await factuplan.invoices.queryExternalByAccessKey({
accessKey: "0104202601019999999900110010010000000011234567813",
save: false,
})
console.log(verificado.id) // null
console.log(verificado.xmlBase64) // XML autorizado

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 ingresado
console.log(result.message) // Confirmación
CampoTipoDescripción
idstringID del comprobante anulado
statusstringVOIDED
accessKeystringClave de acceso del comprobante
voidReasonstringMotivo de la anulación
messagestringMensaje de confirmación

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) // 100
console.log(result.updatedAt) // ISO 8601
CampoTipoDescripción
branchCodestringCódigo del establecimiento
emissionCodestringCódigo del punto de emisión
invoiceSequentialnumberNuevo valor del secuencial aplicado
updatedAtstringFecha 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-factura.ts
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()

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.

const result = await factuplan.invoices.signAndAuthorize({
xml: xmlString, // XML sin firmar como string
})
console.log(result.id) // ID del comprobante
console.log(result.accessKey) // Clave de acceso SRI
console.log(result.status) // Estado inicial

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,
})

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)
}

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 | false
console.log(result.authorizationNumber)
firmar-xml.ts
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()
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 SRI
const 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 estado
const status = await factuplan.creditNotes.getStatus(creditNote.id);
// Descargar XML y PDF
const 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étodoDescripció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

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ódigoHTTPDescripción
AUTH_ERROR401API key inválida o expirada
RATE_LIMIT429Límite de solicitudes excedido
API_10001403Plan no compatible con la operación
API_10002429Cuota mensual excedida
INVOICE_4002422Datos del cliente inválidos
INVOICE_4003422Detalle de factura vacío
CERT_3008422Certificado expirado
CUSTOMER_7004409Cliente duplicado
PRODUCT_6002409Código de producto duplicado

Cada API key tiene dos límites:

  • Velocidad: 100 peticiones 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.

{
"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 }
}
}
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
}
}
}
}

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.

const receipt = await factuplan.webhooks.verifyReceipt("receipt_id")
console.log(receipt.id)
console.log(receipt.status) // PROCESSING | AUTHORIZED | COMPLETED | ERROR | REJECTED
console.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.

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)
CampoTipoDescripción
requestsUsednumberSolicitudes utilizadas en el mes
requestsLimitnumberLímite del plan vigente
estimatedCostnumberCosto estimado en USD
periodStartstringInicio del período (ISO 8601)
periodEndstringFin del período (ISO 8601)

Consulta el estado del certificado P12 cargado en tu cuenta.

const cert = await factuplan.certificateStatus()
console.log(cert.valid) // true = vigente
console.log(cert.expiresAt) // Fecha de vencimiento (ISO 8601)
console.log(cert.taxpayerId) // RUC del titular
console.log(cert.commonName) // Nombre registrado en el certificado
CampoTipoDescripción
validbooleantrue si el certificado está vigente
expiresAtstringFecha de vencimiento (ISO 8601)
taxpayerIdstringRUC del titular
commonNamestringNombre registrado en el certificado

Si valid es false, las facturas no se firmarán hasta que renueves el certificado en Configuración → Certificado desde tu cuenta.

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 certificado
console.log(result.legalName) // Nombre legal
console.log(result.expiresAt) // Fecha de vencimiento
console.log(result.created) // true si se creó un nuevo contribuyente
CampoTipoDescripción
hasCertificatebooleantrue si el certificado se subió correctamente
isExpiredbooleantrue si el certificado ya expiró
daysUntilExpirynumberDías restantes hasta el vencimiento
rucstringRUC extraído del certificado
legalNamestringNombre legal extraído del certificado
expiresAtstringFecha de vencimiento (ISO 8601)
createdbooleantrue 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.