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 | EXTERIOR
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',
auxiliaryCode: 'SERV-001', // opcional — por defecto toma el mismo valor que code
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
| Método SDK | Descripción |
|---|---|
| invoices.create(input) | Crear factura electrónica completa |
| invoices.list(params) | Buscar y listar facturas con filtros |
| invoices.get(id) | Obtener detalle del comprobante |
| invoices.getStatus(id) | Consultar estado de procesamiento |
| invoices.downloadXml(id) | Descargar XML autorizado (URL pre-firmada) |
| invoices.downloadPdf(id) | Descargar PDF RIDE (URL pre-firmada) |
| invoices.importByAccessKey(input) | Importar factura existente desde el SRI |
| invoices.queryExternalByAccessKey(input) | Consultar comprobante externo por clave |
| invoices.void(id, reason) | Anular comprobante |
| invoices.updateSequential(branch, point, seq) | Cambiar secuencial del punto de emisión |
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',
auxiliaryCode: 'SERV-001', // opcional — por defecto toma el mismo valor que code
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
Buscar facturas
Lista las facturas emitidas con paginación. Filtra por RUC del cliente, estado y rango de fechas.
const result = await factuplan.invoices.list({
status: 'AUTHORIZED',
dateFrom: '2025-01-01',
dateTo: '2025-12-31',
page: 1,
limit: 20,
});
result.data.forEach((inv) => {
console.log(inv.sequential, inv.total, inv.customer.legalName);
});
// result.meta: { total, page, limit, totalPages }| Parámetro | Tipo | Descripción |
|---|---|---|
| ruc | string | Filtrar por RUC o cédula del cliente |
| status | string | Estado: DRAFT | PENDING | PROCESSING | AUTHORIZED | REJECTED | VOIDED |
| dateFrom | string | Fecha de inicio en formato ISO 8601 (ej. "2025-01-01") |
| dateTo | string | Fecha de fin en formato ISO 8601 (ej. "2025-12-31") |
| page | number | Número de página (por defecto: 1) |
| limit | number | Resultados por página (por defecto: 20, máximo: 100) |
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 de factura (sin firmar), puedes enviarlo a Factuplan para que lo firme con tu certificado electrónico y lo autorice ante el SRI.
Importante: El XML debe enviarse sin firmar. Factuplan se encarga 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
// 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));Notas de crédito
Corrige total o parcialmente una factura ya autorizada. Se identifica la factura original por su clave de acceso de 49 dígitos. Si no existe en el sistema, se importa automáticamente desde el SRI.
| Método SDK | Descripción |
|---|---|
| creditNotes.create(input) | Emitir nota de crédito |
| creditNotes.list(params) | Buscar y listar notas de crédito con filtros |
| creditNotes.get(id) | Obtener detalle del comprobante |
| creditNotes.getStatus(id) | Consultar estado de procesamiento |
| creditNotes.downloadXml(id) | Descargar XML autorizado (URL pre-firmada) |
| creditNotes.downloadPdf(id) | Descargar PDF RIDE (URL pre-firmada) |
| creditNotes.void(id, reason) | Anular nota de crédito |
Emitir nota de crédito
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'Cuerpo de la petición
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
| invoiceAccessKey | string | Sí | Clave de acceso de 49 dígitos de la factura original |
| reason | string | Sí | Motivo de la nota de crédito |
| items | array | Sí | Ítems a acreditar (misma estructura que en facturas) |
| emissionPointId | string | No | UUID del punto de emisión (hereda el de la factura si se omite) |
| payments | array | No | Métodos de pago |
| additionalInfo | object | No | Campos adicionales clave-valor |
Respuesta 201
| Campo | Tipo | Descripción |
|---|---|---|
| id | string | ID de la nota de crédito |
| accessKey | string | Clave de acceso de 49 dígitos |
| sequential | string | Secuencial (ej: "000000001") |
| status | string | "PROCESSING" — se autoriza de forma asíncrona |
| total | number | Importe total de la nota de crédito |
Consultar detalle
const creditNote = await factuplan.creditNotes.get('CREDIT_NOTE_ID');
console.log(creditNote.id);
console.log(creditNote.accessKey);
console.log(creditNote.status);
console.log(creditNote.total);Consultar estado
const estado = await factuplan.creditNotes.getStatus('CREDIT_NOTE_ID');
// estado.status: PROCESSING | AUTHORIZED | COMPLETED | ERROR | REJECTED
if (estado.status === 'COMPLETED') {
console.log(estado.authorizationNumber);
}Descargar XML y PDF
const { url: xmlUrl } = await factuplan.creditNotes.downloadXml('CREDIT_NOTE_ID');
const { url: pdfUrl } = await factuplan.creditNotes.downloadPdf('CREDIT_NOTE_ID');Buscar notas de crédito
Lista las notas de crédito emitidas con paginación. Filtra por RUC del cliente, estado y rango de fechas.
const result = await factuplan.creditNotes.list({
status: 'AUTHORIZED',
dateFrom: '2025-01-01',
dateTo: '2025-12-31',
page: 1,
limit: 20,
});
result.data.forEach((nc) => {
console.log(nc.sequential, nc.total, nc.customer.legalName);
});
// result.meta: { total, page, limit, totalPages }| Parámetro | Tipo | Descripción |
|---|---|---|
| ruc | string | Filtrar por RUC o cédula del cliente |
| status | string | Estado: DRAFT | PENDING | PROCESSING | AUTHORIZED | REJECTED | VOIDED |
| dateFrom | string | Fecha de inicio en formato ISO 8601 (ej. "2025-01-01") |
| dateTo | string | Fecha de fin en formato ISO 8601 (ej. "2025-12-31") |
| page | number | Número de página (por defecto: 1) |
| limit | number | Resultados por página (por defecto: 20, máximo: 100) |
Firmar y autorizar XML
Si ya tienes el XML de la nota de crédito (sin firmar), Factuplan lo firma y autoriza ante el SRI. La factura vinculada se resuelve automáticamente desde el XML.
Importante: El XML debe enviarse sin firmar.
const result = await factuplan.creditNotes.signAndAuthorize({
xml: xmlString,
});
console.log(result.id);
console.log(result.accessKey);
console.log(result.status);Anular nota de crédito
const result = await factuplan.creditNotes.void(
'CREDIT_NOTE_ID',
'Error en los datos del cliente',
);
console.log(result.status); // "VOIDED"
console.log(result.voidReason); // Motivo ingresado
console.log(result.message); // ConfirmaciónLa anulación de la nota de crédito ante el SRI es responsabilidad del desarrollador. El endpoint /void solo cambia el estado en Factuplan.
Secuencial
Establece el próximo número secuencial para notas de crédito en el punto de emisión indicado. El nuevo valor entra en efecto en el siguiente comprobante emitido.
const result = await factuplan.creditNotes.updateSequential(
'001', // branchCode — código del establecimiento
'001', // emissionCode — código del punto de emisión
100, // creditNoteSequential — nuevo valor del secuencial
);
console.log(result.creditNoteSequential); // 100
console.log(result.updatedAt); // '2026-06-08T12:00:00.000Z'| Campo | Tipo | Descripción |
|---|---|---|
| branchCode | string | Código del establecimiento |
| emissionCode | string | Código del punto de emisión |
| creditNoteSequential | number | Nuevo valor del secuencial aplicado |
| updatedAt | string | Fecha de actualización (ISO 8601) |
Notas de débito
Registra cargos adicionales sobre una factura ya autorizada: ajustes de precio, mora, flete no facturado, etc. Se identifica la factura original por su clave de acceso de 49 dígitos.
| Método SDK | Descripción |
|---|---|
| debitNotes.create(input) | Emitir nota de débito |
| debitNotes.list(params) | Buscar y listar notas de débito con filtros |
| debitNotes.get(id) | Obtener detalle del comprobante |
| debitNotes.getStatus(id) | Consultar estado de procesamiento |
| debitNotes.downloadXml(id) | Descargar XML autorizado (URL pre-firmada) |
| debitNotes.downloadPdf(id) | Descargar PDF RIDE (URL pre-firmada) |
| debitNotes.void(id, reason) | Anular nota de débito |
Emitir nota de débito
const debitNote = await factuplan.debitNotes.create({
invoiceAccessKey: '2312202301179214289100110010020000001231234567811',
reasons: [
{
description: 'Ajuste por flete no facturado',
amount: 25.00,
taxType: 'IVA', // IVA | IVA_0 | NO_TAX | ICE | IRBPNR | IVA_EXEMPT
},
],
// additionalInfo: { Referencia: 'OC-4521' }, // opcional
});
console.log(debitNote.id); // 'clx1234...'
console.log(debitNote.accessKey); // '0512202404...'
console.log(debitNote.status); // 'PROCESSING'Cuerpo de la petición
| Campo | Tipo | Req. | Descripción |
|---|---|---|---|
| invoiceAccessKey | string | Sí | Clave de acceso de 49 dígitos de la factura original |
| reasons | array | Sí | Motivos / cargos (al menos uno requerido) |
| reasons[].description | string | Sí | Descripción del cargo o ajuste |
| reasons[].amount | number | Sí | Importe del cargo (mínimo 0.01) |
| reasons[].taxType | string | Sí | Tipo de impuesto: IVA | IVA_0 | NO_TAX | ICE | IRBPNR | IVA_EXEMPT |
| emissionPointId | string | No | UUID del punto de emisión (auto-detecta si se omite) |
| additionalInfo | object | No | Campos adicionales clave-valor |
Buscar notas de débito
const { data: debitNotes, meta } = await factuplan.debitNotes.list({
status: 'COMPLETED',
page: 1,
limit: 20,
});Consultar estado
const estado = await factuplan.debitNotes.getStatus('DEBIT_NOTE_ID');
// estado.status: PROCESSING | AUTHORIZED | COMPLETED | ERROR | REJECTED
if (estado.status === 'COMPLETED') {
console.log(estado.authorizationNumber);
}Descargar XML y PDF
const { url: xmlUrl } = await factuplan.debitNotes.downloadXml('DEBIT_NOTE_ID');
const { url: pdfUrl } = await factuplan.debitNotes.downloadPdf('DEBIT_NOTE_ID');Firmar y autorizar XML
Si ya tienes el XML de la nota de débito (sin firmar), Factuplan lo firma y autoriza ante el SRI. La factura vinculada se resuelve automáticamente desde el XML.
Importante: El XML debe enviarse sin firmar.
const result = await factuplan.debitNotes.signAndAuthorize({
xml: xmlString,
});
console.log(result.id);
console.log(result.accessKey);
console.log(result.status);Anular
const voided = await factuplan.debitNotes.void('DEBIT_NOTE_ID', 'Emitida por error');
console.log(voided.status); // 'VOIDED'La anulación de la nota de débito ante el SRI es responsabilidad del desarrollador. El endpoint /void solo cambia el estado en Factuplan.
Secuencial
Establece el próximo número secuencial para notas de débito en el punto de emisión indicado. El nuevo valor entra en efecto en el siguiente comprobante emitido.
const result = await factuplan.debitNotes.updateSequential(
'001', // branchCode — código del establecimiento
'001', // emissionCode — código del punto de emisión
100, // debitNoteSequential — nuevo valor del secuencial
);
console.log(result.debitNoteSequential); // 100
console.log(result.updatedAt); // '2026-06-08T12:00:00.000Z'| Campo | Tipo | Descripción |
|---|---|---|
| branchCode | string | Código del establecimiento |
| emissionCode | string | Código del punto de emisión |
| debitNoteSequential | number | Nuevo valor del secuencial aplicado |
| updatedAt | string | Fecha de actualización (ISO 8601) |
Retenciones
Emite comprobantes de retención en la fuente (renta, IVA e ISD) vinculados a una factura ya autorizada, identificada por su clave de acceso de 49 dígitos. El sistema reserva el secuencial, genera el XML, lo firma y lo autoriza ante el SRI de forma asíncrona.
| Método SDK | Descripción |
|---|---|
| withholdings.create(input) | Emitir comprobante de retención |
| withholdings.list(params) | Buscar y listar retenciones con filtros |
| withholdings.get(id) | Obtener detalle del comprobante |
| withholdings.getStatus(id) | Consultar estado de procesamiento |
| withholdings.downloadXml(id) | Descargar XML autorizado (URL pre-firmada) |
| withholdings.downloadPdf(id) | Descargar PDF RIDE (URL pre-firmada) |
| withholdings.void(id, reason) | Anular retención |
Emitir retención
const withholding = await factuplan.withholdings.create({
invoiceAccessKey: '2312202301179214289100110010020000001231234567811',
taxes: [
{
taxType: 'RENTA', // 'RENTA' | 'IVA' | 'ISD'
taxRateCode: '303', // Código SRI (Tabla 25)
taxRate: 10, // Porcentaje de retención
taxBase: 1000.00, // Base imponible
fiscalPeriod: '06/2026', // Período fiscal MM/YYYY (opcional)
},
{
taxType: 'IVA',
taxRateCode: '725',
taxRate: 30,
taxBase: 150.00,
},
],
additionalInfo: { observacion: 'Servicios profesionales' }, // opcional
});
console.log(withholding.id); // 'clx1234...'
console.log(withholding.accessKey); // '0512202404...'
console.log(withholding.status); // 'PROCESSING'Buscar retenciones
const { data: withholdings, meta } = await factuplan.withholdings.list({
status: 'AUTHORIZED',
dateFrom: '2026-01-01',
dateTo: '2026-06-30',
page: 1,
limit: 20,
});Consultar estado
const estado = await factuplan.withholdings.getStatus('WITHHOLDING_ID');
console.log(estado.status); // 'AUTHORIZED'
console.log(estado.authorizationNumber); // Número de autorización SRI
console.log(estado.authorizationDate); // Fecha de autorizaciónDescargar XML / PDF
const { url: xmlUrl } = await factuplan.withholdings.downloadXml('WITHHOLDING_ID');
const { url: pdfUrl } = await factuplan.withholdings.downloadPdf('WITHHOLDING_ID');Firmar y autorizar XML
Si ya tienes el XML de la retención (sin firmar), Factuplan lo firma y autoriza ante el SRI. La factura vinculada se resuelve automáticamente desde el XML.
Importante: El XML debe enviarse sin firmar.
const result = await factuplan.withholdings.signAndAuthorize({
xml: xmlString,
});
console.log(result.id);
console.log(result.accessKey);
console.log(result.status);Anular
const voided = await factuplan.withholdings.void('WITHHOLDING_ID', 'Emitida por error');
console.log(voided.status); // 'VOIDED'La anulación de la retención ante el SRI es responsabilidad del desarrollador. El endpoint /void solo cambia el estado en Factuplan.
Guías de remisión
Emite guías de remisión electrónicas para documentar el traslado de mercadería. El sistema genera el XML, lo firma con tu certificado y lo autoriza ante el SRI de forma asíncrona.
| Método SDK | Descripción |
|---|---|
| waybills.create(input) | Emitir guía de remisión |
| waybills.list(params) | Buscar y listar guías con filtros |
| waybills.get(id) | Obtener detalle de la guía |
| waybills.getStatus(id) | Consultar estado de procesamiento |
| waybills.downloadXml(id) | Descargar XML autorizado (URL pre-firmada) |
| waybills.downloadPdf(id) | Descargar PDF RIDE (URL pre-firmada) |
Emitir guía de remisión
const guia = await factuplan.waybills.create({
emissionPointId: 'EMISSION_POINT_ID',
customer: {
identificationType: 'RUC',
identification: '1790012345001',
legalName: 'Empresa Destino S.A.',
email: 'destino@empresa.com',
},
transporterRuc: '1790012345001',
transporterName: 'Transportes del Norte S.A.',
vehiclePlate: 'ABC-1234',
departureAddress: 'Av. 10 de Agosto N23-45, Quito',
transportStartDate: '2026-06-10',
transportEndDate: '2026-06-11',
items: [
{
code: 'PROD-001',
description: 'Caja de mercadería',
quantity: 10,
},
],
destinations: [
{
receiverIdentification: '0950194407001',
receiverName: 'Cliente Final',
address: 'Av. del Ejército 123, Guayaquil',
transferReason: 'Venta de mercadería',
},
],
});
console.log(guia.id); // 'clx1234...'
console.log(guia.accessKey); // '0606202604...'
console.log(guia.status); // 'PROCESSING'| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| emissionPointId | string | Sí | ID del punto de emisión |
| customer | object | Sí | Destinatario (ID existente o datos inline) |
| transporterRuc | string | Sí | RUC del transportista |
| transporterName | string | Sí | Nombre del transportista |
| vehiclePlate | string | Sí | Placa del vehículo (ej. ABC-1234) |
| departureAddress | string | Sí | Dirección de inicio del transporte |
| transportStartDate | string | Sí | Fecha inicio transporte (ISO 8601) |
| transportEndDate | string | Sí | Fecha fin transporte (ISO 8601) |
| items | array | Sí | Mercadería transportada |
| destinations | array | No | Destinos del traslado |
| additionalInfo | object | No | Información adicional clave-valor |
Buscar guías
const { data: guias, meta } = await factuplan.waybills.list({
status: 'AUTHORIZED',
dateFrom: '2026-01-01',
dateTo: '2026-06-30',
page: 1,
limit: 20,
});Consultar estado
const estado = await factuplan.waybills.getStatus('WAYBILL_ID');
console.log(estado.status); // 'AUTHORIZED'
console.log(estado.authorizationNumber); // Número de autorización SRI
console.log(estado.authorizationDate); // Fecha de autorizaciónDescargar XML / PDF
const { url: xmlUrl } = await factuplan.waybills.downloadXml('WAYBILL_ID');
const { url: pdfUrl } = await factuplan.waybills.downloadPdf('WAYBILL_ID');Anular
const voided = await factuplan.waybills.void('WAYBILL_ID', 'Emitida por error');
console.log(voided.status); // 'VOIDED'La anulación de la guía ante el SRI es responsabilidad del desarrollador. El endpoint /void solo cambia el estado en Factuplan.
Firma & Certificados
Manejo 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.
Anular comprobante
Anula un comprobante a nivel del sistema. El estado cambia a VOIDED. Solo se pueden anular comprobantes en estado AUTHORIZED o COMPLETED.
const result = await factuplan.invoices.void(
'ID_DEL_COMPROBANTE',
'Error en los datos del cliente',
);
console.log(result.status); // "VOIDED"
console.log(result.voidReason); // Motivo ingresado
console.log(result.message); // ConfirmaciónRespuesta
| Campo | Tipo | Descripción |
|---|---|---|
| id | string | ID del comprobante anulado |
| status | string | "VOIDED" (estado anulado) |
| accessKey | string | Clave de acceso del comprobante |
| voidReason | string | Motivo de la anulación |
| message | string | Mensaje de confirmación |
La anulación ante el SRI es responsabilidad del desarrollador. Este endpoint solo cambia el estado del comprobante en Factuplan.
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); // 100
console.log(result.updatedAt); // ISO 8601Respuesta
| 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) |
El cambio afecta únicamente el secuencial de facturas en el punto de emisión indicado. Para actualizar el secuencial de notas de crédito o notas de débito usa sus respectivos endpoints.
Subir firma electrónica (P12)
Sube un certificado P12. 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 contribuyenteRespuesta
| Campo | Tipo | Descripción |
|---|---|---|
| hasCertificate | boolean | true si el certificado se subió correctamente |
| isExpired | boolean | true si el certificado ya expiró |
| 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 |