Skip to main content

Poland Fiscalization Guide

What is Polish fiscalization?

Poland operates KSeF (Krajowy System e-Faktur — National e-Invoicing System), run by the Ministry of Finance. KSeF is mandatory for large taxpayers from 2026-02-01 and all VAT-registered taxpayers from 2026-04-01. Every B2B invoice (and B2C in opt-in mode) must be transmitted as FA(2) XML; KSeF returns a numerKSeF which becomes the canonical legal identifier.

In plain English: Poland is moving everyone onto a single national invoicing platform in 2026. KSeF accepts invoices, validates them, and assigns its own number. Zyntem keeps your issuer-side numbering intact (P_2) while also surfacing the KSeF number for legal references.

You don't need to worry about this

Zyntem builds the FA(2) XML, handles all four KSeF authentication modes (token, cloud QSCD, hardware key, qualified seal), submits to KSeF, polls the UPO (Urzędowe Poświadczenie Odbioru — official confirmation of receipt), and surfaces both the P_2 and numerKSeF on the transaction.

How it works

Poland's flow is asynchronous and dual-numbered:

  1. Your POS creates a transaction via POST /v1/transactions.
  2. Zyntem assembles the FA(2) XML (http://crd.gov.pl/wzor/2023/06/29/12648/) including the issuer-side P_2 invoice number.
  3. Zyntem authenticates per the location's auth_method (see below) and submits to /api/online/Invoice/Send.
  4. KSeF validates synchronously and queues the invoice; the transaction returns with fiscal_id = P_2 and fiscal_id_upstream = null.
  5. The polling worker hits /api/online/Invoice/Status/{id} until KSeF issues the UPO and assigns the numerKSeF. Status flips to success and fiscal_id_upstream = numerKSeF.

Submission window: KSeF mandates within-day submission for B2B (Article 106na of the Polish VAT Act). Zyntem queues immediately.

FA(2) → FA(3) cutover: FA(3) becomes effective 2026-10-01. Configurations using schema_version: FA3 reject prepare() until the FA(3) builder lands. Default to FA2 until the cutover date.

Authentication modes

KSeF supports four auth modes. Pick the one that matches your merchant's setup. The auth_method.kind discriminator selects the variant.

ModeCloud-OKUse when
token✅ RecommendedKSeF-portal-issued bearer token. Simplest — generate the token from the merchant's KSeF portal session and Zyntem stores the KMS reference.
xades_cloud_qscdMerchant has a cloud QSCD (SimplySign / mSzafir) and prefers XAdES-BES signing.
xades_hardware_key❌ Embedded onlyHardware-bound qualified key (forces Embedded-Local mode — Cloud config is rejected).
qualified_sealLegal-entity-bound qualified seal (eIDAS).

Country config fields

The country_config object for Polish locations:

FieldTypeRequiredDescription
nipstringYesPolish NIP (10 digits).
auth_methodobjectYesOne of the four KSeF auth modes (see above).
environmentstringYestest, demo, or production.
schema_versionstringYesFA2 (current) or FA3 (post-2026-10-01).
tax_office_codestringYes4-digit KodUrzedu from the Naglowek.
place_of_issuestringYesPlace of issue printed on the invoice (P_1M).
supplier_glnstringNoOptional supplier GLN (used for SCM integrations).
legal_namestringNoOptional supplier legal name.
vat_uestringNoOptional VAT-UE number (PL + 10 digits).

Configuration example

curl -X POST https://api.zyntem.dev/v1/locations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"name": "Warsaw Office",
"country": "PL",
"address": "ul. Marszałkowska 1, 00-001 Warszawa",
"country_config": {
"nip": "1234567890",
"auth_method": {
"kind": "token",
"token_kms_ref": "kms://pl/ksef-token"
},
"environment": "production",
"schema_version": "FA2",
"tax_office_code": "1010",
"place_of_issue": "Warszawa",
"legal_name": "Zyntem Przykład Sp. z o.o.",
"vat_ue": "PL1234567890"
}
}'

Creating a transaction

Example: A B2B invoice for PLN 1 230 to a Polish customer.

curl -X POST https://api.zyntem.dev/v1/transactions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer zyn_test_abc123def456..." \
-d '{
"location_id": "loc_abc123",
"type": "sale",
"amount": 123000,
"currency": "PLN",
"buyer": {
"name": "Kontrahent Sp. z o.o.",
"tax_id": "PL9876543210",
"country": "PL"
},
"line_items": [
{
"description": "Usługa konsultingowa",
"quantity": 1,
"unit_price": 100000,
"vat_rate": 2300
}
]
}'

Response

{
"id": "txn_abc123",
"status": "pending_submission",
"fiscal_id": "FV/2026/03/00042",
"fiscal_id_upstream": null,
"country": "PL",
"created_at": "2026-03-15T14:30:22Z"
}

The fiscal_id is the issuer-side P_2 invoice number that you (or your numbering policy) assigned. Once KSeF issues the UPO, fiscal_id_upstream populates with the numerKSeF:

fiscal_id_upstream: 1234567890-20260315-A1B2C3D4E5F6-7G

(35-character format: <NIP>-<YYYYMMDD>-<12 hex>-<2 hex>)

Sandbox access

KSeF has two pre-production rings:

  • Test (ksef-test.mf.gov.pl) — open onboarding, lower fidelity.
  • Demo (ksef-demo.mf.gov.pl) — closer to production in throttling and UPO behaviour.

Sandbox routing is automatic with a test API key. Tokens for test/demo are obtained from the corresponding KSeF portal — typical onboarding is 1 day for test, 2–3 days for demo.

Error handling

When KSeF rejects an invoice, error.category distinguishes:

  • transient — network blip, KSeF 5xx, throttling. Zyntem retries automatically.
  • regulatory_reject — KSeF business validation failed. error.authority_code carries the processingCode (40xxx series) or schema-validation code. Correct the invoice and re-issue under a new P_2.
  • permanent — auth-method mismatch (e.g. xades_hardware_key configured for Cloud), sealed-config error, or FA(3) attempted before the cutover. Surface to operator.
  • duplicate_as_success — KSeF rejected as a repeated submission of an already-accepted invoice. Treated as success.

See the Error handling guide for the full taxonomy.