New:Microsoft Teams Notifications Are Now Available in Socket.Learn more
Get Started

ksef-invoice-validate

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ksef-invoice-validate

Zero-dependency validation for Polish invoices before KSeF submission: NIP checksum, timezone-correct dates, buyer-type detection and net + VAT = gross consistency.

latest
Source
npmnpm
Version
0.2.0
Version published
Maintainers
1
Created
Source

ksef-invoice-validate

Pre-submission checks for Polish invoices headed for KSeF (Krajowy System e-Faktur). Zero dependencies, no network calls, no file I/O. Everything runs locally, including in the browser.

The Ministry of Finance publishes SDKs for Java and .NET. This is a small piece of the same job for the TypeScript side: catching the mistakes that get an invoice rejected before it is worth talking to KSeF at all.

What it checks

  • NIP checksum - ten digits, weighted [6,5,7,2,3,4,5,6,7] modulo 11. Separators tolerated.
  • Dates - YYYY-MM-DD, real calendar dates only (2026-02-31 is rejected), future dates flagged against the Polish calendar rather than UTC.
  • Amounts - non-negative, and net + VAT equals gross. Compared in integer grosze, so 0.1 + 0.2 === 0.3 behaves the way an accountant expects.
  • Buyer type - whether a missing NIP is a consumer sale or a company you forgot to fill in.

Dates are Polish calendar dates

The obvious way to turn a timestamp into an invoice date is wrong:

new Date(unixSeconds * 1000).toISOString().slice(0, 10); // renders UTC

Poland runs UTC+2 in summer and UTC+1 in winter, so any instant between midnight and the offset falls on the previous UTC day. A billing platform that issues invoices at 00:00 Europe/Warsaw therefore dates every one of them a day early, and at the turn of a month that is the wrong VAT period, not a cosmetic slip.

import { polishDateFromUnix } from "ksef-invoice-validate";

polishDateFromUnix(1788213600); // "2026-09-01"  (00:00 in Warsaw)
// the naive version gives "2026-08-31"

The offset comes from the timezone database, so winter works too. validateDate uses the same rule, which means an invoice correctly dated today is no longer reported as being in the future when you run it just after midnight.

A missing buyer NIP is not always a mistake

FA(3) carries BrakID for a consumer, and a large share of a real Polish invoice book has no buyer NIP at all. It is wrong for a company, though, and filing a business sale as a consumer sale needs a correcting invoice afterwards. Nothing in the invoice data separates the two; the name sometimes does.

import { mayNeedBuyerNip } from "ksef-invoice-validate";

mayNeedBuyerNip({ buyer_name: "Bookinghost Sp. z o.o.", buyer_nip: null }); // true
mayNeedBuyerNip({ buyer_name: "Jan Kowalski",           buyer_nip: null }); // false

Use it to decide whether a missing NIP deserves a human look, never to reject an invoice. It deliberately ignores generic words like "Usługi" or "Group": measured against a 1,142-invoice production book it flagged 6 of 733 NIP-less invoices and every one was a genuine company. A sole trader is a business whose name is a person's name, so no pattern finds those.

What it does not do

It is not a substitute for validating against the official FA(3) XSD, and it cannot check anything that requires the Ministry's systems: duplicate detection, counterparty status, authorisation, or session handling. Think of it as the cheap check you run first.

Usage

import { validateInvoiceForKsef, isValidNip } from "ksef-invoice-validate";

isValidNip("111-111-11-11"); // true

const result = validateInvoiceForKsef({
  invoice_number: "FV/2026/07/1",
  issue_date: "2026-07-01",
  seller_nip: "1111111111",
  buyer_nip: "1111111111",
  amount_net: 1000,
  amount_vat: 230,
  amount_gross: 1230,
});

result.valid; // true

A failure returns stable codes you can switch on, rather than prose you have to parse:

validateInvoiceForKsef({ amount_net: 100, amount_vat: 23, amount_gross: 999 });
// {
//   valid: false,
//   errors: [
//     { field: "invoice_number", code: "field.required", message: "Invoice number is required." },
//     { field: "amount_gross",   code: "amount.mismatch", message: "Net plus VAT does not equal gross.",
//       details: { differencePln: "876.00" } },
//     ...
//   ]
// }

Buyer NIP is required by default, since the common case is a B2B invoice. Waive it for consumers and foreign buyers:

validateInvoiceForKsef(invoice, { requireBuyerNip: false });

API

ExportPurpose
validateInvoiceForKsef(invoice, options?)Runs every check, returns { valid, errors }
validateNip(nip, field?)NIP format and checksum
isValidNip(nip)Boolean convenience wrapper
validateDate(value, field, options?)Format, real-date and future checks
validateAmounts(net, vat, gross)Sign and net + VAT = gross
polishDate(date)Polish calendar date of an instant, YYYY-MM-DD
polishDateFromUnix(seconds)Same, from a unix timestamp in seconds
polishToday(now?)Today's date in Poland
looksLikeCompany(name)Buyer name carries a registered legal form
mayNeedBuyerNip(invoice)Company name, but no NIP supplied

Error codes: nip.format, nip.checksum, date.format, date.invalid, date.future, amount.negative, amount.mismatch, field.required.

Tests

node --test "test/**/*.test.ts"

Requires Node 22 or newer, which strips TypeScript types natively.

Origin

Extracted from the validation layer of FakturaFlow, a KSeF tool for Polish accounting offices, and released separately because NIP and invoice-arithmetic checks are useful well beyond it.

MIT licensed.

Keywords

ksef

FAQs

Package last updated on 05 Sep 2026

Related posts