Skip to content

Latest commit

 

History

History
1982 lines (1497 loc) · 50.6 KB

File metadata and controls

1982 lines (1497 loc) · 50.6 KB

NFE.io SDK v3 - API Reference

Complete API reference for the NFE.io Node.js SDK v3.

Table of Contents

Installation

npm install nfe-io

Client

Constructor

new NfeClient(config: NfeConfig): NfeClient

Creates a new NFE.io API client instance.

Parameters:

  • config - Client configuration object

Configuration Options:

Property Type Required Default Description
apiKey string Yes* process.env.NFE_API_KEY NFE.io API key
environment 'production' | 'development' No 'production' API environment (both use same endpoint: https://api.nfe.io/v1)
baseUrl string No Auto-detected Custom API base URL
timeout number No 30000 Request timeout in ms
retryConfig RetryConfig No See below Retry configuration

* API key is required but can be provided via NFE_API_KEY environment variable.

Retry Configuration:

Property Type Default Description
maxRetries number 3 Maximum retry attempts
baseDelay number 1000 Initial delay in ms
maxDelay number 30000 Maximum delay in ms
retryableStatuses number[] [408, 429, 500, 502, 503] HTTP status codes to retry

Examples:

// Basic usage
const nfe = new NfeClient({
  apiKey: 'your-api-key',
  environment: 'production'
});

// With environment variable
// Set NFE_API_KEY=your-api-key
const nfe = new NfeClient({
  environment: 'production'
});

// Custom configuration
const nfe = new NfeClient({
  apiKey: 'your-api-key',
  timeout: 60000,
  retryConfig: {
    maxRetries: 5,
    baseDelay: 2000,
    maxDelay: 60000
  }
});

Configuration

updateConfig(newConfig: Partial<NfeConfig>): void

Update client configuration dynamically.

nfe.updateConfig({
  environment: 'development',
  timeout: 60000
});

setTimeout(timeout: number): void

Set request timeout in milliseconds. (v2 compatibility)

nfe.setTimeout(60000); // 60 seconds

setApiKey(apiKey: string): void

Set or update API key. (v2 compatibility)

nfe.setApiKey('new-api-key');

getConfig(): Readonly<RequiredNfeConfig>

Get current client configuration.

const config = nfe.getConfig();
console.log('Environment:', config.environment);

Utility Methods

pollUntilComplete<T>(locationUrl: string, options?: PollOptions): Promise<T>

Poll a resource URL until it completes processing.

Parameters:

  • locationUrl - URL or path to poll
  • options - Polling configuration
    • maxAttempts (default: 30) - Maximum polling attempts
    • intervalMs (default: 2000) - Delay between attempts in ms

Returns: Promise resolving to the completed resource

Example:

const result = await nfe.serviceInvoices.create(companyId, data);

if (result.status === 'pending') {
  const invoice = await nfe.pollUntilComplete(result.location, {
    maxAttempts: 60,
    intervalMs: 3000
  });
}

healthCheck(): Promise<{ status: 'ok' | 'error', details?: any }>

Check API connectivity and authentication.

const health = await nfe.healthCheck();

if (health.status === 'ok') {
  console.log('API connection successful');
} else {
  console.error('API error:', health.details);
}

getClientInfo(): ClientInfo

Get client diagnostic information.

const info = nfe.getClientInfo();
console.log('SDK Version:', info.version);
console.log('Node Version:', info.nodeVersion);
console.log('Environment:', info.environment);

Resources

All resources follow a consistent pattern with standard CRUD operations plus resource-specific methods.

Service Invoices

Resource: nfe.serviceInvoices

Complete service invoice (NFS-e) operations including creation, retrieval, email delivery, document downloads, and cancellation. Supports both synchronous and asynchronous invoice processing with automatic polling capabilities.


Core Operations

create(companyId: string, data: ServiceInvoiceData): Promise<ServiceInvoiceCreateResult>

Create a new service invoice. Returns either a completed invoice (201) or async processing result (202).

Parameters:

Parameter Type Description
companyId string Company ID issuing the invoice
data ServiceInvoiceData Invoice data (see structure below)

Returns: Promise<ServiceInvoiceCreateResult> - Discriminated union:

  • 201 Created (Synchronous): Returns complete ServiceInvoice with id, number, status
  • 202 Accepted (Asynchronous): Returns { flowStatus, flowMessage?, location? } for polling

Invoice Data Structure:

interface ServiceInvoiceData {
  // Required fields
  borrower: {
    federalTaxNumber: number;        // CPF (11 digits) or CNPJ (14 digits)
    name: string;
    email?: string;
    address?: {
      country: string;                // 'BRA'
      postalCode: string;             // CEP: '01310-100'
      street: string;
      number: string;
      additionalInformation?: string;
      district: string;
      city: {
        code: string;                  // IBGE code: '3550308'
        name: string;
      };
      state: string;                   // UF: 'SP'
    };
  };
  cityServiceCode: string;             // Municipal service code
  description: string;                 // Service description
  servicesAmount: number;              // Amount in BRL (e.g., 1500.00)

  // Optional fields
  rpsSerialNumber?: string;            // RPS series
  rpsNumber?: number;                  // RPS number
  issuedOn?: string;                   // ISO date: '2024-01-15'
  deductions?: number;                 // Deductions amount
  discountUnconditioned?: number;      // Unconditional discount
  discountConditioned?: number;        // Conditional discount
  taxes?: {
    retainIss?: boolean;
    iss?: number;
    pis?: number;
    cofins?: number;
    inss?: number;
    ir?: number;
    csll?: number;
  };
}

Examples:

// Example 1: Basic invoice creation (may be sync or async)
const result = await nfe.serviceInvoices.create('company-id', {
  borrower: {
    federalTaxNumber: 12345678901,
    name: 'João da Silva',
    email: 'joao@example.com',
  },
  cityServiceCode: '10677',
  description: 'Consulting services',
  servicesAmount: 1500.00,
});

// Check if synchronous (201) or asynchronous (202)
if ('id' in result) {
  // Synchronous - invoice issued immediately
  console.log('Invoice issued:', result.number);
  console.log('Status:', result.status);
} else {
  // Asynchronous - needs polling
  console.log('Processing:', result.flowStatus);
  console.log('Poll URL:', result.location);
  
  // Use pollUntilComplete or createAndWait instead
  const invoice = await nfe.pollUntilComplete(result.location, {
    intervalMs: 2000,
    timeoutMs: 60000,
  });
  console.log('Invoice issued:', invoice.number);
}

// Example 2: Invoice with full details
const invoice = await nfe.serviceInvoices.create('company-id', {
  borrower: {
    federalTaxNumber: 12345678000190,
    name: 'Acme Corporation',
    email: 'finance@acme.com',
    address: {
      country: 'BRA',
      postalCode: '01310-100',
      street: 'Av. Paulista',
      number: '1578',
      district: 'Bela Vista',
      city: {
        code: '3550308',
        name: 'São Paulo',
      },
      state: 'SP',
    },
  },
  cityServiceCode: '01234',
  description: 'Software development services',
  servicesAmount: 5000.00,
  rpsSerialNumber: 'A',
  rpsNumber: 123,
  deductions: 100.00,
  taxes: {
    retainIss: false,
    iss: 5.0, // 5%
  },
});

Error Handling:

  • ValidationError (400): Invalid invoice data
  • AuthenticationError (401): Invalid API key
  • NotFoundError (404): Company not found
  • InternalError (500): Server error
try {
  const result = await nfe.serviceInvoices.create(companyId, data);
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid data:', error.message);
  } else if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  }
}

createAndWait(companyId: string, data: ServiceInvoiceData, options?: WaitOptions): Promise<ServiceInvoice>

⭐ Recommended: Create invoice with automatic polling for async processing. Simplifies async workflow.

Parameters:

Parameter Type Description
companyId string Company ID
data ServiceInvoiceData Invoice data
options WaitOptions Polling configuration (optional)

Wait Options:

Option Type Default Description
pollingInterval number 2000 Delay between status checks (ms)
maxWaitTime number 60000 Maximum wait time (ms)

Returns: Promise<ServiceInvoice> - Completed invoice with id, number, status: 'Issued'

Examples:

// Example 1: Simple usage (recommended)
const invoice = await nfe.serviceInvoices.createAndWait('company-id', {
  borrower: {
    federalTaxNumber: 12345678901,
    name: 'João da Silva',
    email: 'joao@example.com',
  },
  cityServiceCode: '10677',
  description: 'Consulting services',
  servicesAmount: 1500.00,
});

console.log('Invoice issued:', invoice.number);
console.log('Status:', invoice.status); // 'Issued'

// Example 2: Custom polling configuration
const invoice = await nfe.serviceInvoices.createAndWait('company-id', data, {
  pollingInterval: 3000,  // Check every 3 seconds
  maxWaitTime: 120000,    // Wait up to 2 minutes
});

// Example 3: With error handling
try {
  const invoice = await nfe.serviceInvoices.createAndWait('company-id', data, {
    maxWaitTime: 60000,
  });
  
  console.log('✅ Invoice issued successfully');
  console.log(`   Number: ${invoice.number}`);
  console.log(`   Amount: R$ ${invoice.servicesAmount}`);
} catch (error) {
  if (error.message.includes('timeout')) {
    console.error('⏱️  Invoice processing timeout - may complete later');
  } else if (error.message.includes('failed')) {
    console.error('❌ Invoice processing failed:', error.message);
  }
}

When to use:

  • ✅ You want immediate invoice results without manual polling
  • ✅ You can wait 5-30 seconds for processing
  • ✅ Simple workflows where async complexity isn't needed

When NOT to use:

  • ❌ Background job processing (use create() + queue)
  • ❌ Batch operations (use createBatch())
  • ❌ Need to track processing separately

list(companyId: string, options?: ListOptions): Promise<ServiceInvoice[]>

List service invoices for a company with pagination and filtering.

Parameters:

Parameter Type Description
companyId string Company ID
options ListOptions Filtering and pagination (optional)

List Options:

Option Type Description
pageCount number Items per page (default: 25)
pageIndex number Page number, 0-indexed (default: 0)
searchPeriod object Date range filter
searchPeriod.startDate string Start date: 'YYYY-MM-DD'
searchPeriod.endDate string End date: 'YYYY-MM-DD'

Returns: Promise<ServiceInvoice[]> - Array of invoices

Examples:

// Example 1: List all (first page)
const invoices = await nfe.serviceInvoices.list('company-id');
console.log(`Found ${invoices.length} invoices`);

// Example 2: Pagination
const page2 = await nfe.serviceInvoices.list('company-id', {
  pageCount: 50,   // 50 per page
  pageIndex: 1,    // Second page (0-indexed)
});

// Example 3: Date filtering
const lastMonth = await nfe.serviceInvoices.list('company-id', {
  searchPeriod: {
    startDate: '2024-01-01',
    endDate: '2024-01-31',
  },
  pageCount: 100,
});

// Example 4: Process all invoices
let pageIndex = 0;
let allInvoices = [];

while (true) {
  const page = await nfe.serviceInvoices.list('company-id', {
    pageCount: 100,
    pageIndex,
  });
  
  allInvoices.push(...page);
  
  if (page.length < 100) break; // Last page
  pageIndex++;
}

console.log(`Total invoices: ${allInvoices.length}`);

// Example 5: Find specific invoices
const recentHighValue = await nfe.serviceInvoices.list('company-id', {
  searchPeriod: {
    startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
      .toISOString()
      .split('T')[0],
    endDate: new Date().toISOString().split('T')[0],
  },
});

const filtered = recentHighValue.filter(inv => inv.servicesAmount > 5000);
console.log(`High-value invoices: ${filtered.length}`);

retrieve(companyId: string, invoiceId: string): Promise<ServiceInvoice>

Get a specific service invoice by ID with complete details.

Parameters:

Parameter Type Description
companyId string Company ID
invoiceId string Invoice ID

Returns: Promise<ServiceInvoice> - Complete invoice object

Examples:

// Example 1: Basic retrieval
const invoice = await nfe.serviceInvoices.retrieve('company-id', 'invoice-id');

console.log('Invoice:', invoice.number);
console.log('Amount:', invoice.servicesAmount);
console.log('Status:', invoice.status);
console.log('Issued:', invoice.issuedOn);

// Example 2: Check invoice details
const invoice = await nfe.serviceInvoices.retrieve('company-id', 'invoice-id');

console.log('Borrower:', invoice.borrower.name);
console.log('Service:', invoice.description);
console.log('Tax Amount:', invoice.taxes?.iss || 0);

// Example 3: Verify invoice exists before operation
async function sendInvoiceIfExists(companyId, invoiceId) {
  try {
    const invoice = await nfe.serviceInvoices.retrieve(companyId, invoiceId);
    
    if (invoice.status === 'Issued') {
      await nfe.serviceInvoices.sendEmail(companyId, invoiceId, {
        emails: [invoice.borrower.email],
      });
      console.log('Email sent successfully');
    } else {
      console.log('Invoice not ready:', invoice.status);
    }
  } catch (error) {
    if (error instanceof NotFoundError) {
      console.error('Invoice not found');
    }
  }
}

Error Handling:

  • NotFoundError (404): Invoice or company not found
  • AuthenticationError (401): Invalid API key

getStatus(companyId: string, invoiceId: string): Promise<InvoiceStatus>

Check invoice processing status (useful for async invoices).

Parameters:

Parameter Type Description
companyId string Company ID
invoiceId string Invoice ID

Returns: Promise<InvoiceStatus> with:

Field Type Description
status string Current status (see status values below)
isComplete boolean true if processing finished (success or failure)
isFailed boolean true if processing failed

Status Values:

Status isComplete isFailed Description
Issued true false ✅ Invoice issued successfully
IssueFailed true true ❌ Issuance failed
Cancelled true false 🚫 Invoice cancelled
CancellationFailed true true ❌ Cancellation failed
WaitingSend false false ⏳ Pending
WaitingSendAuthorize false false ⏳ Awaiting authorization
Processing false false ⏳ Processing

Examples:

// Example 1: Simple status check
const status = await nfe.serviceInvoices.getStatus('company-id', 'invoice-id');

console.log('Status:', status.status);
console.log('Complete:', status.isComplete ? 'Yes' : 'No');
console.log('Failed:', status.isFailed ? 'Yes' : 'No');

// Example 2: Manual polling loop
async function waitForInvoice(companyId, invoiceId, maxAttempts = 30) {
  for (let i = 0; i < maxAttempts; i++) {
    const status = await nfe.serviceInvoices.getStatus(companyId, invoiceId);
    
    if (status.isComplete) {
      if (status.isFailed) {
        throw new Error(`Invoice processing failed: ${status.status}`);
      }
      
      console.log('Invoice complete:', status.status);
      return await nfe.serviceInvoices.retrieve(companyId, invoiceId);
    }
    
    console.log(`Attempt ${i + 1}: ${status.status}`);
    await new Promise(resolve => setTimeout(resolve, 2000));
  }
  
  throw new Error('Polling timeout');
}

// Example 3: Status-based logic
const status = await nfe.serviceInvoices.getStatus('company-id', 'invoice-id');

if (status.status === 'Issued') {
  console.log('✅ Ready to send email');
  await nfe.serviceInvoices.sendEmail(/* ... */);
} else if (status.isFailed) {
  console.error('❌ Failed:', status.status);
} else {
  console.log('⏳ Still processing:', status.status);
}

cancel(companyId: string, invoiceId: string): Promise<ServiceInvoice>

Cancel an issued service invoice.

Parameters:

Parameter Type Description
companyId string Company ID
invoiceId string Invoice ID to cancel

Returns: Promise<ServiceInvoice> - Cancelled invoice with status: 'Cancelled'

Examples:

// Example 1: Simple cancellation
const cancelled = await nfe.serviceInvoices.cancel('company-id', 'invoice-id');
console.log('Status:', cancelled.status); // 'Cancelled'

// Example 2: Conditional cancellation
const invoice = await nfe.serviceInvoices.retrieve('company-id', 'invoice-id');

if (invoice.status === 'Issued') {
  const cancelled = await nfe.serviceInvoices.cancel('company-id', 'invoice-id');
  console.log('✅ Invoice cancelled');
} else {
  console.log('⚠️  Invoice cannot be cancelled:', invoice.status);
}

// Example 3: Batch cancellation with error handling
const invoiceIds = ['id-1', 'id-2', 'id-3'];

for (const invoiceId of invoiceIds) {
  try {
    await nfe.serviceInvoices.cancel('company-id', invoiceId);
    console.log(`✅ Cancelled: ${invoiceId}`);
  } catch (error) {
    if (error instanceof NotFoundError) {
      console.log(`⚠️  Not found: ${invoiceId}`);
    } else {
      console.error(`❌ Error cancelling ${invoiceId}:`, error.message);
    }
  }
}

Error Handling:

  • NotFoundError (404): Invoice not found
  • ValidationError (400): Invoice cannot be cancelled (already cancelled, etc.)

Email & Downloads

sendEmail(companyId: string, invoiceId: string, options: SendEmailOptions): Promise<void>

Send invoice via email to specified recipients.

Parameters:

Parameter Type Description
companyId string Company ID
invoiceId string Invoice ID
options SendEmailOptions Email configuration

Email Options:

Field Type Description
emails string[] Recipient email addresses

Examples:

// Example 1: Send to single recipient
await nfe.serviceInvoices.sendEmail('company-id', 'invoice-id', {
  emails: ['client@example.com'],
});

console.log('✅ Email sent');

// Example 2: Send to multiple recipients
await nfe.serviceInvoices.sendEmail('company-id', 'invoice-id', {
  emails: [
    'client@example.com',
    'finance@example.com',
    'accounting@example.com',
  ],
});

// Example 3: Send after invoice creation
const invoice = await nfe.serviceInvoices.createAndWait('company-id', data);

await nfe.serviceInvoices.sendEmail('company-id', invoice.id, {
  emails: [invoice.borrower.email],
});

console.log(`Email sent to ${invoice.borrower.email}`);

// Example 4: Bulk email sending
const invoices = await nfe.serviceInvoices.list('company-id');

for (const invoice of invoices) {
  if (invoice.status === 'Issued' && invoice.borrower.email) {
    try {
      await nfe.serviceInvoices.sendEmail('company-id', invoice.id, {
        emails: [invoice.borrower.email],
      });
      console.log(`✅ Sent: ${invoice.number}`);
    } catch (error) {
      console.error(`❌ Failed ${invoice.number}:`, error.message);
    }
  }
}

downloadPdf(companyId: string, invoiceId?: string): Promise<Buffer>

Download invoice PDF. If invoiceId is omitted, downloads all invoices as ZIP.

Parameters:

Parameter Type Description
companyId string Company ID
invoiceId string Invoice ID (optional - omit for bulk ZIP)

Returns: Promise<Buffer> - PDF file as Buffer (or ZIP for bulk)

Examples:

import { writeFileSync } from 'fs';

// Example 1: Download single invoice PDF
const pdfBuffer = await nfe.serviceInvoices.downloadPdf('company-id', 'invoice-id');

// Validate PDF signature
if (pdfBuffer.toString('utf8', 0, 4) === '%PDF') {
  console.log('✅ Valid PDF');
}

// Save to file
writeFileSync('invoice.pdf', pdfBuffer);
console.log('Saved invoice.pdf');

// Example 2: Download all invoices as ZIP
const zipBuffer = await nfe.serviceInvoices.downloadPdf('company-id');

writeFileSync(`invoices_${Date.now()}.zip`, zipBuffer);
console.log('Saved ZIP with all invoices');

// Example 3: Download and send via HTTP response (Express)
app.get('/invoice/:id/pdf', async (req, res) => {
  try {
    const pdfBuffer = await nfe.serviceInvoices.downloadPdf(
      req.user.companyId,
      req.params.id
    );
    
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', `attachment; filename="invoice-${req.params.id}.pdf"`);
    res.send(pdfBuffer);
  } catch (error) {
    res.status(404).json({ error: 'Invoice not found' });
  }
});

// Example 4: Download after creation
const invoice = await nfe.serviceInvoices.createAndWait('company-id', data);

const pdf = await nfe.serviceInvoices.downloadPdf('company-id', invoice.id);
writeFileSync(`invoice_${invoice.number}.pdf`, pdf);
console.log(`Downloaded invoice ${invoice.number}`);

Error Handling:

  • NotFoundError (404): Invoice not ready or not found

downloadXml(companyId: string, invoiceId?: string): Promise<Buffer>

Download invoice XML. If invoiceId is omitted, downloads all invoices as ZIP.

Parameters:

Parameter Type Description
companyId string Company ID
invoiceId string Invoice ID (optional - omit for bulk ZIP)

Returns: Promise<Buffer> - XML file as Buffer (or ZIP for bulk)

Examples:

import { writeFileSync } from 'fs';

// Example 1: Download single invoice XML
const xmlBuffer = await nfe.serviceInvoices.downloadXml('company-id', 'invoice-id');

// Convert Buffer to string
const xmlString = xmlBuffer.toString('utf8');
console.log('XML preview:', xmlString.substring(0, 100));

// Validate XML signature
if (xmlString.startsWith('<?xml')) {
  console.log('✅ Valid XML');
}

// Save to file
writeFileSync('invoice.xml', xmlBuffer);

// Example 2: Download all invoices as ZIP
const zipBuffer = await nfe.serviceInvoices.downloadXml('company-id');
writeFileSync(`invoices_xml_${Date.now()}.zip`, zipBuffer);

// Example 3: Parse XML for integration
import { parseString } from 'xml2js';

const xmlBuffer = await nfe.serviceInvoices.downloadXml('company-id', 'invoice-id');
const xmlString = xmlBuffer.toString('utf8');

parseString(xmlString, (err, result) => {
  if (err) throw err;
  console.log('Parsed XML:', result);
  // Process structured data
});

// Example 4: Bulk download and extract
const zipBuffer = await nfe.serviceInvoices.downloadXml('company-id');
writeFileSync('invoices.zip', zipBuffer);

// Extract ZIP using library like 'adm-zip'
// const AdmZip = require('adm-zip');
// const zip = new AdmZip(zipBuffer);
// zip.extractAllTo('./invoices/', true);

Advanced Operations

createBatch(companyId: string, invoicesData: ServiceInvoiceData[], options?: BatchOptions): Promise<ServiceInvoice[]>

Create multiple invoices concurrently with concurrency control.

Parameters:

Parameter Type Description
companyId string Company ID
invoicesData ServiceInvoiceData[] Array of invoice data
options BatchOptions Batch configuration (optional)

Batch Options:

Option Type Default Description
waitForComplete boolean true Wait for all invoices to complete processing
maxConcurrent number 5 Maximum concurrent requests
pollingInterval number 2000 Polling interval in ms (if waitForComplete=true)
maxWaitTime number 60000 Max wait time per invoice in ms

Returns: Promise<ServiceInvoice[]> - Array of created invoices

Examples:

// Example 1: Basic batch creation
const invoicesData = [
  {
    borrower: { federalTaxNumber: 111, name: 'Client 1', email: 'client1@example.com' },
    cityServiceCode: '10677',
    description: 'Service 1',
    servicesAmount: 1000,
  },
  {
    borrower: { federalTaxNumber: 222, name: 'Client 2', email: 'client2@example.com' },
    cityServiceCode: '10677',
    description: 'Service 2',
    servicesAmount: 2000,
  },
  {
    borrower: { federalTaxNumber: 333, name: 'Client 3', email: 'client3@example.com' },
    cityServiceCode: '10677',
    description: 'Service 3',
    servicesAmount: 3000,
  },
];

const invoices = await nfe.serviceInvoices.createBatch('company-id', invoicesData);

console.log(`Created ${invoices.length} invoices`);
invoices.forEach(inv => console.log(`- ${inv.number}: R$ ${inv.servicesAmount}`));

// Example 2: Custom concurrency
const invoices = await nfe.serviceInvoices.createBatch('company-id', invoicesData, {
  maxConcurrent: 3,      // Process 3 at a time
  waitForComplete: true, // Wait for all to finish
});

// Example 3: Batch without waiting (fire and forget)
const results = await nfe.serviceInvoices.createBatch('company-id', invoicesData, {
  waitForComplete: false, // Don't wait for async processing
  maxConcurrent: 10,
});

// Results may contain async processing info (location, flowStatus)
results.forEach(result => {
  if ('id' in result) {
    console.log(`Issued: ${result.id}`);
  } else {
    console.log(`Processing: ${result.location}`);
  }
});

// Example 4: Read from CSV and batch create
import { parse } from 'csv-parse/sync';
import { readFileSync } from 'fs';

const csv = readFileSync('invoices.csv', 'utf8');
const records = parse(csv, { columns: true });

const invoicesData = records.map(row => ({
  borrower: {
    federalTaxNumber: parseInt(row.cpf),
    name: row.name,
    email: row.email,
  },
  cityServiceCode: row.serviceCode,
  description: row.description,
  servicesAmount: parseFloat(row.amount),
}));

console.log(`Creating ${invoicesData.length} invoices from CSV...`);

const invoices = await nfe.serviceInvoices.createBatch('company-id', invoicesData, {
  maxConcurrent: 5,
  waitForComplete: true,
});

console.log(`✅ Created ${invoices.length} invoices`);

// Example 5: Error handling in batch
try {
  const invoices = await nfe.serviceInvoices.createBatch('company-id', invoicesData);
  console.log('All succeeded');
} catch (error) {
  console.error('Batch creation failed:', error.message);
  // Note: Some invoices may have been created successfully
  // Check partial results if needed
}

Performance Notes:

  • Default concurrency (5) is safe for most APIs
  • Increase maxConcurrent carefully to avoid rate limiting
  • Large batches (>100 invoices) should be split into chunks
  • Use waitForComplete: false for background processing

Type Reference

ServiceInvoice:

interface ServiceInvoice {
  id: string;
  number?: string;
  status: 'Issued' | 'Cancelled' | 'Processing' | 'IssueFailed';
  flowStatus?: string;
  flowMessage?: string;
  borrower: {
    federalTaxNumber: number;
    name: string;
    email?: string;
    address?: Address;
  };
  cityServiceCode: string;
  description: string;
  servicesAmount: number;
  deductions?: number;
  discountUnconditioned?: number;
  discountConditioned?: number;
  issuedOn?: string;
  rpsSerialNumber?: string;
  rpsNumber?: number;
  taxes?: {
    retainIss?: boolean;
    iss?: number;
    pis?: number;
    cofins?: number;
    inss?: number;
    ir?: number;
    csll?: number;
  };
}

Companies

Resource: nfe.companies

Company management operations including CRUD, certificate management, and search capabilities.

Core CRUD Operations

create(data: Omit<Company, 'id' | 'createdOn' | 'modifiedOn'>): Promise<Company>

Create a new company with automatic CNPJ/CPF validation.

const company = await nfe.companies.create({
  federalTaxNumber: 12345678000190, // CNPJ (14 digits) or CPF (11 digits)
  name: 'My Company',
  email: 'company@example.com',
  address: {
    country: 'BRA',
    postalCode: '01310-100',
    street: 'Av. Paulista',
    number: '1000',
    city: {
      code: '3550308',
      name: 'São Paulo'
    },
    state: 'SP'
  }
});
list(options?: PaginationOptions): Promise<ListResponse<Company>>

List companies with pagination.

const companies = await nfe.companies.list({
  pageCount: 20,
  pageIndex: 0
});
listAll(): Promise<Company[]>

Get all companies (auto-pagination).

// Automatically fetches all pages
const allCompanies = await nfe.companies.listAll();
console.log(`Total: ${allCompanies.length} companies`);
listIterator(): AsyncIterableIterator<Company>

Memory-efficient streaming of companies.

// Process companies one at a time
for await (const company of nfe.companies.listIterator()) {
  console.log(company.name);
  // Process company...
}
retrieve(companyId: string): Promise<Company>

Get a specific company by ID.

const company = await nfe.companies.retrieve('company-id');
console.log(company.name);
update(companyId: string, data: Partial<Company>): Promise<Company>

Update company information with validation.

const updated = await nfe.companies.update('company-id', {
  name: 'New Company Name',
  email: 'newemail@example.com'
});
remove(companyId: string): Promise<{ deleted: boolean; id: string }>

Delete a company (named remove to avoid JS keyword conflict).

const result = await nfe.companies.remove('company-id');
console.log(`Deleted: ${result.deleted}`);

Certificate Management

validateCertificate(file: Buffer, password: string): Promise<CertificateValidationResult>

Pre-validate a certificate before upload.

import { readFile } from 'fs/promises';

const certBuffer = await readFile('./certificate.pfx');
const validation = await nfe.companies.validateCertificate(certBuffer, 'password');

if (validation.valid) {
  console.log('Valid until:', validation.metadata?.validTo);
} else {
  console.error('Invalid:', validation.error);
}
uploadCertificate(companyId: string, data: CertificateData): Promise<{ uploaded: boolean; message?: string }>

Upload digital certificate with automatic validation.

import { readFile } from 'fs/promises';

const certBuffer = await readFile('./certificate.pfx');

const result = await nfe.companies.uploadCertificate('company-id', {
  file: certBuffer,
  password: 'certificate-password',
  filename: 'certificate.pfx' // Optional
});

console.log(result.message);
getCertificateStatus(companyId: string): Promise<CertificateStatus>

Get certificate status with expiration calculation.

const status = await nfe.companies.getCertificateStatus('company-id');

console.log('Has certificate:', status.hasCertificate);
console.log('Expires on:', status.expiresOn);
console.log('Days until expiration:', status.daysUntilExpiration);

if (status.isExpiringSoon) {
  console.warn('⚠️  Certificate expiring soon!');
}
replaceCertificate(companyId: string, data: CertificateData): Promise<{ uploaded: boolean; message?: string }>

Replace existing certificate (alias for upload).

const result = await nfe.companies.replaceCertificate('company-id', {
  file: newCertBuffer,
  password: 'new-password',
  filename: 'new-certificate.pfx'
});
checkCertificateExpiration(companyId: string, thresholdDays?: number): Promise<ExpirationWarning | null>

Check if certificate is expiring soon.

// Check with 30-day threshold (default)
const warning = await nfe.companies.checkCertificateExpiration('company-id', 30);

if (warning) {
  console.warn(`Certificate expiring in ${warning.daysRemaining} days`);
  console.log('Expires on:', warning.expiresOn);
}

Search & Helper Methods

findByTaxNumber(taxNumber: number): Promise<Company | null>

Find company by federal tax number (CNPJ or CPF).

// Search by CNPJ (14 digits)
const company = await nfe.companies.findByTaxNumber(12345678000190);

// Or by CPF (11 digits)
const company = await nfe.companies.findByTaxNumber(12345678901);

if (company) {
  console.log('Found:', company.name);
} else {
  console.log('Company not found');
}
findByName(name: string): Promise<Company[]>

Find companies by name (case-insensitive partial match).

// Find all companies with "Acme" in the name
const companies = await nfe.companies.findByName('Acme');

companies.forEach(company => {
  console.log(`Match: ${company.name}`);
});
getCompaniesWithCertificates(): Promise<Company[]>

Get all companies that have valid certificates.

const companiesWithCerts = await nfe.companies.getCompaniesWithCertificates();

console.log(`${companiesWithCerts.length} companies with valid certificates`);
getCompaniesWithExpiringCertificates(thresholdDays?: number): Promise<Company[]>

Get companies with expiring certificates.

// Find companies with certificates expiring within 30 days
const expiring = await nfe.companies.getCompaniesWithExpiringCertificates(30);

expiring.forEach(company => {
  console.warn(`⚠️  ${company.name} certificate expiring soon`);
});

Certificate Validator Utility

The CertificateValidator utility can also be used independently:

import { CertificateValidator } from 'nfe-io';

// Check if format is supported
if (CertificateValidator.isSupportedFormat('cert.pfx')) {
  console.log('✓ Supported format');
}

// Calculate days until expiration
const expirationDate = new Date('2026-12-31');
const days = CertificateValidator.getDaysUntilExpiration(expirationDate);
console.log(`Days remaining: ${days}`);

// Check if expiring soon
if (CertificateValidator.isExpiringSoon(expirationDate, 30)) {
  console.warn('Certificate expiring within 30 days!');
}

Legal People

Resource: nfe.legalPeople

Legal person (PJ/CNPJ) operations, scoped by company.

create(companyId: string, data: Partial<LegalPerson>): Promise<LegalPerson>

Create a legal person.

const legalPerson = await nfe.legalPeople.create('company-id', {
  federalTaxNumber: '12345678000190',
  name: 'Legal Person Company',
  email: 'legal@example.com'
});

list(companyId: string, options?: PaginationOptions): Promise<ListResponse<LegalPerson>>

List legal people for a company.

const people = await nfe.legalPeople.list('company-id');

retrieve(companyId: string, legalPersonId: string): Promise<LegalPerson>

Get a specific legal person.

const person = await nfe.legalPeople.retrieve('company-id', 'person-id');

update(companyId: string, legalPersonId: string, data: Partial<LegalPerson>): Promise<LegalPerson>

Update legal person information.

const updated = await nfe.legalPeople.update('company-id', 'person-id', {
  name: 'Updated Name'
});

delete(companyId: string, legalPersonId: string): Promise<void>

Delete a legal person.

await nfe.legalPeople.delete('company-id', 'person-id');

findByTaxNumber(companyId: string, cnpj: string): Promise<LegalPerson | null>

Find legal person by CNPJ.

const person = await nfe.legalPeople.findByTaxNumber('company-id', '12345678000190');

Natural People

Resource: nfe.naturalPeople

Natural person (PF/CPF) operations, scoped by company.

create(companyId: string, data: Partial<NaturalPerson>): Promise<NaturalPerson>

Create a natural person.

const person = await nfe.naturalPeople.create('company-id', {
  federalTaxNumber: '12345678901',
  name: 'John Doe',
  email: 'john@example.com'
});

list(companyId: string, options?: PaginationOptions): Promise<ListResponse<NaturalPerson>>

List natural people for a company.

const people = await nfe.naturalPeople.list('company-id');

retrieve(companyId: string, personId: string): Promise<NaturalPerson>

Get a specific natural person.

const person = await nfe.naturalPeople.retrieve('company-id', 'person-id');

update(companyId: string, personId: string, data: Partial<NaturalPerson>): Promise<NaturalPerson>

Update natural person information.

const updated = await nfe.naturalPeople.update('company-id', 'person-id', {
  name: 'Jane Doe'
});

delete(companyId: string, personId: string): Promise<void>

Delete a natural person.

await nfe.naturalPeople.delete('company-id', 'person-id');

findByTaxNumber(companyId: string, cpf: string): Promise<NaturalPerson | null>

Find natural person by CPF.

const person = await nfe.naturalPeople.findByTaxNumber('company-id', '12345678901');

Webhooks

Resource: nfe.webhooks

Webhook configuration and management.

create(data: Partial<Webhook>): Promise<Webhook>

Create a webhook.

const webhook = await nfe.webhooks.create({
  url: 'https://example.com/webhook',
  events: ['invoice.issued', 'invoice.cancelled'],
  secret: 'webhook-secret'
});

list(options?: PaginationOptions): Promise<ListResponse<Webhook>>

List all webhooks.

const webhooks = await nfe.webhooks.list();

retrieve(webhookId: string): Promise<Webhook>

Get a specific webhook.

const webhook = await nfe.webhooks.retrieve('webhook-id');

update(webhookId: string, data: Partial<Webhook>): Promise<Webhook>

Update webhook configuration.

const updated = await nfe.webhooks.update('webhook-id', {
  events: ['invoice.issued', 'invoice.cancelled', 'invoice.error']
});

delete(webhookId: string): Promise<void>

Delete a webhook.

await nfe.webhooks.delete('webhook-id');

validateSignature(payload: string, signature: string, secret: string): boolean

Validate webhook signature (HMAC SHA-256).

// In your webhook endpoint
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-nfe-signature'];
  const payload = JSON.stringify(req.body);
  
  const isValid = nfe.webhooks.validateSignature(
    payload,
    signature,
    'your-webhook-secret'
  );
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process webhook...
});

test(webhookId: string): Promise<void>

Test webhook delivery.

await nfe.webhooks.test('webhook-id');

getAvailableEvents(): Promise<WebhookEvent[]>

Get list of available webhook event types.

const events = await nfe.webhooks.getAvailableEvents();
// ['invoice.issued', 'invoice.cancelled', ...]

Transportation Invoices (CT-e)

Resource: nfe.transportationInvoices

Manage CT-e (Conhecimento de Transporte Eletrônico) documents via SEFAZ Distribuição DFe.

Note: This resource uses a separate API host (api.nfse.io). You can configure a specific API key with dataApiKey, or the SDK will use apiKey as fallback.

Prerequisites:

  • Company must be registered with a valid A1 digital certificate
  • Webhook must be configured to receive CT-e notifications

enable(companyId: string, options?: EnableTransportationInvoiceOptions): Promise<TransportationInvoiceInboundSettings>

Enable automatic CT-e search for a company.

// Enable with default settings
const settings = await nfe.transportationInvoices.enable('company-id');

// Enable starting from a specific NSU
const settings = await nfe.transportationInvoices.enable('company-id', {
  startFromNsu: 12345
});

// Enable starting from a specific date
const settings = await nfe.transportationInvoices.enable('company-id', {
  startFromDate: '2024-01-01T00:00:00Z'
});

Options:

Property Type Description
startFromNsu number Start searching from this NSU number
startFromDate string Start searching from this date (ISO 8601)

disable(companyId: string): Promise<TransportationInvoiceInboundSettings>

Disable automatic CT-e search for a company.

const settings = await nfe.transportationInvoices.disable('company-id');
console.log('Status:', settings.status); // 'Disabled'

getSettings(companyId: string): Promise<TransportationInvoiceInboundSettings>

Get current automatic CT-e search settings.

const settings = await nfe.transportationInvoices.getSettings('company-id');
console.log('Status:', settings.status);
console.log('Start NSU:', settings.startFromNsu);
console.log('Created:', settings.createdOn);

Response:

Property Type Description
status string Current status ('Active', 'Disabled', etc.)
startFromNsu number Starting NSU number
startFromDate string Starting date (if configured)
createdOn string Creation timestamp
modifiedOn string Last modification timestamp

retrieve(companyId: string, accessKey: string): Promise<TransportationInvoiceMetadata>

Retrieve CT-e metadata by its 44-digit access key.

const cte = await nfe.transportationInvoices.retrieve(
  'company-id',
  '35240112345678000190570010000001231234567890'
);
console.log('Sender:', cte.nameSender);
console.log('CNPJ:', cte.federalTaxNumberSender);
console.log('Amount:', cte.totalInvoiceAmount);
console.log('Issued:', cte.issuedOn);

Response:

Property Type Description
accessKey string 44-digit access key
type string Document type
status string Document status
nameSender string Sender company name
federalTaxNumberSender string Sender CNPJ
totalInvoiceAmount number Total invoice amount
issuedOn string Issue date
receivedOn string Receipt date

downloadXml(companyId: string, accessKey: string): Promise<string>

Download CT-e XML content.

const xml = await nfe.transportationInvoices.downloadXml(
  'company-id',
  '35240112345678000190570010000001231234567890'
);
fs.writeFileSync('cte.xml', xml);

getEvent(companyId: string, accessKey: string, eventKey: string): Promise<TransportationInvoiceMetadata>

Retrieve CT-e event metadata.

const event = await nfe.transportationInvoices.getEvent(
  'company-id',
  '35240112345678000190570010000001231234567890',
  'event-key-123'
);
console.log('Event type:', event.type);
console.log('Event status:', event.status);

downloadEventXml(companyId: string, accessKey: string, eventKey: string): Promise<string>

Download CT-e event XML content.

const eventXml = await nfe.transportationInvoices.downloadEventXml(
  'company-id',
  '35240112345678000190570010000001231234567890',
  'event-key-123'
);
fs.writeFileSync('cte-event.xml', eventXml);

Types

Core Types

interface NfeConfig {
  apiKey?: string;
  dataApiKey?: string;     // API key for data/query services (Addresses, CT-e, CNPJ, CPF)
  environment?: 'production' | 'development';
  baseUrl?: string;
  timeout?: number;
  retryConfig?: RetryConfig;
}

interface RetryConfig {
  maxRetries?: number;
  baseDelay?: number;
  maxDelay?: number;
  retryableStatuses?: number[];
}

interface PaginationOptions {
  pageCount?: number;
  pageIndex?: number;
}

interface PollOptions {
  maxAttempts?: number;
  intervalMs?: number;
}

interface ListResponse<T> {
  items: T[];
  totalCount: number;
  pageIndex: number;
  pageCount: number;
}

Entity Types

interface Company {
  id: string;
  name: string;
  federalTaxNumber: string;
  email: string;
  address: Address;
  // ... other fields
}

interface ServiceInvoice {
  id: string;
  number?: string;
  status: ServiceInvoiceStatus;
  borrower: ServiceInvoiceBorrower;
  cityServiceCode: string;
  servicesAmount: number;
  // ... other fields
}

interface LegalPerson {
  id: string;
  name: string;
  federalTaxNumber: string; // CNPJ
  email?: string;
  // ... other fields
}

interface NaturalPerson {
  id: string;
  name: string;
  federalTaxNumber: string; // CPF
  email?: string;
  // ... other fields
}

interface Webhook {
  id: string;
  url: string;
  events: string[];
  secret?: string;
  active: boolean;
  // ... other fields
}

interface Address {
  country: string;
  postalCode: string;
  street: string;
  number: string;
  additionalInformation?: string;
  district?: string;
  city: City;
  state: string;
}

interface City {
  code: string;
  name: string;
}

type ServiceInvoiceStatus = 
  | 'pending' 
  | 'issued' 
  | 'cancelled' 
  | 'error';

Error Handling

The SDK uses a comprehensive error hierarchy:

import { 
  NfeError,
  AuthenticationError,
  ValidationError,
  NotFoundError,
  RateLimitError,
  ServerError,
  ConnectionError,
  TimeoutError,
  PollingTimeoutError,
  isNfeError,
  isAuthenticationError
} from 'nfe-io';

Error Types

Error Class HTTP Status Description
AuthenticationError 401 Invalid API key
ValidationError 400, 422 Request validation failed
NotFoundError 404 Resource not found
ConflictError 409 Resource conflict
RateLimitError 429 Rate limit exceeded
ServerError 500, 502, 503 Server error
ConnectionError - Network/connection failure
TimeoutError 408 Request timeout
PollingTimeoutError - Polling exceeded max attempts

Error Handling Example

import { 
  AuthenticationError, 
  ValidationError, 
  NotFoundError,
  isNfeError 
} from 'nfe-io';

try {
  const invoice = await nfe.serviceInvoices.create(companyId, data);
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof ValidationError) {
    console.error('Validation errors:', error.details);
  } else if (error instanceof NotFoundError) {
    console.error('Company not found');
  } else if (isNfeError(error)) {
    console.error('NFE.io error:', error.message);
  } else {
    console.error('Unexpected error:', error);
  }
}

Error Properties

All NFE.io errors extend NfeError and include:

class NfeError extends Error {
  type: ErrorType;
  statusCode?: number;
  details?: any;
  requestId?: string;
}

Advanced Usage

Custom Retry Logic

const nfe = new NfeClient({
  apiKey: 'your-api-key',
  retryConfig: {
    maxRetries: 5,
    baseDelay: 2000,
    maxDelay: 60000,
    retryableStatuses: [408, 429, 500, 502, 503, 504]
  }
});

Async Invoice Processing

NFE.io uses async processing for invoices (202 responses). The SDK provides two approaches:

Manual Polling:

const result = await nfe.serviceInvoices.create(companyId, data);

if (result.status === 'pending') {
  const invoice = await nfe.pollUntilComplete(result.location, {
    maxAttempts: 60,
    intervalMs: 3000
  });
  console.log('Invoice issued:', invoice.number);
} else {
  console.log('Invoice issued immediately:', result.number);
}

Automatic Polling (Recommended):

const invoice = await nfe.serviceInvoices.createAndWait(companyId, data, {
  maxAttempts: 30,
  interval: 2000
});

console.log('Invoice issued:', invoice.number);

Environment Detection

import { isEnvironmentSupported, getRuntimeInfo } from 'nfe-io';

// Check environment compatibility
const support = isEnvironmentSupported();
if (!support.supported) {
  console.error('Environment issues:', support.issues);
}

// Get runtime information
const info = getRuntimeInfo();
console.log('SDK Version:', info.sdkVersion);
console.log('Node Version:', info.nodeVersion);
console.log('Platform:', info.platform);

Quick Start Helpers

import { createClientFromEnv, validateApiKeyFormat } from 'nfe-io';

// Create client from environment variable
// Requires NFE_API_KEY environment variable
const nfe = createClientFromEnv('production');

// Validate API key format
const validation = validateApiKeyFormat('my-api-key');
if (!validation.valid) {
  console.error('API key issues:', validation.issues);
}

TypeScript Support

The SDK is fully typed with TypeScript:

import type {
  NfeConfig,
  ServiceInvoice,
  ServiceInvoiceData,
  Company,
  LegalPerson,
  NaturalPerson,
  Webhook,
  ListResponse,
  PaginationOptions
} from 'nfe-io';

const config: NfeConfig = {
  apiKey: 'your-api-key',
  environment: 'production'
};

const invoice: ServiceInvoice = await nfe.serviceInvoices.retrieve(
  'company-id',
  'invoice-id'
);

Extension Development

The SDK is designed to be extensible. See CONTRIBUTING.md for guidance on:

  • Creating MCP (Model Context Protocol) integrations
  • Building n8n workflow nodes
  • Developing custom adapters
  • Extending the HTTP client

Example: Custom Resource Extension

import { HttpClient } from 'nfe-io/core/http/client';

class CustomResource {
  constructor(private http: HttpClient) {}
  
  async customMethod(id: string): Promise<any> {
    return this.http.get(`/custom/${id}`);
  }
}

// Extend NfeClient
import { NfeClient } from 'nfe-io';

class ExtendedNfeClient extends NfeClient {
  public readonly custom: CustomResource;
  
  constructor(config: NfeConfig) {
    super(config);
    this.custom = new CustomResource(this.http);
  }
}

Support

License

MIT License - see LICENSE for details