npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

efactura-sdk

v1.3.0

Published

Complete TypeScript SDK for Romanian ANAF API -E-Factura, Company checks

Readme

ANAF e-Factura TypeScript SDK

A comprehensive TypeScript SDK for interacting with the Romanian ANAF e-Factura system. This SDK provides OAuth 2.0 authentication, document upload/download, validation, UBL generation, and company data lookup capabilities.

Features

  • OAuth 2.0 Authentication: Complete OAuth flow with USB token support
  • Document Operations: Upload, status checking, and download
  • Message Management: List and paginate invoice messages
  • Validation: XML validation and digital signature verification
  • PDF Conversion: Convert XML invoices to PDF format
  • UBL Generation: Create compliant UBL 2.1 XML invoices
  • Company Data Lookup: Fetch Romanian company details from public ANAF API
  • TypeScript: Full type safety and IntelliSense support

Installation

yarn add efactura-sdk

Quick Start

The SDK is organized into four main classes:

1. AnafAuthenticator - OAuth 2.0 Authentication

import { AnafAuthenticator } from 'efactura-sdk';

const auth = new AnafAuthenticator({
  clientId: 'your-oauth-client-id',
  clientSecret: 'your-oauth-client-secret',
  redirectUri: 'https://your-app.com/oauth/callback',
});

// Get authorization URL (user will authenticate with USB token)
const authUrl = auth.getAuthorizationUrl({
  state: { sessionId: 'abc123', returnTo: '/dashboard' }, // optional
});
console.log('Redirect user to:', authUrl);

// Exchange authorization code for tokens
const tokens = await auth.exchangeCodeForToken(authorizationCode);
console.log('Access token:', tokens.access_token);

// Refresh tokens when needed
const newTokens = await auth.refreshAccessToken(tokens.refresh_token);

2. AnafClient - API Operations

import { AnafClient } from 'efactura-sdk';

const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: true, // Use test environment
  refreshToken: tokens.refresh_token, // Optional: refresh token
  accessToken: tokens.access_token, // Optional: access token
  // Optional: token expiration. Provide epoch milliseconds (ms since 1970),
  // for example: `Date.now() + tokens.expires_in * 1000`.
  expiresAt: tokens.expires_at,
};

// Upload a document
const uploadResult = await client.uploadDocument(tokens.access_token, xmlContent, {
  standard: 'UBL',
  executare: true,
});

// Check upload status
const status = await client.getUploadStatus(tokens.access_token, uploadResult.index_incarcare);

// Download processed document
if (status.id_descarcare) {
  const result = await client.downloadDocument(tokens.access_token, status.id_descarcare);
}

// List recent messages
const messages = await client.getMessages(tokens.access_token, {
  zile: 7, // Last 7 days
  filtru: 'E', // Only errors
});

// Validate XML
const validation = await client.validateXml(tokens.access_token, xmlContent, 'FACT1');

// Convert XML to PDF
const pdfBuffer = await client.convertXmlToPdf(tokens.access_token, xmlContent, 'FACT1');

3. UblBuilder - UBL XML Generation

import { UblBuilder } from 'efactura-sdk';

const builder = new UblBuilder();

const xml = builder.generateInvoiceXml({
  invoiceNumber: 'INV-2024-001',
  invoiceTypeCode: '380',
  issueDate: new Date(),
  supplier: {
    registrationName: 'Company SRL',
    companyId: 'RO12345678',
    isVatPayer: true,
    address: {
      street: 'Str. Example 1',
      city: 'Bucharest',
      postalZone: '010101',
    },
  },
  customer: {
    registrationName: 'Customer SRL',
    companyId: 'RO87654321',
    isVatPayer: true,
    address: {
      street: 'Str. Customer 2',
      city: 'Cluj-Napoca',
      postalZone: '400001',
    },
  },
  lines: [
    {
      description: 'Product/Service',
      quantity: 1,
      unitPrice: 100,
      taxPercent: 19,
    },
  ],
});

4. AnafDetailsClient - Company Data Lookup

import { AnafDetailsClient } from 'efactura-sdk';

const detailsClient = new AnafDetailsClient({
  timeout: 30000,
  url: 'https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva', // Optional: custom ANAF API URL
});

// Fetch company data by VAT code (single company)
const result = await detailsClient.getCompanyData('RO12345678');
if (result.success) {
  console.log('Company:', result.data[0].name);
  console.log('Address:', result.data[0].address);
  console.log('VAT registered:', result.data[0].scpTva);
  console.log('Registration number:', result.data[0].registrationNumber);
  console.log('Phone:', result.data[0].contactPhone);
} else {
  console.error('Error:', result.error);
}

// Validate VAT code format
const isValid = await detailsClient.isValidVatCode('RO12345678');
console.log('Valid format:', isValid);

// Batch fetch multiple companies (single API call)
const batchResult = await detailsClient.batchGetCompanyData(['RO12345678', 'RO87654321', 'RO11111111']);
if (batchResult.success) {
  batchResult.data.forEach((company, index) => {
    console.log(`Company ${index + 1}:`, company.name);
    console.log(`VAT registered:`, company.scpTva);
  });
} else {
  console.error('Batch error:', batchResult.error);
}

// Configuration options
const customClient = new AnafDetailsClient({
  timeout: 60000, // 60 second timeout
  url: 'https://custom-anaf-proxy.example.com/api/tva', // Custom endpoint (e.g., proxy server)
});

AnafDetailsClient Configuration

The AnafDetailsClient supports the following configuration options:

| Option | Type | Default | Description | | --------- | -------- | ----------------------------------------------------------- | ------------------------------- | | timeout | number | 30000 | Request timeout in milliseconds | | url | string | 'https://webservicesp.anaf.ro/api/PlatitorTvaRest/v9/tva' | ANAF API endpoint URL |

Configuration Examples

// Default configuration
const client = new AnafDetailsClient();

// Custom timeout
const clientWithTimeout = new AnafDetailsClient({
  timeout: 60000, // 60 seconds
});

// Custom API endpoint (useful for proxy servers or testing)
const clientWithCustomUrl = new AnafDetailsClient({
  url: 'https://your-proxy.example.com/anaf-api',
  timeout: 45000,
});

// Minimal configuration
const minimalClient = new AnafDetailsClient({
  timeout: 15000, // Fast timeout for quick responses
});

Use Cases for Custom URL

  • Proxy Server: Route requests through your own proxy for logging/monitoring
  • Load Balancer: Distribute requests across multiple ANAF endpoints
  • Testing: Point to a mock server during development
  • Regional Endpoints: Use different ANAF regional servers if available
  • Corporate Firewall: Route through approved corporate gateways

Complete Example

import { AnafAuthenticator, AnafClient, AnafDetailsClient, UblBuilder } from 'efactura-sdk';

// Setup
const auth = new AnafAuthenticator({
  clientId: process.env.ANAF_CLIENT_ID,
  clientSecret: process.env.ANAF_CLIENT_SECRET,
  redirectUri: 'https://myapp.com/oauth/callback',
});

const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: true,
});

const detailsClient = new AnafDetailsClient();
const builder = new UblBuilder();

// 1. Get customer company data
const customerData = await detailsClient.getCompanyData('RO87654321');
if (!customerData.success) {
  throw new Error(`Customer not found: ${customerData.error}`);
}

// 2. Authentication (one-time setup)
const authUrl = auth.getAuthorizationUrl({
  // set the state param optional if you want to receive identifiers on anaf callback url
  state: { sessionId: 'abc123', customerVat: customerData.data[0].vatCode },
});
// Direct user to authUrl, they authenticate with USB token
const tokens = await auth.exchangeCodeForToken(authCode);

// 3. Generate invoice XML using fetched company data
const xml = builder.generateInvoiceXml({
  invoiceNumber: 'INV-2024-001',
  invoiceTypeCode: '380',
  issueDate: new Date(),
  supplier: {
    registrationName: 'My Company SRL',
    companyId: 'RO12345678',
    isVatPayer: true,
    address: {
      street: 'Str. Example 1',
      city: 'Bucharest',
      postalZone: '010101',
    },
  },
  customer: {
    registrationName: customerData.data[0].name,
    companyId: customerData.data[0].vatCode,
    isVatPayer: customerData.data[0].isVatPayer,
    address: {
      street: customerData.data[0].address,
      city: 'Cluj-Napoca', // Parse from address if needed
      postalZone: customerData.data[0].postalCode || '400001',
    },
  },
  lines: [
    {
      description: 'Consulting Services',
      quantity: 1,
      unitPrice: 1000,
      taxPercent: customerData.data[0].scpTva ? 19 : 0, // Apply VAT if customer is VAT registered
    },
  ],
});

// 4. Upload to ANAF
const uploadResult = await client.uploadDocument(tokens.access_token, xml);

// 5. Monitor status
const status = await client.getUploadStatus(tokens.access_token, uploadResult.index_incarcare);

// 6. Download result
if (status.id_descarcare) {
  const result = await client.downloadDocument(tokens.access_token, status.id_descarcare);
}

Development & Testing

Prerequisites

  1. USB Security Token: Required for ANAF authentication

    • Supported tokens: Any qualified certificate from Romanian CA
    • Install manufacturer drivers (SafeNet, Gemalto, etc.)
    • Certificate must be registered with ANAF SPV
  2. ANAF OAuth Application: Register at ANAF Portal

    • Navigate: Servicii Online → Înregistrare utilizatori → DEZVOLTATORI APLICAȚII
    • Register application with your callback URL

Environment Setup

Create a .env file in your project root:

ANAF_CLIENT_ID=your_oauth_client_id_here
ANAF_CLIENT_SECRET=your_oauth_client_secret_here

Local Development with ngrok

For local testing, you need a public HTTPS URL for OAuth callbacks:

  1. Install ngrok:

    # Using npm
    npm install -g ngrok
    
    # Or download from https://ngrok.com/
  2. Expose local server:

    # Start your local server on port 3000
    npm start
    
    # In another terminal, expose it publicly
    ngrok http 3000
  3. Update OAuth Settings:

    • Copy the ngrok HTTPS URL (e.g., https://abc123.ngrok.io)
    • Register callback URL: https://abc123.ngrok.io/oauth/callback
    • Update your AnafAuthenticator configuration:
    const auth = new AnafAuthenticator({
      clientId: process.env.ANAF_CLIENT_ID,
      clientSecret: process.env.ANAF_CLIENT_SECRET,
      redirectUri: 'https://abc123.ngrok.io/oauth/callback', // Your ngrok URL
    });

OAuth Authentication Flow

The complete OAuth flow with USB token authentication:

  1. Generate Authorization URL:

    const authUrl = auth.getAuthorizationUrl({
      state: { companyId: 123, foo: 'bar' }, // optional
    });
    console.log('Direct user to:', authUrl);

    State objects are JSON-serialized and base64-encoded automatically; use decodeOAuthState in your callback to validate them.

  2. User Authentication Process:

    • User clicks/visits the authorization URL
    • ANAF login page opens
    • Insert USB Token: User inserts USB security token
    • Enter PIN: User enters token PIN when prompted
    • Certificate Selection: Browser shows certificate selection dialog
    • Select Certificate: User selects appropriate certificate
    • Authorize Application: User grants permissions to your app
    • Redirect: Browser redirects to your callback URL with authorization code
  3. Handle Callback:

    // Your callback endpoint receives: ?code=AUTH_CODE&state=STATE
    app.get('/oauth/callback', async (req, res) => {
      const { code, state } = req.query;
    
      try {
        const tokens = await auth.exchangeCodeForToken(code);
    
        // Optionally validate decodedState.sessionId against your session store
        const decodedState = state ? decodeOAuthState(state) : undefined;
        // decodedState = { companyId: 123, foo: 'bar' }
    
        // Store tokens securely
        res.send('Authentication successful!');
      } catch (error) {
        res.status(400).send('Authentication failed');
      }
    });
  4. Use Access Token:

    // Token is valid for 1 hour
    const client = new AnafClient({ vatNumber: 'RO12345678' });
    const result = await client.uploadDocument(tokens.access_token, xmlContent);
  5. Refresh Tokens:

    // Refresh before expiration
    const newTokens = await auth.refreshAccessToken(tokens.refresh_token);

Automated Testing

The SDK includes comprehensive Jest tests with an integrated OAuth flow:

# Run all tests
yarn test

# Run OAuth authentication tests with callback server
yarn test:auth

# Run tests with coverage
yarn test:coverage

Manual OAuth Testing

The test suite includes a helpful OAuth testing flow:

  1. Start Test:

    yarn test:auth
  2. Callback Server: Automatically starts on http://localhost:4040

  3. Get OAuth URL: Test displays authorization URL in console

  4. Complete OAuth:

    • Copy URL to browser
    • Insert USB token when prompted
    • Enter PIN and select certificate
    • Authorize application
    • Browser redirects to localhost:4040/callback
  5. Automatic Token Handling: Test captures code and exchanges for tokens

Testing Environment

  • Test Environment: All tests use ANAF test environment
  • OAuth Endpoints: logincert.anaf.ro
  • API Endpoints: api.anaf.ro/test
  • Callback URL: http://localhost:4040/callback (for tests)

Token Management

  • Tokens are automatically saved to token.secret during tests
  • Access tokens expire in 1 hour
  • Refresh tokens have longer validity
  • Tests automatically refresh expired tokens
  • Invalid tokens are cleaned up automatically

Troubleshooting

USB Token Issues

❌ Certificate selection failed

Solutions:

  • Ensure USB token is properly inserted
  • Install manufacturer drivers
  • Try different browsers (Chrome recommended)
  • Check certificate validity in browser settings

OAuth Callback Issues

❌ Redirect URI mismatch

Solutions:

  • Verify callback URL matches registered URL exactly
  • Include protocol (https://) and path
  • For ngrok: use HTTPS URL, not HTTP
  • Check for trailing slashes

Network Issues

❌ Connection refused or timeout

Solutions:

  • Check internet connection
  • Verify firewall settings
  • For ngrok: ensure tunnel is active
  • Try different ngrok region: ngrok http 3000 --region eu

Token Expiration

❌ Access token expired

Solutions:

  • Use refresh token to get new access token
  • Implement automatic token refresh in your app
  • Store token expiration time and refresh proactively

API Coverage

The SDK implements all endpoints from the ANAF e-Factura OpenAPI specification:

Authentication

  • ✅ OAuth 2.0 authorization flow
  • ✅ Token exchange and refresh

Document Operations

  • ✅ Upload documents (/upload, /uploadb2c)
  • ✅ Check upload status (/stareMesaj)
  • ✅ Download processed documents (/descarcare)

Message Management

  • ✅ List messages with pagination (/listaMesajePaginatieFactura)
  • ✅ List recent messages (/listaMesajeFactura)

Validation & Conversion

  • ✅ XML validation (/validare/{standard})
  • ✅ Digital signature validation (/api/validate/signature)
  • ✅ XML to PDF conversion (/transformare/{standard})
  • ✅ XML to PDF without validation (/transformare/{standard}/DA)
  • ℹ️ XML validation is only available on ANAF production endpoints; the SDK uses the production URL even when testMode is enabled

ANAF serves /validare/{standard} only in production. The SDK issues a POST with Content-Type: text/plain to https://api.anaf.ro/prod/FCTEL/rest/validare/{FACT1|FCN} (or your custom base path for proxies). There is no test endpoint for validation.

UBL Generation

  • ✅ UBL 2.1 compliant XML generation
  • ✅ Romanian CIUS-RO specification support

Company Data Lookup

  • ✅ Fetch company data by VAT code
  • ✅ Validate VAT code format
  • ✅ Batch fetch multiple companies
  • ✅ Cache management

Environment Configuration

The SDK supports both test and production environments:

// Test environment (recommended for development)
const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: true,
});

// Production environment
const client = new AnafClient({
  vatNumber: 'RO12345678',
  testMode: false,
});

Error Handling

The SDK provides specific error types for different scenarios:

import { AnafAuthenticationError, AnafValidationError, AnafApiError } from 'efactura-sdk';

try {
  await client.uploadDocument(token, xml);
} catch (error) {
  if (error instanceof AnafAuthenticationError) {
    // Handle authentication issues - refresh token or re-authenticate
    console.log('Authentication failed:', error.message);
  } else if (error instanceof AnafValidationError) {
    // Handle validation errors - fix XML or parameters
    console.log('Validation error:', error.message);
  } else if (error instanceof AnafApiError) {
    // Handle API errors - check status, retry, or contact support
    console.log('API error:', error.message);
  }
}

TypeScript Support

The SDK is written in TypeScript and provides comprehensive type definitions:

import type { InvoiceInput, UploadStatus, ListMessagesResponse, ValidationResult, OAuthTokens } from 'efactura-sdk';

Security Best Practices

  • Never commit tokens: Add token.secret and .env to .gitignore
  • Use HTTPS: Always use HTTPS for OAuth callbacks in production
  • Validate certificates: Ensure USB token certificates are valid and not expired
  • Secure token storage: Store tokens securely (encrypted, database, secure storage)
  • Implement refresh: Automatically refresh tokens before expiration
  • Test environment: Use test mode for development and staging

Production Deployment

When deploying to production:

  1. Register Production OAuth App:

    • Use your production domain for callback URL
    • Get separate client credentials for production
  2. Environment Configuration:

    const client = new AnafClient({
      vatNumber: 'RO12345678',
      testMode: false, // Production mode
    });
  3. Secure Callback Handling:

    • Use HTTPS for all OAuth callbacks
    • Validate state parameter
    • Implement CSRF protection
    • Log authentication events
  4. Token Management:

    • Store tokens securely (encrypted database)
    • Implement automatic refresh
    • Handle refresh token expiration gracefully
    • Monitor token usage and expiration

License

MIT License - see LICENSE file for details.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests
  5. Run tests and linting
  6. Submit a pull request

Support

For questions about:


Perfect for: SaaS applications, accounting software, ERP integrations, invoicing systems

Requirements: USB security token, ANAF OAuth registration, Node.js 16+