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

@financica/myminfin

v0.13.0

Published

TypeScript client and document generators for the Belgian SPF Finances MyMinFin and Intervat APIs.

Readme

@financica/myminfin

TypeScript client for the Belgian SPF Finances MyMinFin and Intervat APIs.

Provides typed access to:

  • MyMinFin API — Search and download documents from MyDoc / MyDocPro
  • Intervat API — Submit VAT returns via XML
  • OIDC Authentication — OAuth2 + PKCE with JWT client authentication

Installation

npm install @financica/myminfin

Requires Node.js 24+ (uses native fetch and crypto).

Authentication

All API calls require an OAuth2 access token obtained through the SPF Finances OIDC flow, which MyMinFinAuth implements.

Prerequisites

You need:

  • A client ID from SPF Finances registration
  • An RS256 private key (PEM format) whose public key is published at your JWKS URL
  • The key ID (kid) matching your public key
  • A registered redirect URI

Step 1: Build the authorization URL

import { MyMinFinAuth } from "@financica/myminfin";

const auth = new MyMinFinAuth({
	clientId: "YourClientId",
	privateKey: process.env.PRIVATE_KEY!, // PEM string
	keyId: "your-key-id",
	redirectUri: "https://yourapp.com/callback",
	environment: "test", // or "production"
});

// Generate the URL to redirect the legal representative to
const { url, state, nonce, codeVerifier } = await auth.getAuthorizationUrl({
	ecb: "0123456789", // 10-digit enterprise number
	scopes: ["myminfin_docs_read"], // API-specific scopes
});

// Store state, nonce, codeVerifier in the user's session
// Redirect the user's browser to `url`

Step 2: Exchange the authorization code

// In your callback handler, after the user is redirected back:
const tokens = await auth.exchangeCode({
	code: callbackParams.code,
	codeVerifier: session.codeVerifier,
	redirectUri: "https://yourapp.com/callback",
});

// tokens.accessToken  — use for API calls
// tokens.refreshToken — store securely for token renewal
// tokens.idToken      — JWT with user identity claims
// tokens.expiresIn    — access token lifetime in seconds

Step 3: Refresh tokens

Access tokens expire after ~5 minutes. Refresh tokens last 7 days but are single-use.

const newTokens = await auth.refreshToken({
	refreshToken: storedRefreshToken,
});
// Use newTokens.accessToken for subsequent API calls
// Store newTokens.refreshToken (the old one is now invalid)

MyMinFin API — Document Search & Download

Search and download documents available on MyMinFin for companies you have mandates for.

import { MyMinFinClient } from "@financica/myminfin";

const client = new MyMinFinClient({
	accessToken: tokens.accessToken,
	environment: "test",
});

Search documents

// Search all documents for the connected company + mandated entities
const result = await client.searchDocuments({
	since: "2024-10-01",
});

for (const doc of result.documents) {
	console.log(doc.uuid, doc.type, doc.title, doc.publishDate);
}
// Search only for a specific company
const result = await client.searchDocuments({
	since: "2024-10-01",
	ownerType: "CBE",
	ownerIdentifier: "0662348959",
});

Download a document

import { writeFileSync } from "fs";

// Download a document owned by the connected company
const { content, contentType } = await client.downloadDocument(
	"662c6014-9f62-4956-acdc-0e25a233107d",
);

writeFileSync("document.pdf", Buffer.from(content));
// Download a document owned by a mandated entity
const { content } = await client.downloadDocument(
	"63c407ac-f56f-4b28-b36b-4e1336d6be89",
	{
		ownerType: "SSIN",
		ownerIdentifier: "01520605978",
	},
);

Intervat API — Declaration Submission

Submit a VAT return (or any other declaration Intervat takes) as XML conforming to its XSD. Intervat only accepts a ZIP holding the XML; the client zips it for you.

import { IntervatClient } from "@financica/myminfin";
import { readFileSync } from "fs";

const intervat = new IntervatClient({
	accessToken: tokens.accessToken,
	environment: "test",
});

// Submit XML string
const xml = readFileSync("vat-return.xml", "utf-8");
const result = await intervat.submitVatReturn("0806153934", xml);
// The proof of submission, published on MyMinFin the next day
console.log(result.pdfReference, result.xmlReference);

// Another declaration type, e.g. the annual client listing
const listing = await intervat.submitDeclaration("lc", "0806153934", listingXml);

// A ready-made ZIP, for a declaration with annexes
const archive = readFileSync("vat-return.zip");
const zipResult = await intervat.submitDeclarationArchive("tva", "0806153934", archive);

Retrieve submission receipts

Submission receipts (PDF/XML) are not available through the Intervat API. Use the MyMinFin API to download them — note they are only available from the following day due to caching.

const receiptDocs = await client.searchDocuments({
	since: "2024-10-02", // day after submission
	ownerType: "CBE",
	ownerIdentifier: "0806153934",
});

Error Handling

Everything the clients throw is a MyMinFinError. Network failures and aborted requests are wrapped in a MyMinFinError with cause set to the original rejection; every non-OK HTTP response (including a non-JSON 5xx from the token endpoint) throws MyMinFinApiError extends MyMinFinError carrying status and the RFC 7807 problem detail on problem (aliased as details):

import { MyMinFinApiError } from "@financica/myminfin";

try {
	await client.searchDocuments({ since: "2020-01-01" });
} catch (e) {
	if (e instanceof MyMinFinApiError) {
		console.error(e.status); // 400
		console.error(e.message); // "Search filtering invalid"
		console.error(e.problem?.detail); // Full problem detail
		console.error(e.problem?.instance); // Tracking UUID for support
	}
}

Each request method accepts an optional { signal } to abort it, and every config accepts a fetch override (defaulting to globalThis.fetch) for testing.

For Intervat business rule errors, the problem includes businessrules with multilingual descriptions:

try {
	await intervat.submitVatReturn(vatNumber, xml);
} catch (e) {
	if (e instanceof MyMinFinApiError && e.problem && "businessrules" in e.problem) {
		for (const rule of e.problem.businessrules) {
			console.error(rule.errorIdentifier, rule.descriptions.en);
		}
	}
}

Rate Limiting

The MyMinFin API enforces rate limits per company (CBE number):

  • Search (/documents): 1 request per 10 minutes per company
  • Download: 12 downloads per minute per company (production)

When rate-limited, the API returns HTTP 429 with a Retry-After header.

Environments

| Environment | API base | OIDC base | | ------------ | ------------------------------ | ---------------------------------- | | test | https://wsapi-a.minfin.be | https://fediamapi-a.minfin.be | | production | https://wsapi.minfin.fgov.be | https://fediamapi.minfin.fgov.be |

Endpoint helpers

All endpoint URLs are also exported as functions:

import {
	apiBase,
	oidcBase,
	authorizeUrl,
	tokenUrl,
	jwksUrl,
	discoveryUrl,
	issuerUrl,
	myminfinDocumentsUrl,
	intervatDeclarationUrl,
} from "@financica/myminfin";

console.log(discoveryUrl("test"));
// https://fediamapi-a.minfin.be/sso/oauth2/.well-known/openid-configuration

API Reference

MyMinFinAuth

| Method | Parameters | Returns | | ----------------------------- | ------------------------------------- | ---------------------------------------------- | | getAuthorizationUrl(params) | { ecb, scopes? } | Promise<{ url, state, nonce, codeVerifier }> | | exchangeCode(params) | { code, codeVerifier, redirectUri } | Promise<TokenSet> | | refreshToken(params) | { refreshToken } | Promise<TokenSet> |

MyMinFinClient

| Method | Parameters | Returns | | --------------------------------- | ------------------------------------------------- | -------------------------------------------------------- | | searchDocuments(params) | { since, until?, ownerType?, ownerIdentifier? } | Promise<DocumentSearchResult> | | downloadDocument(uuid, params?) | UUID string, { ownerType?, ownerIdentifier? } | Promise<{ content: ArrayBuffer, contentType: string }> |

IntervatClient

| Method | Parameters | Returns | | ----------------------------------------------------------------- | ----------------------------------------- | ------------------------------ | | submitVatReturn(vatNumber, xml) | VAT number, XML string | Promise<VatSubmissionResult> | | submitDeclaration(declarationType, ownerIdentifier, xml) | DeclarationType, VAT number, XML string | Promise<VatSubmissionResult> | | submitDeclarationArchive(declarationType, ownerIdentifier, zip) | DeclarationType, VAT number, ZIP bytes | Promise<VatSubmissionResult> | | getOpenApiSpec() | — | Promise<string> |

TokenSet

| Field | Type | Description | | -------------- | -------- | ----------------------------- | | accessToken | string | Bearer token for API calls | | refreshToken | string | Single-use token for renewal | | idToken | string | JWT with user identity claims | | scope | string | Authorized scopes | | tokenType | string | Always "Bearer" | | expiresIn | number | Lifetime in seconds |

Document generators

Besides the API clients, the package generates the Intervat XML documents you submit. These are pure functions: no auth, no network.

Periodic VAT return

buildBelgianVatReturn maps semantic figures onto the Belgian grid and renders VATConsignment XML. The balance lands in box 71 (payable) or 72 (refundable), and a refund is requested automatically when in credit.

import { buildBelgianVatReturn } from "@financica/myminfin";

const { xml, grid, warnings } = buildBelgianVatReturn({
	declarant: { vatNumber: "BE0806.153.934", name: "Acme BV", countryCode: "BE" },
	period: { year: 2026, quarter: 2 },
	figures: {
		standardRatedSales: [{ rate: 21, base: 1000, vat: 210 }],
		purchaseBase: 400,
		deductibleVat: 84,
	},
});

The mapping covers domestic standard-rated sales (boxes 00/01/02/03 + 54), deductible domestic purchases (boxes 82 + 59), intra-community supplies and acquisitions, exports, domestic reverse charge and imports with postponed accounting (44/45/46/47, 86/87/88 + 55/56/57), credit-note corrections (48/49/84/85 with the VAT in 63/64) and miscellaneous regularisations (61/62). The goods/investment purchase split (81/83) and prepayments (91) are not modelled; warnings flags anything that could not be mapped. Use serializeVatReturn if you already have explicit grid box amounts, or computeBelgianVatGrid for just the grid.

Annual client listing

import { generateClientListingXml } from "@financica/myminfin";

const xml = generateClientListingXml({
	declarant: { vatNumber: "0806153934", name: "Acme BV", countryCode: "BE" },
	period: 2025,
	clients: [
		{
			vatNumber: "0766280697",
			countryCode: "BE",
			turnover: 10500,
			vatAmount: 2205,
		},
	],
});

Intra-community listing

Due for every period whose return fills grid 44, 46 or 48: one line per EU customer VAT number and code (L goods, S services, T triangular), with a signed amount in EUR (credit notes issued make it negative). The VAT number goes without its country prefix, and Greece is EL. A goods or triangular line can correct an earlier listing with correctingPeriod; a services correction is netted into the current period, as the schema allows no other. Output validates against SPF's NewICO-in_v0_9.xsd.

import { generateIntraListingXml } from "@financica/myminfin";

const xml = generateIntraListingXml({
	declarant: { vatNumber: "0806153934", name: "Acme BV", countryCode: "BE" },
	period: { year: 2026, quarter: 3 },
	clients: [
		{ vatNumber: "72879981983", countryCode: "FR", code: "S", amount: 10000 },
		{ vatNumber: "862488084B01", countryCode: "NL", code: "L", amount: 25000 },
	],
});

Reference material

  • convention.md — SPF Finances API usage convention (French, third-party text).
  • intervat-api.md — vendored Intervat API guide, version 09/07/2025.

Development

Standard scripts — see the repository README.

License

MIT