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

@open-nav/client

v0.3.0

Published

API client for the Hungarian Tax Authority (NAV) Online Számla 3.0 invoice service

Readme

@open-nav/client

Client for all ten operations of NAV's Online Számla invoice service (interface 3.0), plus the transaction polling and bulk download that using it actually requires.

npm install @open-nav/client

The client

import { NavClient, waitForTransaction } from '@open-nav/client';

const client = new NavClient({
  environment: 'test', // or 'production'
  credentials: {
    login: process.env.NAV_LOGIN!,
    password: process.env.NAV_PASSWORD!,
    signKey: process.env.NAV_SIGN_KEY!,
    exchangeKey: process.env.NAV_EXCHANGE_KEY!,
    taxNumber: '12345678', // the 8 digit core, not the 11 digit number
  },
  software: {
    softwareId: 'MYCOMPANY000000001',
    softwareName: 'my invoicing app',
    softwareOperation: 'LOCAL_SOFTWARE',
    softwareMainVersion: '1.0.0',
    softwareDevName: 'My Company',
    softwareDevContact: '[email protected]',
  },
});

Each method takes its generated request type minus the header, user and software blocks, which the client builds and signs itself. There is no hand-maintained parameter list to fall out of step with the schema.

tokenExchange, manageInvoice, manageAnnulment, queryTransactionStatus, queryTransactionList, queryInvoiceData, queryInvoiceDigest, queryInvoiceChainDigest, queryInvoiceCheck and queryTaxpayer are all there, one method each, named as NAV names them.

Submitting

const { transactionId } = await client.submitInvoices([{ operation: 'CREATE', invoice }]);

const outcome = await waitForTransaction(client, transactionId);
console.log(outcome.accepted.length, 'stored;', outcome.rejected.length, 'rejected');

submitInvoices is the reason this package is worth using over a hand-rolled request. It exchanges a token, encodes each invoice once and both sends and hashes that same base64 — hashing a separately serialised copy is a signature failure waiting to happen — and it never retries a submission that reached NAV, because the retry would be a duplicate invoice report, not a repeated read.

manageInvoice returns a transaction id, not a verdict. The verdict arrives later, per invoice, split across technical and business validation messages, and waitForTransaction polls the lifecycle (RECEIVED → PROCESSING → SAVED → DONE/ABORTED) and sorts the outcome into accepted and rejected invoices with their messages attached.

submitAnnulments does the same for technical annulment.

Downloading invoices

The queries have a 35-day cap per request and page at 100 results, which makes "give me last year's incoming invoices" a loop rather than a call. That loop is here:

import { downloadInvoices, iterateInvoiceDigests } from '@open-nav/client';

const invoices = await downloadInvoices(client, {
  direction: 'INBOUND',
  dateFrom: '2025-01-01',
  dateTo: '2025-12-31',
});

// or stream, if a year of invoices should not be held in memory at once
for await (const digest of iterateInvoiceDigests(client, {
  direction: 'OUTBOUND',
  dateFrom,
  dateTo,
})) {
  // ...
}

chunkDateRange splits the range into legal windows, pagination is followed to the end, and supplierTaxNumber is sent only for INBOUND — sending it on an outbound query is BAD_QUERY_PARAM_SUPPLIER_NOT_EXPECTED, and omitting it on an inbound one returns nothing.

Transport

postXml is the layer underneath, and it is deliberately conservative:

  • 429 and 503 are retried, honouring Retry-After (capped at 60s). Everything else in the 4xx range is fatal — a retry would not change the answer.
  • Submissions are never retried. Only reads are.
  • NAV's GeneralErrorResponse becomes a NavApiError carrying the fault code and NAV's own message, so INVALID_SIGNATURE reads as itself rather than as an HTTP 500.

Credentials never appear in a thrown error, a log line or a serialised request beyond the hash and signature NAV requires — there are tests that assert the password, sign key and exchange key never reach the wire.

Testing without credentials

@open-nav/mock-server is a local stand-in for the service: real HTTP, its own independent signature verification, and the same error codes. This client is driven end to end against it in CI — token exchange, batch submission, polling and every query.

Licence

MIT. Not affiliated with NAV.