Poland — KSeF (Krajowy System e-Faktur)
Research doc for the Poland fiscalization adapter. Consumed by the implementation bead (TBD). No code in this bead.
Status: Research — no adapter code written yet.
Strategic context: Phase-1.5 Central European country added to parallel
research at user direction (see fi-awx). €0 cert cost (qualified electronic
signature OR portal-issued authorization token), REST API at the Ministry of
Finance, mandatory B2B from 2026 (with a phased rollout — see §1). ~2M VAT
taxpayers — the largest market in the Phase-1 cluster. Caveat: rollout has
been chaotic. The mandatory date has slipped multiple times (originally
2024-01-01, then 2024-07-01, then 2026-02-01, then split into a phased
2026-02 / 2026-04 timeline). All dates below are confirmed against
post-2025-09 Ministry of Finance communications. See
docs/strategy/thesis-api-wedge.md (fi-awx) and
apps/docs-internal/docs/ops/country-prioritization.md.
1. Regulatory scope
What must be reported
Poland's Krajowy System e-Faktur (KSeF — National e-Invoicing System), operated by the Ministerstwo Finansów (Ministry of Finance, MF), is a central platform through which structured electronic invoices are issued, received, archived, and made available to the tax authority. Legal basis is the Ustawa o podatku od towarów i usług (VAT Act) of 11 March 2004, articles 106nb–106ne as amended by the Ustawa o zmianie ustawy o podatku od towarów i usług oraz niektórych innych ustaw (Dz.U. 2023 poz. 1598, the so-called "KSeF Act") and subsequent amendments.
Mandatory-use timeline (verified post-2025-09)
| Effective date | Scope change |
|---|---|
| 2022-01-01 | KSeF available on a voluntary basis. Taxpayers may opt in to issue faktury ustrukturyzowane (structured invoices). |
| 2024-01-01 | Original mandatory-use date — deferred (announced 2023-04). |
| 2024-07-01 | Second mandatory-use date — deferred in 2024-01 after MF audit found the system "not ready for compulsory use". |
| 2026-02-01 → 2026-04-01 | Current phased rollout (announced by MF on 2025-09; codified in the amending Act signed 2025-11): |
| 2026-02-01 | Mandatory for "large taxpayers" (sales value incl. VAT > PLN 200 million in 2024). Approx. 4,500 entities. |
| 2026-04-01 | Mandatory for all other VAT-active taxpayers issuing invoices. Approx. 2 million entities. Foreign VAT-registered entities without a fixed establishment (stałe miejsce prowadzenia działalności) in Poland remain outside the mandate but may opt in. |
| 2026-04-01 | Mandatory to capture paragony z NIP (cash-register receipts ≤ PLN 450 with the buyer's NIP that are treated as simplified invoices) outside KSeF — these continue to flow through the cash-register regime, not KSeF. |
| 2027-01-01 | Mandatory inclusion of B2C invoices that the customer requests in invoice form — provisional, may slip again. B2C receipts (paragony) remain outside KSeF indefinitely. |
Source for the phased dates: Ministry of Finance press release
2025-09-26 ("KSeF — kalendarz wdrożenia"), the amending Act published in
Dziennik Ustaw 2025 (sygn. legislative track UD125), and the MF FAQ at
gov.pl/web/kas/krajowy-system-e-faktur (last verified 2026-04-26).
Out of scope
- B2C consumer invoices and receipts (paragony fiskalne) — handled by the separate kasy fiskalne (fiscal cash register) regime; not KSeF. Our Poland adapter targets KSeF only. A merchant who issues only paragony for B2C and never issues invoices is out of scope for this adapter.
- Invoices issued by foreign entities not VAT-registered in Poland — out of scope.
- Invoices where the seller is a VAT-exempt small business (podatnik zwolniony, art. 113 VAT Act) — exempt from the mandate but may opt in.
- Simplified invoices issued through a fiscal cash register (paragon ≤ PLN 450 with the buyer's NIP) — these are the "fiskalne uproszczone" invoices and remain in the cash-register universe.
- Documents that are not invoices — proforma, dispatch notes, demand letters, etc. KSeF accepts only the structured-invoice document type (faktura ustrukturyzowana).
Invoice categories handled by KSeF
| Type | Polish term | Notes |
|---|---|---|
| Standard invoice | Faktura | Default. Issued for taxable supplies. |
| Credit note | Faktura korygująca | References the original numerKSeF (or numerKSeF-less issuer number for pre-KSeF originals). Adjusts net/VAT/quantity. |
| Advance-payment invoice | Faktura zaliczkowa | Issued before the supply for an advance payment. |
| Final settlement | Faktura końcowa / rozliczeniowa | Settles the supply against earlier zaliczka(s). |
| Self-billing | Samofakturowanie | Issued by the buyer on behalf of the seller. Requires prior agreement and authorisation in KSeF. |
| Reverse-charge | Faktura z odwrotnym obciążeniem | VAT shifted to recipient. Marked with P_18 (reverse-charge indicator) and a 0% / "oo" rate. |
| Intra-community supply | WDT (wewnątrzwspólnotowa dostawa towarów) | EU customer with valid VAT-UE number. Marked with P_18A. |
| Export | Faktura eksportowa | Non-EU customer. |
| Marketplace / platform | Faktura w procedurze unijnej / OSS / IOSS | OSS/IOSS flagged via Adnotacje. |
2. API surface
Base URLs
| Environment | API base | Web portal |
|---|---|---|
| Test (synthetic NIPs) | https://ksef-test.mf.gov.pl/api/ | https://ksef-test.mf.gov.pl/web/login |
| Demo (real NIPs, no fiscal effect) | https://ksef-demo.mf.gov.pl/api/ | https://ksef-demo.mf.gov.pl/ |
| Production | https://ksef.mf.gov.pl/api/ | https://ksef.mf.gov.pl/ |
Cited in the MF KSeF technical documentation page (gov.pl/web/kas/krajowy-system-e-faktur,
"Specyfikacja interfejsów API"). Test, demo, and production are fully
isolated taxpayer universes; an authorization token issued in test cannot
be used in demo or production.
API version
Current production: KSeF 1.0, schema FA(2) (namespace
http://crd.gov.pl/wzor/2023/06/29/12648/, schema file
Schemat_FA(2)_v1-0E.xsd, valid since 2023-09-01).
KSeF 2.0, schema FA(3), has been published in beta:
| Version | Schema | Status | Production date |
|---|---|---|---|
| KSeF 1.0 | FA(2) v1.0E | Live in production; only XSD currently accepted by the production endpoint. | Live since 2022-01-01. Will remain accepted at least through 2026-09-30 per MF roadmap. |
| KSeF 2.0 | FA(3) | Sandbox (test / demo) only. Public consultation ran 2024-12 / 2025-02; final XSD published 2025-06-13. | Mandated alongside the 2026-04-01 rollout; FA(2) acceptance ends 2026-10-01 (per the amending Act). |
Adapter strategy: ship FA(2) first (the only schema accepted in
production at the 2026-02-01 cutover), with FA(3) on a feature flag and on
the same dispatch path. The two schemas are not wire-compatible (different
namespace, restructured Fa body, additional Adnotacje codes); we'll
gate per the Location.country_config.schema_version setting.
Endpoints (KSeF 1.0, FA(2))
All endpoints share Content-Type: application/xml for write operations
(except /Session/InitSigned which is XAdES-enveloped XML) and
application/json is also accepted on most read endpoints when the
Accept: application/json header is sent. All paths are relative to the
base URL and rooted under /online/ for interactive flows or /batch/ for
the batch (async-archive) flow.
| Path | Method | Purpose | Auth |
|---|---|---|---|
/online/Session/AuthorisationChallenge | POST | Initiate a session — server returns a challenge (timestamp + nonce). | none (anonymous) |
/online/Session/InitToken | POST | Open a session using an authorization token (system-to-system flow). Body wraps the encrypted token. | challenge + encrypted token |
/online/Session/InitSigned | POST | Open a session using a XAdES-BES enveloped qualified signature or qualified seal. | challenge + XAdES |
/online/Session/Status | GET | Session status + count of submitted invoices in this session. | session token |
/online/Session/Terminate | POST | Close the session (best-effort; sessions also expire after 2 h or 100 MB of traffic). | session token |
/online/Invoice/Send | POST | Submit one FA(2) invoice. Returns synchronous referenceNumber (submission ID) + processingCode. | session token |
/online/Invoice/Status/{referenceNumber} | GET | Per-submission status; returns the numerKSeF and embedded UPO once async validation completes. | session token |
/online/Invoice/Get/{numerKSeF} | GET | Retrieve a previously accepted invoice (XML) by its KSeF number. | session token |
/online/Common/Status/{referenceNumber} | GET | Generic status endpoint for any submission reference (invoice, query, batch). | session token |
/online/Query/Invoice/Sync | POST | Synchronous lookup; returns up to 100 invoices matching filter (date range, NIP, type). | session token |
/online/Query/Invoice/Async/Init | POST | Initialise an async query covering up to 2-year ranges. | session token |
/online/Query/Invoice/Async/Status/{ref} | GET | Poll async query progress; download URL when ready. | session token |
/batch/Invoice/Send | POST | Submit a ZIP archive of up to 10,000 invoices in one batch. | session token (batch session) |
/batch/Invoice/Status/{ref} | GET | Per-batch status. | session token |
Rate limiting: MF publishes a soft limit of 1 invoice/second sustained
on /online/Invoice/Send per session, with bursts up to 100/min tolerated.
The batch endpoint is the recommended path for >10 invoices/minute. Hard
quotas: max 2-hour session lifetime, max 100 MB of payload per session.
Adapter hot path: AuthorisationChallenge → InitToken →
Invoice/Send → (poll) Invoice/Status to retrieve UPO.
3. Authentication
KSeF supports four authentication modes. Our adapter targets token mode as the primary path, with InitSigned (XAdES qualified signature/seal) as a secondary path for customers who do not want to provision a portal token.
Mode 1 — Authorization token (recommended, system-to-system)
One-time onboarding (out-of-band, performed by the merchant once):
- Merchant logs into the KSeF web portal (
ksef.mf.gov.pl) using a qualified electronic signature, qualified seal, or Profil Zaufany (Trusted Profile, the Polish e-government identity). - In "Tokeny autoryzacyjne" they generate an authorization token with
one or more scopes:
InvoiceWrite— issue invoices on behalf of this NIP.InvoiceRead— read invoices issued under this NIP.CredentialsManage— manage downstream tokens (avoid).
- Token is a 40+ character opaque string, displayed once. Capture immediately. Bound to the NIP and the chosen scopes.
- Optionally: in "Uprawnienia" the merchant grants the token's owner permission to issue self-billing invoices on behalf of other NIPs.
Per-session flow (every 2 hours or per submission batch):
-
POST /online/Session/AuthorisationChallenge<AuthorisationChallengeRequest>
<ContextIdentifier>
<Type>onip</Type>
<Identifier>1234567890</Identifier> <!-- 10-digit NIP -->
</ContextIdentifier>
</AuthorisationChallengeRequest>Response:
<AuthorisationChallengeResponse>
<Timestamp>2026-04-26T14:00:00.000Z</Timestamp>
<Challenge>20260426140000-XXXXXXXXXX-XX</Challenge> <!-- 26 chars -->
<ReferenceNumber>20260426-CR-...</ReferenceNumber>
</AuthorisationChallengeResponse> -
Encrypt the authorization token. The token plaintext is concatenated with the challenge:
plaintext = authorizationToken + "|" + challengeEncryption is AES-256-CBC with a fresh per-session key:
sessionKey = random 32 bytes
iv = random 16 bytes
cipher = AES-256-CBC(sessionKey, iv).encrypt(plaintext)
encryptedKey = RSA-OAEP-SHA256(MFPublicKey).encrypt(sessionKey)The MF public key is published as an X.509 certificate at
gov.pl/api/files/.../publicKey.pem(separate per environment — test, demo, production keys are distinct). The certificate is rotated yearly in late Q4; pin the year-current key by file digest, refresh onEncryptionKeyExpirederrors. -
POST /online/Session/InitTokenwith the encrypted token:<InitSessionTokenRequest>
<Context>
<Challenge>20260426140000-XXXXXXXXXX-XX</Challenge>
<Identifier>
<Type>onip</Type>
<Identifier>1234567890</Identifier>
</Identifier>
<DocumentType>FA</DocumentType>
<Token>BASE64(iv || cipher || encryptedKey)</Token>
</Context>
</InitSessionTokenRequest>Response: a JWT-like
SessionToken(200 chars, opaque) andReferenceNumber. Lifetime: 2 hours, sliding on activity. -
Subsequent requests carry header:
SessionToken: <token>
Mode 2 — Qualified electronic signature / seal (InitSigned, XAdES)
One-time onboarding: none — the merchant just needs a qualified certificate (smart card, USB token, or cloud QSCD via SimplySign / mSzafir / similar) issued by an EU-listed QTSP.
Per-session flow:
AuthorisationChallengeas above.- Build the InitSessionRequest payload (XML):
<InitSessionRequest xmlns="…/2021/10/01/0001/">
<Context>
<Challenge>…</Challenge>
<Identifier>
<ContextIdentifierType>onip</ContextIdentifierType>
<ContextIdentifierValue>1234567890</ContextIdentifierValue>
</Identifier>
<DocumentType>FA</DocumentType>
</Context>
</InitSessionRequest> - Sign with XAdES-BES enveloped (the
<Signature>element nests inside<InitSessionRequest>per ETSI EN 319 132-1). Hash algorithm must be SHA-256 (the previous SHA-1 fallback was retired 2023-12). The signing certificate must be a qualified certificate per eIDAS. - POST the signed XML to
/online/Session/InitSigned. Returns the sameSessionTokenshape as InitToken.
Cloud-OK implication: signing requires access to the qualified key material. For SimplySign / mSzafir (cloud-hosted qualified signing), the adapter calls the QTSP's REST signing API instead of holding a local key — this preserves the Cloud deployment story. For local-key customers (smart card / USB), the InitSigned flow must run embedded (in the POS or on a customer-side gateway). We will document both paths but expect token mode to dominate.
Mode 3 — Profil Zaufany / podpis zaufany (Trusted Profile)
Web-only. Used by individual entrepreneurs to log into the KSeF portal and generate authorization tokens. Not available as an API auth method — mentioned here for completeness because every Polish KSeF onboarding discussion confuses Profil Zaufany with the API auth modes. Our adapter does not integrate with PZ directly.
Mode 4 — Pieczęć kwalifikowana (qualified seal)
Same wire flow as Mode 2 (XAdES enveloped signed XML), but the signing cert is a seal certificate (legal-entity-bound, no natural-person identification) instead of a signature certificate (natural-person signature). KSeF accepts both. We treat them identically in the adapter.
What our adapter persists
Per Location:
| Field | Source | Stored as |
|---|---|---|
nip | merchant | 10-digit string, plaintext |
auth_method | merchant | enum `Token |
authorization_token | merchant (Mode 1 only) | encrypted at rest (Cloud KMS) |
signing_certificate | merchant (Mode 2/4) | reference into KMS / QTSP API config |
mf_public_key_fingerprint | MF environment | local file SHA-256, refreshed yearly |
Session tokens are never persisted — they are short-lived and stay in memory.
4. Payload shape (FA(2))
Schema
- XSD:
Schemat_FA(2)_v1-0E.xsd— published at https://www.gov.pl/web/kas/krajowy-system-e-faktur → "Struktury dokumentów XML" → "Faktura ustrukturyzowana FA(2)". - Namespace:
http://crd.gov.pl/wzor/2023/06/29/12648/ - Imports:
http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2022/01/05/eD/DefinicjeTypy/(general MF types —TNIP,TKodKraju, etc.) andhttp://crd.gov.pl/xml/schematy/dziedzinowe/mf/2024/01/05/eD/StrukturyDanych/(address structures).
Top-level shape
Faktura
├── Naglowek
│ ├── KodFormularza kodSystemowy="FA (2)" wersjaSchemy="1-0E"
│ ├── WariantFormularza # always "2" for FA(2)
│ ├── DataWytworzeniaFa # ISO datetime, ms precision
│ ├── SystemInfo # informational, free text
│ └── KodUrzedu # 4-digit tax-office code (e.g. "1010" Warszawa-Śródmieście)
├── Podmiot1 # Supplier (issuer)
│ ├── DaneIdentyfikacyjne
│ │ ├── NIP # 10 digits
│ │ ├── PelnaNazwa
│ │ └── (optional) StatusInfo
│ ├── Adres
│ │ ├── KodKraju # ISO-3166 alpha-2 (PL)
│ │ ├── AdresL1 # full address line 1 (street + number)
│ │ ├── AdresL2 # postal code + city + voivodeship
│ │ └── (optional) GLN
│ ├── (optional) AdresKoresp # correspondence address
│ └── (optional) DaneKontaktowe # email / phone
├── Podmiot2 # Customer
│ ├── DaneIdentyfikacyjne
│ │ ├── (one of) NIP | KodUE+NrVatUE | NoIdentyfikatoraPodatkowego | BrakID
│ │ └── PelnaNazwa
│ ├── Adres ...
│ └── (optional) Status / KodKrajuOdbiorcy
├── (optional) Podmiot3 ×N # Third party — recipient distinct from buyer (rzeczywisty odbiorca)
├── (optional) PodmiotUpowazniony # Authorised representative (self-billing buyer)
├── Fa
│ ├── KodWaluty # ISO-4217 (PLN | EUR | USD …)
│ ├── P_1 # invoice issue date (YYYY-MM-DD)
│ ├── P_1M # place of issue (city)
│ ├── P_2 # issuer's invoice number — UNIQUE PER ISSUER PER YEAR
│ ├── P_6 # supply date (date of completion / VAT obligation)
│ ├── P_6_Od / P_6_Do # supply period (range, alternative to P_6)
│ ├── (per-rate sums — populated only for rates the invoice actually uses)
│ │ ├── P_13_1, P_14_1 # net + VAT at 23% (basic rate)
│ │ ├── P_13_2, P_14_2 # net + VAT at 8%
│ │ ├── P_13_3, P_14_3 # net + VAT at 5%
│ │ ├── P_13_4, P_14_4 # net + VAT at 0% (export / WDT / 0% goods)
│ │ ├── P_13_5, P_14_5 # net at "zw" (zwolniona — exempt)
│ │ ├── P_13_6 # net at "np" (out-of-scope, no VAT)
│ │ ├── P_13_7, P_14_7 # net at "oo" (reverse-charge)
│ │ └── (FA(2) v1.0E adds: P_13_8/P_14_8 reserved for future rate changes)
│ ├── P_15 # gross total in invoice currency
│ ├── KursWaluty # exchange rate to PLN (4-decimal) when KodWaluty ≠ PLN