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

anaf-js

v0.1.2

Published

A comprehensive TypeScript library for the Romanian ANAF e-Factura system.

Readme

About the project

The library helps you integrate the Romanian ANAF e-Factura system into your application. From authentication to invoice generation and eFactura management, it provides a set of tools to make the process as simple as possible. We've tried to make it as complete and as easy to use as possible but also unopinionated.

Features

  • Invoice XML Generation - CIUS-RO compliant UBL 2.1 invoices
  • Company Info Lookup - Public ANAF API (no auth required)
  • e-Factura API - Upload, download, validate, messages
  • OAuth 2.0 Authentication - Full OAuth flow support
  • Type-Safe - Full TypeScript support

Installation

bun add anaf-js
# or
npm install anaf-js

Usage

How to integrate ANAF OAuth in your app

To integrate ANAF OAuth into your own application, you'll need to handle the redirect and callback steps manually.

import { AnafAuthenticator } from "anaf-js";

// 1. Initialize Authenticator
const auth = new AnafAuthenticator({
  clientId: process.env.ANAF_CLIENT_ID,
  clientSecret: process.env.ANAF_CLIENT_SECRET,
  redirectUri: "https://myapp.com/callback", // Must match the redirect URI in your ANAF account
});

// 2. Redirect User to ANAF Login
const url = auth.getAuthorizationUrl();
// Use this url to redirect the user to ANAF's login page
// He will be prompted to login with the usb stick

// 3. Handle Callback and Save Tokens
app.get("/callback", async (req, res) => {
  const code = req.query.code; // Get 'code' from query parameters

  try {
    // Exchange the authorization code for access tokens
    const tokens = await auth.exchangeCodeForToken(code);

    // Save tokens securely (e.g., in your database)
    await db.saveUserTokens(tokens);

    res.send("Authenticated successfully!");
  } catch (error) {
    console.error("Auth failed:", error);
    res.status(500).send("Authentication failed");
  }
});

Company Info Lookup (No Auth Required)

import { CompanyInfoClient } from "anaf-js";

const client = new CompanyInfoClient();

// Single company
const result = await client.getCompanyData("RO12345678");

if (result.success && result.data) {
  const company = result.data;
  console.log(company.generalData.companyName);
  console.log(company.hqAddress); // HQ address
  console.log(company.fiscalAddress); // Fiscal address
  console.log(company.vatRegistration); // VAT status
  console.log(company.generalData.eFacturaStatus); // e-Factura enrollment
}

// Batch lookup (max 100)
const batch = await client.batchGetCompanyData(["RO123", "RO456"]);

e-Factura Operations (Requires OAuth)

import { EfacturaClient, AnafAuthenticator, Invoice, loadCredentials } from "anaf-js";

// Create authenticator for automatic token refresh
const authenticator = new AnafAuthenticator({
  clientId: process.env.ANAF_CLIENT_ID,
  clientSecret: process.env.ANAF_CLIENT_SECRET,
  redirectUri: "https://myapp.com/callback",
});

// Create client - authenticator is required for automatic token refresh
const client = new EfacturaClient({
  vatNumber: "RO12345678",
  // Recommended to set to true in development
  testMode: true,
  // You should get these credentials from your database after the auth flow
  accessToken: accessToken,
  refreshToken: refreshToken,
}, authenticator);

// Generate and upload invoice
const xml = Invoice.buildXml({ ... });
const upload = await client.uploadDocument(xml);

// Check status
const status = await client.getStatusMessage(upload.uploadIndex);

// Download result
const file = await client.downloadDocument(status.downloadId);

// List messages
const messages = await client.getMessages({ days: 7 });

// Validate XML (no auth required)
const validation = await client.validateXml(xml);

// Convert to PDF
const pdf = await client.xmlToPdf(xml);

Invoice XML Generation

import { Invoice } from "anaf-js";

const xml: string = Invoice.buildXml({
  // ═══════════════════════════════════════════════════════════════════════════
  // REQUIRED - TypeScript will error if you forget these
  // ═══════════════════════════════════════════════════════════════════════════

  invoiceNumber: "2024-001",
  issueDate: new Date(),

  seller: {
    registrationName: "Furnizor S.R.L.",
    registrationCode: "12345678",
    vatCode: "RO12345678",
    registrationNumber: "J40/123/2020",
    legalFormData: "Capital social: 200 LEI",
    address: {
      streetName: "Strada Exemplu 10",
      cityName: "Sector 1", // This gets sanitized to "SECTOR1" if București is the region
      postalZone: "010101", // Optional
      countrySubentity: "RO-B", // Works with Bucuresti also because it gets sanitized to "RO-B" automatically
    },
  },

  buyer: {
    registrationName: "Client S.A.",
    registrationCode: "87654321",
    vatCode: "RO87654321",
    address: {
      streetName: "Bulevardul Client 25",
      cityName: "Cluj-Napoca",
      postalZone: "400001", // Optional
      countrySubentity: "RO-CJ",
    },
  },

  lines: [
    {
      name: "Servicii consultanță",
      quantity: 10,
      unitCode: "HUR", // Optional: defaults to "EA"
      unitPrice: 150,
      vatPercent: 21, // Optional: uses defaultVatPercent if omitted
    },
    {
      name: "Licență software",
      quantity: 1,
      unitPrice: 500,
      vatPercent: 21,
    },
  ],

  // ═══════════════════════════════════════════════════════════════════════════
  // OPTIONAL - Omit any you don't need
  // ═══════════════════════════════════════════════════════════════════════════

  invoiceSeries: "ABC", // Invoice prefix
  dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
  defaultVatPercent: 21, // Default VAT for lines without vatPercent
  paymentIban: "RO49AAAA1B31007593840000",
  note: "Mulțumim pentru colaborare!",
  invoiceTypeCode: "380",             // Default: Commercial Invoice
  currencyCode: "RON",                // Default: RON
  buyerReference: "PO-2024-001",
  taxPointDate: new Date(),
  orderReference: { id: "PO-001" },
  contractReference: { id: "CONTRACT-001" },
  invoicePeriod: { startDate: "2024-01-01", endDate: "2024-01-31" },
  paymentTerms: { note: "Payment within 30 days" },
  paymentMeans: { ... },              // Full payment config (alternative to paymentIban)
  allowanceCharges: [...],            // Document-level discounts/surcharges
  precedingInvoiceReferences: [...],  // Required for credit notes
});

API

Invoice.buildXml(config: InvoiceConfig)

Returns the UBL 2.1 XML string directly.

Invoice Types

| Code | Description | | ----- | --------------------------------- | | 380 | Commercial Invoice (default) | | 381 | Credit Note | | 384 | Corrected Invoice | | 389 | Self-billed Invoice (Autofactură) | | 751 | Invoice for accounting purposes |

Tax Categories

| Code | Description | | ---- | ----------------------------- | | S | Standard rate (e.g., 21% VAT) | | Z | Zero rated | | E | Exempt from VAT | | AE | VAT Reverse Charge | | K | Intra-community (EU export) | | G | Free export | | O | Not subject to VAT |

Credit Notes

const creditNote = Invoice.buildXml({
  invoiceNumber: "CN-001",
  issueDate: new Date(),
  invoiceTypeCode: "381",

  seller: { ... },
  buyer: { ... },

  // Link to original invoice
  precedingInvoiceReferences: [{
    id: "INV-2024-001",
    issueDate: new Date("2024-01-01"),
  }],

  lines: [{
    name: "Returned product",
    quantity: -1,  // Negative for credits
    unitPrice: 500,
    vatPercent: 21,
  }],
});

Non-VAT Payer Invoices

const invoice = Invoice.buildXml({
  invoiceNumber: "PFA-001",
  issueDate: new Date(),

  seller: {
    registrationName: "Freelancer PFA",
    registrationCode: "12345678",
    vatCode: null, // ← Not VAT registered
    address: { ... },
  },

  buyer: { ... },

  lines: [{
    name: "Consulting services",
    quantity: 1,
    unitPrice: 2000,
    // VAT automatically set to 0 with category 'O'
  }],
});

Allowances & Charges

const invoice = Invoice.buildXml({
  invoiceNumber: "INV-001",
  issueDate: new Date(),
  seller: { ... },
  buyer: { ... },
  lines: [{ ... }],

  allowanceCharges: [
    // Document-level discount
    {
      chargeIndicator: false, // false = discount
      reason: "10% loyalty discount",
      reasonCode: "95",
      amount: 100,
      taxCategoryCode: "S",
      vatPercent: 21,
    },
    // Document-level surcharge
    {
      chargeIndicator: true, // true = surcharge
      reason: "Shipping",
      reasonCode: "FC",
      amount: 50,
      taxCategoryCode: "S",
      vatPercent: 21,
    },
  ],
});

Utility Functions

import {
  formatDate,
  normalizeVatNumber,
  sanitizeCounty,
  sanitizeCity,
} from "anaf-js";

formatDate(new Date()); // "2024-01-15"
normalizeVatNumber("12345678"); // "RO12345678"
sanitizeCounty("Cluj"); // "RO-CJ"
sanitizeCity("Sector 1"); // "SECTOR1"

Available Constants

import {
  InvoiceTypeCodes,
  TaxCategoryCodes,
  PaymentMeansCodes,
  CommonUnitCodes,
  RomanianCountyCodes,
} from "anaf-js";

Local Testing Helper

For quick local testing or CLI tools, the library exports an optional internal server runOAuthFlow (which uses Bun's HTTP server) to handle the callback for you automatically.

When testing locally, the internal OAuth server listens on localhost:3000 by default. However, ANAF requires a public HTTPS URL for redirects.

You do not need to change the local server configuration. Instead:

  1. Use a tool like ngrok to forward traffic: ngrok http 3000.
  2. Set the ANAF_REDIRECT_URI in your .env (and in the ANAF portal) to your ngrok URL (e.g., https://xxxx.ngrok-free.app/callback).
  3. The internal server will automatically handle the callback on localhost:3000.

Inspiration

This project takes inspiration from the following open-source projects:

License

MIT