FactuplanSDK
v0.8.0
Postman

Factuplan SDK

Librería oficial de JavaScript y TypeScript para crear facturas electrónicas autorizadas por el SRI de Ecuador. Zero dependencies.

Node.js 18+TypeScriptESM & CJSSin dependencias

Instalación

npm
npm install factuplan
pnpm
pnpm add factuplan
yarn
yarn add factuplan

Autenticació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.

Inicializar el cliente
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

CampoTipoDefaultDescripción
sendEmailbooleantrueSi 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 secuencial

Punto 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.

Por códigos SRI
// Usar códigos SRI del establecimiento y punto de emisión
const factura = await factuplan.invoices.create({
  establishment: '001',
  emissionPoint: '001',
  customer: { ... },
  items: [ ... ],
});
Auto-detección
// 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:

taxTypeDescripción
IVA_RATEAplica una tarifa de IVA específica (usar campo tax).
IVA_0Aplica tarifa 0% (Código SRI 0).
NOT_TAXABLENo aplica IVA (Código SRI 6).
EXEMPTExento 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.

taxCódigo SRIDescripción
00IVA 0%
55IVA 5%
88IVA 8%
122IVA 12%
143IVA 14%
154IVA 15% (default)
Ejemplo con IVA 5%
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ó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
Unidad de tiempo (timeUnit)Descripción
diasDías
mesesMeses
aniosAñ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.

Solo API key de producción (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);
POST/developer/receipts/query

Consultar 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.

Solo API key de producción (ak_live_...). Esta función consulta el SRI en tiempo real y no está disponible con claves de prueba (ak_test_...).

Parámetros

CampoTipoRequeridoDescripción
accessKeystringClave de acceso de 49 dígitos del SRI
savebooleanNo (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.
Nota sobre el contador: El crédito se descuenta siempre que el comprobante esté autorizado en el SRI, independientemente del valor de 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 base64

Ejemplo completo

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',
  });
  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 inicial

Ejemplo completo

firmar-xml.ts
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 firmado

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

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ón

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ó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

Rate Limit

Cada API key tiene un límite de velocidad y una cuota mensual para garantizar un servicio estable para todos los usuarios.

Límite de velocidad

100

peticiones por segundo por API key

Cuota mensual

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

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

Respuesta

CampoTipoDescripción
validbooleantrue si el certificado está vigente
expiresAtstringFecha de vencimiento (ISO 8601)
taxpayerIdstringRUC del titular del certificado
commonNamestringNombre registrado en el certificado

Si valid es false, las facturas no podrán firmarse hasta que renueves el certificado en Configuración → Certificado.