Factuplan SDK
Librería oficial de JavaScript y TypeScript para crear facturas electrónicas autorizadas por el SRI de Ecuador. Zero dependencies.
Instalación
npm install factuplanpnpm add factuplanyarn add factuplanAutenticación
Crea una API key desde Developer → Crear API key en tu cuenta de Factuplan. Los API keys son a nivel de workspace — debes enviar el header x-taxpayer-ruc en cada petición para identificar el contribuyente a operar.
import { Factuplan } from 'factuplan';
// Inicializar con RUC del contribuyente (requerido para la mayoría de operaciones)
const factuplan = new Factuplan('ak_test_tu_api_key_aqui', {
ruc: '0950194407001', // RUC del contribuyente a operar
});
// Sin RUC (solo para queryExternalByAccessKey u operaciones que no requieren contribuyente)
const factuplan = new Factuplan('ak_test_tu_api_key_aqui');Nunca expongas tu API key en el frontend. Úsala solo desde tu servidor backend.
Entorno de pruebas (ak_test_...)
Los documentos emitidos con una API key de prueba se eliminan automáticamente cada hora. Esto incluye facturas, notas de crédito, guías de remisión y cualquier otro comprobante generado en modo TESTING. No uses el entorno de pruebas para documentos que necesites conservar.
Clientes
Crear
const cliente = await factuplan.customers.create({
identificationType: 'RUC', // RUC | CEDULA | PASSPORT | FINAL_CONSUMER
identification: '0000000000001', // RUC=13 dígitos | CEDULA=10 dígitos
legalName: 'Empresa Demo S.A.',
email: 'contacto@empresa.com', // opcional
address: 'Guayaquil, Ecuador', // opcional
phone: '+593000000000', // opcional — ej. +593 EC, +1 US, +44 UK
});Listar
const { data: clientes, meta } = await factuplan.customers.list({
page: 1,
limit: 20,
search: 'empresa',
});
console.log(meta.total, 'clientes encontrados');Obtener / Actualizar / Eliminar
const cliente = await factuplan.customers.get('id');
await factuplan.customers.update('id', { email: 'nuevo@email.com' });
await factuplan.customers.delete('id');Productos
Crear
const producto = await factuplan.products.create({
code: 'SERV-001',
name: 'Servicio de consultoría',
unitPrice: 150.00,
type: 'SERVICE', // PRODUCT | SERVICE
taxType: 'IVA_RATE', // IVA_0 | IVA_RATE | NOT_TAXABLE | EXEMPT
description: 'Hora de consultoría técnica',
});Listar / Actualizar / Eliminar
const { data: productos } = await factuplan.products.list({ search: 'consultoría' });
await factuplan.products.update('id', { unitPrice: 175.00 });
await factuplan.products.delete('id');Facturas
Crear factura
Crea una factura electrónica completa. El sistema genera el XML, lo firma, lo envía al SRI, genera el PDF y envía el email al cliente. Usa sendEmail: false para omitir el correo.
Parámetros destacados
| Campo | Tipo | Default | Descripción |
|---|---|---|---|
| sendEmail | boolean | true | Si false, el PDF se genera pero no se envía correo al cliente. |
const factura = await factuplan.invoices.create({
// Punto de emisión: elige UNA de estas 3 opciones
// Opción 1: por UUID
emissionPointId: 'ep_id',
// Opción 2: por códigos SRI
// establishment: '001',
// emissionPoint: '001',
// Opción 3: omitir todo (auto-detecta si solo hay un punto de emisión activo)
customer: {
identificationType: 'RUC',
identification: '0000000000001', // RUC=13 dígitos | CEDULA=10 dígitos
legalName: 'Cliente S.A.',
email: 'cliente@email.com',
saveToContacts: true,
},
items: [
{
code: 'SERV-001',
description: 'Servicio de consultoría',
quantity: 1,
unitPrice: 1.00,
discount: 0,
taxType: 'IVA_RATE', // IVA_0 | IVA_RATE | NOT_TAXABLE | EXEMPT
tax: 15, // Tarifa IVA: 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: omite el envío de correo al cliente (default: true)
});
console.log(factura.id); // ID del comprobante
console.log(factura.accessKey); // Clave de acceso (49 dígitos)
console.log(factura.sequential); // Número secuencialPunto de emisión
El punto de emisión se puede resolver de tres formas. Si no se envía ningún parámetro, el sistema auto-detecta el único punto de emisión activo del contribuyente.
// Usar códigos SRI del establecimiento y punto de emisión
const factura = await factuplan.invoices.create({
establishment: '001',
emissionPoint: '001',
customer: { ... },
items: [ ... ],
});// Si solo tienes un punto de emisión activo, no necesitas enviarlo
const factura = await factuplan.invoices.create({
customer: { ... },
items: [ ... ],
});Tipos de Impuesto (taxType)
Define el tipo de impuesto aplicable a cada ítem. Los valores posibles son:
| taxType | Descripción |
|---|---|
| IVA_RATE | Aplica una tarifa de IVA específica (usar campo tax). |
| IVA_0 | Aplica tarifa 0% (Código SRI 0). |
| NOT_TAXABLE | No aplica IVA (Código SRI 6). |
| EXEMPT | Exento de IVA (Código SRI 7). |
Tarifas de IVA
El campo tax permite especificar la tarifa de IVA cuando usas taxType: 'IVA_RATE'. Si no se envía, se aplica 15% por defecto.
| 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) |
items: [
{
code: 'PROD-001',
description: 'Producto con IVA reducido',
quantity: 1,
unitPrice: 100.00,
taxType: 'IVA_RATE',
tax: 5, // IVA 5% en lugar del 15% por defecto
},
]Formas de pago
Usa el arreglo payments para especificar una o varias formas de pago. Cada objeto debe incluir el method (código SRI), amount (monto total), y opcionalmente term y timeUnit. Si no se envía el arreglo, se aplica '01' (Sin utilización del sistema financiero) por defecto.
Importante
La suma de los campos amount en todas las formas de pago enviadas debe ser exactamente igual al total de la factura (incluyendo impuestos). Si los montos no coinciden, la petición será rechazada con un error 400 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 |
| Unidad de tiempo (timeUnit) | Descripción |
|---|---|
| dias | Días |
| meses | Meses |
| anios | Años |
Redondeo y Precisión
Factuplan utiliza reglas de redondeo automáticas para cumplir con los estándares del SRI manteniendo la mayor precisión en los cálculos.
Precios Unitarios
Puedes enviar el campo unitPrice con hasta 4 decimales. Esto es ideal para productos con costos unitarios bajos donde la precisión es crítica.
Totales e Impuestos
Todos los totales finales (subtotal, IVA, total factura) se redondean automáticamente a 2 decimales usando el estándar del SRI.
Escenarios de Redondeo
1. Multiplicación de precisión:
Cantidad: 1000 × Precio: 0.1234 = Subtotal: 123.40
2. Redondeo de IVA (15%):
Base Imponible: 10.05 × 0.15 = 1.5075 → IVA: 1.51
Total a pagar (Base + IVA): 11.56
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
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
Importa una factura ya autorizada por el SRI usando su clave de acceso de 49 dígitos. Útil para sincronizar comprobantes emitidos fuera de Factuplan.
ak_live_...). Esta función consulta el SRI en tiempo real y no está disponible con claves de prueba (ak_test_...).const factura = await factuplan.invoices.importByAccessKey({
accessKey: '0104202601099337815000110010010000000011234567890',
});
console.log(factura.id);
console.log(factura.sequential);/developer/receipts/queryConsultar comprobante externo por clave de acceso
Consulta un comprobante ya autorizado por el SRI emitido desde cualquier sistema de facturación. El contribuyente emisor se crea automáticamente en tu workspace (con firma vacía) si no existe. Soporta Facturas, Notas de Crédito y Guías de Remisión.
ak_live_...). Esta función consulta el SRI en tiempo real y no está disponible con claves de prueba (ak_test_...).Parámetros
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| accessKey | string | Sí | Clave de acceso de 49 dígitos del SRI |
| save | boolean | No (default: true) | Si false, no guarda registros en la base de datos ni genera PDF. El contador sí se descuenta si el comprobante está autorizado. |
save. Si el comprobante no está autorizado, no se descuenta ningún crédito.// Consultar y guardar (comportamiento por defecto)
const comprobante = await factuplan.invoices.queryExternalByAccessKey({
accessKey: '0104202601019999999900110010010000000011234567813',
});
console.log(comprobante.id); // ID del documento guardado
console.log(comprobante.status); // 'AUTHORIZED'
console.log(comprobante.xmlBase64); // XML en base64
// Solo verificar sin guardar (save=false)
const verificado = await factuplan.invoices.queryExternalByAccessKey({
accessKey: '0104202601019999999900110010010000000011234567813',
save: false,
});
console.log(verificado.id); // null (no guardado)
console.log(verificado.xmlBase64); // XML autorizado en base64Ejemplo 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',
});
let cliente = clientes[0] ?? await factuplan.customers.create({
identificationType: 'RUC',
identification: '0993378150001',
legalName: 'Mi Cliente S.A.',
email: 'cliente@email.com',
});
// 2. Crear factura (sin emissionPointId → auto-detecta)
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.00,
taxType: 'IVA_RATE',
tax: 15,
},
],
payments: [
{
method: '19',
amount: 1725.00, // Total con IVA
term: 6,
timeUnit: 'meses',
},
],
});
// 3. Esperar autorización
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 y autorizar XML
Si ya generas tu propio XML (sin firmar), puedes enviarlo a Factuplan para que lo firme con tu certificado electrónico y lo autorice ante el SRI. El sistema se encarga de firmar, enviar al SRI, generar el PDF y enviar el email.
Importante: El XML debe enviarse como string sin firmar. Factuplan se encarga de la firma electrónica.
Enviar XML
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 inicialEjemplo completo
import { Factuplan } from 'factuplan';
import { readFileSync } from 'fs';
const factuplan = new Factuplan(process.env.FACTUPLAN_API_KEY!);
async function firmarYAutorizar() {
// 1. Leer XML sin firmar
const xml = readFileSync('./factura-sin-firmar.xml', 'utf-8');
// 2. Enviar a firmar y autorizar
const result = await factuplan.invoices.signAndAuthorize({ xml });
console.log('Clave de acceso:', result.accessKey);
// 3. Esperar autorización
let estado;
do {
await new Promise((r) => setTimeout(r, 3000));
estado = await factuplan.invoices.getStatus(result.id);
} while (!['COMPLETED', 'ERROR', 'REJECTED'].includes(estado.status));
// 4. Descargar documentos
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();Solo firmar (Modo C)
Firma el XML con tu certificado sin enviarlo al SRI. Útil si deseas controlar el envío al SRI por tu cuenta.
const { signedXml } = await factuplan.invoices.signOnly({
xml: xmlString, // XML sin firmar como string
});
console.log(signedXml); // XML firmadoValidar 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
Consulta el estado de autorización de un comprobante ante el SRI usando su clave de acceso.
const result = await factuplan.invoices.verify(
'0104202601099337815000110010010000000011234567890'
);
console.log(result.authorized); // true/false
console.log(result.authorizationNumber); // Número de autorizaciónManejo de errores
import { FactuplanError, AuthenticationError } from 'factuplan';
try {
await factuplan.invoices.create({ ... });
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('API key inválida');
} else if (error instanceof FactuplanError) {
console.error(error.code); // "INVOICE_4002"
console.error(error.message); // Descripción
console.error(error.statusCode); // 422
console.error(error.details); // Detalles adicionales
}
}Códigos de error
| 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
Cada API key tiene un límite de velocidad y una cuota mensual para garantizar un servicio estable para todos los usuarios.
100
peticiones por segundo por API key
Según plan
documentos emitidos por mes
Al superar cualquiera de los dos límites recibirás un HTTP 429. Implementa reintentos con espera exponencial para manejarlos correctamente.
Respuesta al superar el límite
// HTTP 429 — Rate limit de velocidad (100 req/s)
{
"success": false,
"error": {
"code": "GENERAL_0003",
"message": "Has superado el límite de peticiones. Intenta de nuevo en unos segundos."
}
}
// HTTP 429 — Cuota mensual agotada
{
"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
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...
console.log(`Rate limit — reintentando en ${espera}ms`);
await new Promise(r => setTimeout(r, espera));
} else {
throw error;
}
}
}
}Webhooks
Antes de procesar un evento webhook, verifica que el comprobante exista y consulta su estado actual usando su ID.
Verificar comprobante
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 al recibir un evento webhook para confirmar que el comprobante existe antes de actualizar tu base de datos.
Uso & Límites
Consulta el consumo de API del mes en curso y el límite de tu plan.
Consultar uso
const uso = await factuplan.usage();
console.log(uso.requestsUsed, '/', uso.requestsLimit);
console.log('Costo estimado: $' + uso.estimatedCost);Respuesta
| Campo | Tipo | Descripción |
|---|---|---|
| requestsUsed | number | Solicitudes usadas este mes |
| requestsLimit | number | Límite del plan actual |
| estimatedCost | number | Costo estimado en USD |
| periodStart | string | Inicio del período (ISO 8601) |
| periodEnd | string | Fin del período (ISO 8601) |
Certificado
Consulta el estado del certificado electrónico P12 configurado en tu cuenta.
Estado del certificado
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 en el certificadoRespuesta
| Campo | Tipo | Descripción |
|---|---|---|
| valid | boolean | true si el certificado está vigente |
| expiresAt | string | Fecha de vencimiento (ISO 8601) |
| taxpayerId | string | RUC del titular del certificado |
| commonName | string | Nombre registrado en el certificado |
Si valid es false, las facturas no podrán firmarse hasta que renueves el certificado en Configuración → Certificado.