SDK oficial de FiscalAPI para Node.js, la API de facturación CFDI y otros servicios fiscales en México. Simplifica la integración con los servicios de facturación electrónica, eliminando las complejidades del SAT y facilitando la generación de facturas, notas de crédito, complementos de pago, nómina, carta porte, y más. ¡Factura sin dolor!
- Soporte completo para CFDI 4.0 con todas las especificaciones oficiales
- Timbrado de facturas de ingreso con validación automática
- Timbrado de notas de crédito (facturas de egreso)
- Timbrado de complementos de pago en MXN, USD y EUR
- Timbrado de facturas de nómina
- Timbrado de facturas de carta porte
- Timbrado de facturas de comercio exterior
- Consulta del estatus de facturas en el SAT en tiempo real
- Cancelación de facturas
- Generación de archivos PDF de las facturas con formato profesional
- Personalización de logos y colores en los PDF generados
- Envío de facturas por correo electrónico automatizado
- Descarga de archivos XML con estructura completa
- Almacenamiento y recuperación de facturas por 5 años.
- Dos modos de operación: Por valores o Por referencias
- Acceso a catálogos de descarga masiva del SAT
- Descarga de CFDI y Metadatos en lotes grandes
- Descarga masiva XML con filtros personalizados
- Reglas de descarga automática por RFC
- Solicitudes de descarga via API y Dashboard.
- Automatización de solicitudes de descarga
- Administración de personas (emisores, receptores, clientes, usuarios, etc.)
- Gestión de certificados CSD y FIEL (subir archivos .cer y .key a FiscalAPI)
- Configuración de datos fiscales (RFC, domicilio fiscal, régimen fiscal)
- Datos de empleado (agrega/actualiza/elimina datos de empleado a una persona. CFDI Nómina)
- Datos de empleador (agrega/actualiza/elimina datos de empleador a una persona. CFDI Nómina)
- Firma de carta manifiesto (firma el manifiesto de una persona con su e.firma/FIEL)
- Gestión de folios fiscales Compra timbres a fiscalapi y transfiere/retira a las personas de tu organizacion segun tus reglas de negocio.
- Créditos de validación Transfiere créditos para validaciones del SAT con el mismo endpoint, indicando
creditType. - Créditos de ticket Transfiere créditos de ticket con el mismo endpoint (
creditType: CreditType.Ticket) y consulta su saldo enbalancesde la persona.
- Verificación de CFDI timbrados contra los servicios oficiales del SAT: estructura del XML (Anexo 20), vigencia del certificado del emisor, sello del comprobante y sello del SAT en el Timbre Fiscal Digital.
- Estado del comprobante en ConsultaCFDIService: vigente, cancelado o no encontrado.
- Listas negras de los artículos 69-B y 69-B Bis del CFF, consultables por CFDI o directamente por RFC.
- Catálogo consultable de tipos de validación y de los estatus que cada uno puede devolver.
- Gestión de productos y servicios con catálogo personalizable
- Administración de impuestos aplicables (IVA, ISR, IEPS)
- Consulta en catálogos oficiales del SAT actualizados
- Consulta en catálogos oficiales de Descarga masiva del SAT actualizados
- Búsqueda de información en catálogos del SAT con filtros avanzados
- Acceso y búsqueda en catálogos completos
- Cientos de ejemplos de código disponibles en múltiples lenguajes de programación
- Documentación completa con guías paso a paso
- Ejemplos prácticos para casos de uso comunes
- Soporte técnico especializado
- Actualizaciones regulares conforme a cambios del SAT
Compatible con múltiples versiones de Node.js ( Node.js 12.0.0 o superior)
npm:
npm install fiscalapiyarn:
yarn add fiscalapiPuedes usar el SDK tanto en aplicaciones Node.js tradicionales como en frameworks modernos (Express, NestJS, Next.js, etc.). A continuación se describen ambas formas:
-
Crea tu objeto de configuración con tus credenciales:
// CommonJS const { FiscalapiClient } = require('fiscalapi'); // o ESM import { FiscalapiClient } from 'fiscalapi'; const settings = { apiUrl: "https://test.fiscalapi.com", // https://live.fiscalapi.com (producción) apiKey: "<tu_api_key>", tenant: "<tenant>" };
-
Crea la instancia del cliente:
const fiscalApi = FiscalapiClient.create(settings);
Para ejemplos completos, consulta ejemplos Express, ejemplos NestJs o más ejemplos en NodeJs.
-
Agrega la configuración en tu archivo de variables de entorno (
.env):FISCALAPI_API_KEY=<api_key> FISCALAPI_TENANT=<tenant> FISCALAPI_API_URL=https://test.fiscalapi.com -
Crea y registra el cliente (por ejemplo, en un servicio o módulo):
// services/fiscalapi.service.ts import { FiscalapiClient } from 'fiscalapi' import config from '../config/config'; export const createFiscalApiClient = () => { return FiscalapiClient.create({ apiUrl: config.fiscalapiSettings.apiUrl, apiKey: config.fiscalapiSettings.apiKey, tenant: config.fiscalapiSettings.tenant }); };
En Express:
// En tu controlador o router
import { createFiscalApiClient } from '../services/fiscalapi.service';
const fiscalapi = createFiscalApiClient();
app.post('/invoices', async (req, res) => {
try {
const response = await fiscalapi.invoices.create(req.body);
res.json(response);
} catch (error) {
res.status(500).json({ error: error.message });
}
});Para más ejemplos, revisa ejemplos Express, ejemplos NestJs o más ejemplos en NodeJs.
FiscalAPI admite dos modos de operación:
-
Por Referencias: Envía solo IDs de objetos previamente creados en el dashboard de FiscalAPI.
Ideal para integraciones ligeras. -
Por Valores: Envía todos los campos requeridos en cada petición, con mayor control sobre los datos.
No se requiere configuración previa en el dashboard.
A continuación se muestran algunos ejemplos básicos para ilustrar cómo utilizar el SDK. Puedes encontrar más ejemplos en la documentación oficial.
const fiscalApi = FiscalApiClient.create(settings);
const request = {
legalName: "Persona de Prueba",
email: "[email protected]",
password: "YourStrongPassword123!",
};
try {
const apiResponse = await fiscalApi.persons.create(request);
console.log(apiResponse.data);
} catch (error) {
console.error(error);
}Descarga certificados de prueba
const fiscalApi = FiscalApiClient.create(settings);
const certificadoCsd = {
personId: "984708c4-fcc0-43bd-9d30-ec017815c20e",
base64File: "MIIFsDCCA5igAwIBAgI...==", // Certificado .cer codificado en Base64
fileType: 0, // 0 = certificado (.cer)
password: "12345678a",
tin: "EKU9003173C9" // RFC de la persona (opcional)
};
const clavePrivadaCsd = {
personId: "984708c4-fcc0-43bd-9d30-ec017815c20e",
base64File: "MIIFDjBABgkqhkiG9w0BBQ0...==", // Llave privada .key codificada en Base64
fileType: 1, // 1 = llave privada (.key)
password: "12345678a",
tin: "EKU9003173C9" // RFC de la persona (opcional)
};
try {
const apiResponseCer = await fiscalApi.taxFiles.create(certificadoCsd);
const apiResponseKey = await fiscalApi.taxFiles.create(clavePrivadaCsd);
console.log(apiResponseCer.data, apiResponseKey.data);
} catch (error) {
console.error(error);
}const fiscalApi = FiscalApiClient.create(settings);
const request = {
description: "Servicios contables",
unitPrice: 100,
satUnitMeasurementId: "E48",
satTaxObjectId: "02",
satProductCodeId: "84111500"
};
try {
const apiResponse = await fiscalApi.products.create(request);
console.log(apiResponse.data);
} catch (error) {
console.error(error);
}const fiscalApi = FiscalApiClient.create(settings);
const request = {
id: "310301b3-1ae9-441b-b463-51a8f9ca8ba2",
description: "Servicios contables",
unitPrice: 100,
satUnitMeasurementId: "E48",
satTaxObjectId: "02",
satProductCodeId: "84111500",
productTaxes: [
{ rate: 0.16, taxId: "002", taxFlagId: "T", taxTypeId: "Tasa" }, // IVA 16%
{ rate: 0.10, taxId: "001", taxFlagId: "R", taxTypeId: "Tasa" }, // ISR 10%
{ rate: 0.10666666666, taxId: "002", taxFlagId: "R", taxTypeId: "Tasa" } // IVA 2/3 partes
]
};
try {
const apiResponse = await fiscalApi.products.update(request.id, request);
console.log(apiResponse.data);
} catch (error) {
console.error(error);
}const fiscalApi = FiscalApiClient.create(settings);
const invoice = {
versionCode: "4.0",
series: "SDK-F",
date: new Date(),
paymentFormCode: "01",
currencyCode: "MXN",
typeCode: "I",
expeditionZipCode: "42501",
issuer: {
id: "<id-emisor-en-fiscalapi>"
},
recipient: {
id: "<id-receptor-en-fiscalapi>"
},
items: [
{
id: "<id-producto-en-fiscalapi>",
quantity: 1,
discount: 10.85
}
],
paymentMethodCode: "PUE",
};
try {
const apiResponse = await fiscalApi.invoices.create(invoice);
console.log(apiResponse.data);
} catch (error) {
console.error(error);
}Si la creación puede repetirse (por ejemplo, tras un corte de red), pasa una llave de idempotencia como segundo argumento, de hasta 64 caracteres. Repetir la misma petición con la misma llave de idempotencia devuelve la factura original sin crear otra; con otra petición distinta, la API responde 422.
const apiResponse = await fiscalApi.invoices.create(invoice, "pedido-1001-intento-1");const fiscalApi = FiscalApiClient.create(settings);
// Agregar sellos CSD, Emisor, Receptor, Items, etc.
const invoice = {
versionCode: "4.0",
series: "SDK-F",
date: new Date(),
paymentFormCode: "01",
currencyCode: "MXN",
typeCode: "I",
expeditionZipCode: "42501",
issuer: {
tin: "EKU9003173C9",
legalName: "ESCUELA KEMPER URGATE",
taxRegimeCode: "601",
taxCredentials: [
{
base64File: "certificate_base64...",
fileType: 0, // 0 = certificado (.cer)
password: "12345678a"
},
{
base64File: "private_key_base64...",
fileType: 1, // 1 = llave privada (.key)
password: "12345678a"
}
]
},
recipient: {
tin: "EKU9003173C9",
legalName: "ESCUELA KEMPER URGATE",
zipCode: "42501",
taxRegimeCode: "601",
cfdiUseCode: "G01",
email: "[email protected]"
},
items: [
{
itemCode: "01010101",
quantity: 9.5,
unitOfMeasurementCode: "E48",
description: "Invoicing software as a service",
unitPrice: 3587.75,
taxObjectCode: "02",
discount: 255.85,
itemTaxes: [
{
taxCode: "002", // IVA
taxTypeCode: "Tasa",
taxRate: 0.16,
taxFlagCode: "T"
}
]
}
],
paymentMethodCode: "PUE",
};
try {
const apiResponse = await fiscalApi.invoices.create(invoice);
console.log(apiResponse.data);
} catch (error) {
console.error(error);
}try {
// Busca los registros que contengan 'inter' en el catalogo 'SatUnitMeasurements' (pagina 1, tamaño pagina 10)
const apiResponse = await fiscalApi.catalogs.searchCatalog("SatUnitMeasurements", "inter", 1, 10);
if (apiResponse.succeeded) {
apiResponse.data.items.forEach(item => {
console.log(`Unidad: ${item.description}`);
});
} else {
console.log(apiResponse.message);
}
} catch (error) {
console.error(error);
}Cada tipo de validación solicitado consume un crédito de validación. El cobro es todo o nada: si el saldo no alcanza para todos, no se ejecuta ninguno.
try {
const apiResponse = await fiscalApi.satValidations.validate({
xml: encodeToBase64(xmlDelCfdi), // CFDI timbrado, en base64
validationTypes: [
SatValidationTypeIds.XmlStructure,
SatValidationTypeIds.CfdiStatus,
SatValidationTypeIds.Blacklist69B
]
});
if (apiResponse.succeeded) {
apiResponse.data.forEach(resultado => {
console.log(`${resultado.type.id}: ${resultado.status.id} (passed: ${resultado.passed})`);
});
} else {
console.log(apiResponse.details);
}
} catch (error) {
console.error(error);
}Para consultar únicamente las listas negras de un RFC, envía tin en lugar de xml:
const apiResponse = await fiscalApi.satValidations.validate({
tin: "XAXX010101000",
validationTypes: [SatValidationTypeIds.Blacklist69B, SatValidationTypeIds.Blacklist69BBis]
});El saldo de créditos de validación se consulta en la persona, en availableValidationBalance, y se
transfiere con el servicio de timbres indicando creditType:
await fiscalApi.stamps.transferStamps({
fromPersonId: "...",
toPersonId: "...",
amount: 10,
creditType: CreditType.Validation
});Firma la carta manifiesto de una persona y devuelve el PDF resultante en base64.
Requiere la e.firma (FIEL) del contribuyente, no el CSD de timbrado, y que exista una persona con ese RFC en el tenant.
try {
const apiResponse = await fiscalApi.manifests.sign({
base64Cer: "<FIEL_CER_BASE64>",
base64Key: "<FIEL_KEY_BASE64>",
password: "<FIEL_PASSWORD>"
});
if (apiResponse.succeeded) {
console.log(apiResponse.data.fileName); // EKU9003173C9.pdf
console.log(apiResponse.data.fileExtension); // .pdf
console.log(apiResponse.data.base64File); // PDF firmado en base64
} else {
console.log(apiResponse.details);
}
} catch (error) {
console.error(error);
}Tras una firma exitosa, todas las personas del tenant con el RFC del certificado quedan con manifestStatusId en Signed; fiscalApi.persons.getById(...) lo refleja.
FiscalAPI firma cada entrega con Standard Webhooks y el secreto whsec_… de tu webhook. Webhook.verify comprueba la firma (en tiempo constante) y que webhook-timestamp no difiera más de 5 minutos del reloj del receptor, y devuelve el WebhookEvent. La firma es la única prueba de que la entrega viene de FiscalAPI.
Verifica el cuerpo crudo: en Express, recíbelo con express.raw({ type: 'application/json' }) y pasa el Buffer tal cual. Un cuerpo deserializado y vuelto a serializar (JSON.stringify(req.body) después de express.json()) cambia sus bytes y la firma deja de coincidir. Registra la ruta antes de un app.use(express.json()) global: si ese parser corre primero, req.body ya no es el cuerpo crudo.
const express = require('express');
const { FiscalapiClient, Webhook, WebhookEventTypes, WebhookVerificationError } = require('fiscalapi');
const app = express();
const fiscalApi = FiscalapiClient.create(settings);
const webhookSecret = process.env.FISCALAPI_WEBHOOK_SECRET; // whsec_… del webhook, desde tu configuración segura
app.post('/webhooks/fiscalapi', express.raw({ type: 'application/json' }), (req, res) => {
// Sin cuerpo o con otro Content-Type, express.raw no deja un Buffer: no es una entrega de FiscalAPI.
if (!Buffer.isBuffer(req.body)) return res.sendStatus(400);
let webhookEvent;
try {
webhookEvent = Webhook.verify(req.headers, req.body, webhookSecret);
} catch (error) {
if (error instanceof WebhookVerificationError) {
return res.sendStatus(400); // firma inválida, header faltante o timestamp fuera de tolerancia
}
throw error; // secreto mal configurado: error del receptor, no de la entrega
}
// Responde 2xx rápido y procesa después: FiscalAPI corta cada intento a los 15 s y lo reintenta.
res.sendStatus(200);
// Procesa fuera de la petición. setImmediate es ilustrativo: en producción usa una cola durable.
setImmediate(() => processEvent(webhookEvent).catch(console.error));
});
async function processEvent(webhookEvent) {
// La entrega es al menos una vez: webhookEvent.id (el header webhook-id) se repite en cada reintento;
// descarta los ya procesados. El orden de llegada no está garantizado: el estado autoritativo se lee del recurso.
switch (webhookEvent.type) {
case WebhookEventTypes.InvoiceCanceled: {
const invoice = await fiscalApi.invoices.getById(webhookEvent.data.relatedObject.id);
break;
}
case WebhookEventTypes.DownloadRequestCompleted: {
const request = await fiscalApi.downloadRequests.getById(webhookEvent.data.relatedObject.id);
break;
}
default:
break; // un tipo que no conoces se ignora: el catálogo puede crecer
}
}WebhookEventes el mismo sobre para todo evento:id,type(texto; compáralo con las constantes deWebhookEventTypes),createdAt(unDate; el sobre lo trae en UTC),personId(nullsi el recurso no tiene persona) ydataconrelatedObject(id,type,url),statusypreviousStatus.relatedObject.urles la ruta relativa del GET v4 del recurso (/api/v4/invoices/{id}); para leerlo, usa el servicio de su tipo conrelatedObject.id.- El botón "Enviar prueba" del webhook (
POST /api/v4/webhooks/{id}/test) envíaWebhookEventTypes.WebhookTest(webhook.test), cuyorelatedObjectes el webhook mismo (typewebhook,url/api/v4/webhooks/{id}), sin persona ni estados; no se suscribe. headersacepta el objetoreq.headersde Node/Express o unHeadersde fetch; los nombres no distinguen mayúsculas.bodyacepta elBuffercrudo o ese mismo cuerpo como texto; un cuerpo que no es UTF-8 válido se rechaza.- Durante una rotación del secreto, FiscalAPI firma con el nuevo y con el anterior por 24 h: pasa ambos,
Webhook.verify(req.headers, req.body, [secretoNuevo, secretoAnterior]). - La tolerancia es configurable en segundos (
Webhook.verify(req.headers, req.body, secreto, 120)); el valor por omisión esWebhook.DEFAULT_TOLERANCE_SECONDS(300). - Un secreto mal copiado (no es base64) o una lista de secretos vacía lanza
TypeErrorcon la posición del secreto (nunca su valor), y una tolerancia negativaRangeError: son errores de configuración, no de la entrega.
- Gestión de Timbres
- Firma de Carta Manifiesto
- Validaciones del SAT
- Tickets de Auto CFDI
- Datos Empleador/Empleado
- Facturas de Nómina (Por Valores)
- Facturas de Nómina (Por Referencias)
- Facturas de Impuestos Locales (Por Referencias)
- Facturas de Impuestos Locales (Por Valores)
- Facturas (CFDI) - Ingreso, egreso, pago, nómina, cancelaciones, PDF/XML
- Personas - Emisores, receptores, certificados CSD
- Productos - Catálogo de productos/servicios
- Timbres - Transferencias de timbres, de créditos de validación y de créditos de ticket
- Validaciones SAT - Estructura, certificado, sellos, estado del CFDI y listas negras 69-B / 69-B Bis
- Tickets (Auto CFDI) - Ingesta de ventas pagadas con
Idempotency-Keyopcional, consulta con filtros, código para reimprimir y cancelación
- Haz un fork del repositorio.
- Crea una rama para tu feature:
git checkout -b feature/AmazingFeature. - Realiza commits de tus cambios:
git commit -m 'Add some AmazingFeature'. - Sube tu rama:
git push origin feature/AmazingFeature. - Abre un Pull Request en GitHub.
- Asegúrate de usar la última versión del SDK.
- Verifica si el problema ya fue reportado.
- Proporciona un ejemplo mínimo reproducible.
- Incluye los mensajes de error completos.
Este proyecto está licenciado bajo la Licencia MPL-2.0. Consulta el archivo LICENSE para más detalles.
Desarrollado con ❤️ por Fiscalapi