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

@ironfang/finance

v2.1.0

Published

Ironfang Finance API validation client: Peppol UBL, XRechnung and ZUGFeRD / Factur-X, as XML or PDF

Readme

Ironfang Finance TypeScript client

@ironfang/finance, Node.js 20+ ESM, no runtime dependencies.

npm install @ironfang/finance

Formerly @ironfang/financewolf; 2.0.0 removed the old Financewolf and FinancewolfError exports. 2.1.0 follows Ironfang's unified billing: V1 errors carry the API's problem code too, billing refusals add product, meter, resetAt and retryAfter. A V2 verdict's usage carries remaining and period_ends_at only on results recorded before usage billing.

import { readFile } from 'node:fs/promises';
import { IronfangFinance } from '@ironfang/finance';

const client = new IronfangFinance({ apiKey: process.env.IRONFANG_API_KEY ?? '' });
const result = await client.validate(await readFile('invoice.xml'), {
  ruleset: 'fwrs_bis3_billing_invoice_2026_5_r5',
});
console.log(result.outcome);

Use an Ironfang key scoped to finance:einvoices:write. Keep it in a secret manager/environment variable; this is a server-side Node client, not a browser integration. Optional timeoutMs defaults to 30 seconds; signal supports cancellation. A timeout/cancel does not cancel work already accepted by the API.

The SDK snapshots the exact bytes before sending them, checks the returned hash and length, verifies completed validation layers and rejects a different pinned ruleset. PHIVE remains the validator: the SDK does not implement invoice rules, correct XML or reinterpret warnings. valid and invalid return structured results. Other failures throw IronfangFinanceError with safe code/HTTP status and, when the API answered with a problem, its problem code and requestId.

Pin an immutable document-type-specific ruleset for CI, or deliberately use latest (the default) for current active rules. CreditNote uses its own ruleset, for example fwrs_bis3_billing_creditnote_2026_5_r5. Retired/unavailable rulesets produce service errors, never a silently substituted version.

There is one HTTP attempt, no idempotency key and no automatic retry. Repeated calls can be billable. Redirects are refused, XML is capped at 5 MiB, and the streamed response at 4 MiB. The optional injected fetch is a trusted transport and receives the credential; use it only for tests or a reviewed integration.

V2: XRechnung and ZUGFeRD / Factur-X, XML or PDF

The *V2 methods use the V2 API, which validates Peppol BIS Billing 3, XRechnung 3.0.2 (UBL or CII) and ZUGFeRD 2.5.2 / Factur-X 1.09.2 in its five profiles, as XML or as a PDF with its embedded invoice XML. The V1 methods are unchanged.

const verdict = await client.validateV2(await readFile('invoice.pdf'));
console.log(verdict.outcome, verdict.ruleset.id);
for (const group of verdict.groups) console.log(group.group, group.status); // pdfa, attachment_metadata, invoice_xml
for (const finding of verdict.findings) console.log(finding.rule_id, finding.location.kind); // xpath, line-column, pdf or none
  • validateV2(document, { mediaType, ruleset, family, variant, documentType, scope, idempotencyKey, signal }) sends the exact bytes once. A PDF is recognised from its header, or pass mediaType. Without family, V2 detects it from the document's own declaration. XML is at most 5 MiB, a PDF 20 MiB. Unless you set timeoutMs, a PDF gets 45 seconds (the server's hybrid deadline is 30).
  • The verdict is typed (V2ValidationResult) and checked like V1's: hash, length and media type of the bytes sent, the scope they imply (xml or hybrid_pdf), the selection asked for, both engines for a PDF, and layers consistent with the outcome. coverage.not_checked states what it does not establish, such as the visible PDF matching its XML.
  • A refusal or indeterminate answer throws IronfangFinanceError with problem set to the API's code (for example family_mismatch, no_embedded_invoice or validation_timeout) and requestId for support. The problem's other text is never kept.
  • idempotencyKey (8-128 of A-Z a-z 0-9 _ -) makes a retry after a lost response return the first result instead of a second operation.

Reads and durable work, V1 and V2 together where the API lists both: rulesetsV2, resultsV2, resultV2, deleteResultV2; submitJobV2, jobV2, jobsV2, cancelJobV2, waitForJobV2; submitBatchV2 (up to 100 { document, ...selectors }), batchV2, batchesV2, cancelBatchV2; deliveriesV2, deliveryV2, retryDeliveryV2 (the retry needs finance:einvoices:destinations:manage). Reads need finance:einvoices:read.

const job = await client.submitJobV2(pdf, { idempotencyKey: 'invoice-2026-0042' });
const done = await client.waitForJobV2(job.id, { timeoutMs: 300_000 });
const stored = await client.resultV2(done.operation_id);

Usage and billing refusals

Validations count against your organisation's Ironfang billing account, shared by every Ironfang product and managed in Billing in the Ironfang portal. Each meter has a monthly free allowance; beyond it, usage is pay-as-you-go once paid usage is enabled. An authenticated V2 verdict's usage is { charged, credits }: charged is true, and credits 1, when the rules ran and the validation counted.

When the billing account refuses a validation, nothing is validated or counted and the error's problem is one of free_allowance_exhausted, account_budget_exhausted, product_budget_exhausted, exemption_limit_reached, paid_usage_paused, payment_required, payment_action_required, account_restricted, product_not_eligible, account_closed, operation_too_large or billing_temporarily_unavailable. The error then also carries product, meter and, for a free allowance, resetAt. Only billing_temporarily_unavailable clears by itself, after retryAfter seconds; the rest are resolved in Billing or when the month rolls over.

Full results can contain invoice text. Do not log/store them without appropriate access and retention. XML and PDFs are sent to Ironfang Finance under the authenticated API's retention policy. Validation does not send invoices through Peppol, establish Access Point status, certify tax/legal compliance or guarantee acceptance.