@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/financeFormerly @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 nonevalidateV2(document, { mediaType, ruleset, family, variant, documentType, scope, idempotencyKey, signal })sends the exact bytes once. A PDF is recognised from its header, or passmediaType. Withoutfamily, V2 detects it from the document's own declaration. XML is at most 5 MiB, a PDF 20 MiB. Unless you settimeoutMs, 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 (xmlorhybrid_pdf), the selection asked for, both engines for a PDF, and layers consistent with the outcome.coverage.not_checkedstates what it does not establish, such as the visible PDF matching its XML. - A refusal or indeterminate answer throws
IronfangFinanceErrorwithproblemset to the API's code (for examplefamily_mismatch,no_embedded_invoiceorvalidation_timeout) andrequestIdfor support. The problem's other text is never kept. idempotencyKey(8-128 ofA-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.
