Peppol BIS to KSeF FA(3)
This page describes how xTool maps Peppol BIS Billing 3.0 (UBL Invoice or Credit Note) to KSeF FA(3).
The conversion is one-way: Peppol → KSeF. The reverse path (KSeF → Peppol) exists separately and is not covered here.
| Role | Format code |
|---|---|
| Source invoice | peppol_bis_billing.invoice.3_0.xml_ubl |
| Source credit note | peppol_bis_billing.credit_note.3_0.xml_ubl |
| Target | ksef_fa.invoice.3_0.xml |
xTool first reads Peppol into the internal invoice model, then builds FA(3). The result is validated against the official FA(3) XSD.
To connect the organisation to KSeF before sending, see Connect to KSeF. To convert a file automatically, see Validate and convert and SFTP (+fa3 in the file name).
Invoices (type 380)
A Peppol commercial invoice (InvoiceTypeCode = 380) becomes a standard KSeF document with RodzajFaktury = VAT. Amounts are copied as they are — the sign is not changed.
| Peppol BIS | KSeF FA(3) | Notes |
|---|---|---|
| Invoice number (BT-1) | P_2 |
Unchanged |
| Issue date (BT-2) | P_1 |
Unchanged |
| Tax point date (BT-7) / delivery date | P_6 |
Delivery date if present, otherwise tax point date |
| Invoice period | P_6_OD / P_6_DO |
Start and end |
| Currency (BT-5) | KodWaluty |
e.g. PLN, EUR |
| Seller (BG-4) | Podmiot1 |
NIP, name, address, contacts |
| Buyer (BG-7) | Podmiot2 |
NIP / EU VAT / other ID |
| Delivery party (BG-13) | Podmiot3 |
Role = 2 (Odbiorca) |
| Lines (BG-25) | FaWiersz |
One FA(3) line per Peppol line |
| Tax breakdown | P_13_x / P_14_x |
Taxable base and VAT per rate |
| Amount due (BT-115) | P_15 |
Payable amount including VAT |
| Payment (BG-16) | Platnosc |
Method, account, due date |
| Notes (BT-22) | Stopka |
Joined note text |
| Purchase order (BT-13) | WarunkiTransakcji |
Order number |
| Delivery address (BG-13) | WarunkiTransakcji/Transport |
Ship-to address |
| Despatch advice (BT-16) | WZ |
If present |
Credit notes (type 381)
KSeF has no separate “negative invoice” type. A Peppol credit note (381) is either a correcting invoice (KOR) or a standard invoice (VAT) with negative net amounts.
KOR or VAT
The choice depends on a preceding invoice reference (BG-3):
| Condition | KSeF type | Logic |
|---|---|---|
| Credit note has a real original invoice number | KOR + DaneFaKorygowanej |
Correction with a link to the original |
| Credit note has no original invoice number | VAT with negative net amounts |
KOR is not allowed without a required reference |
Placeholder references are treated as missing: N/A, NA, -, none, null, or an empty string. That avoids a KOR document with no real original number.
DaneFaKorygowanej (corrected invoice)
For KOR only:
| FA(3) field | Source | Description |
|---|---|---|
DataWystFaKorygowanej |
Issue date of the preceding invoice | Falls back to the credit note’s own issue date |
NrFaKorygowanej |
Preceding invoice number | Original invoice number |
NrKSeFN |
Always 1 |
Original invoice was issued outside KSeF |
NrKSeFN = 1 is always used for incoming Peppol credit notes: xTool does not have a KSeF number for the original, because it was issued via Peppol.
For the VAT fallback, DaneFaKorygowanej is omitted. PrzyczynaKorekty and TypKorekty are cleared — they are not valid on VAT.
Sign of amounts
KSeF expects a correction to be filled as the delta after the correction. For a reducing credit note the net delta is negative. xTool applies -abs() to line net, unit price, line discount, and document totals (including payable):
| Sign in the Peppol credit note | Result for net / price / payable |
|---|---|
| Positive (usual Peppol convention) | Negative |
| Already negative (non-standard sender) | Negative — not inverted again |
| Zero (e.g. a 0% line) | Zero |
Unconditional × -1 would turn an already-negative amount positive. -abs() prevents that.
This sign is applied to:
- Lines (
FaWiersz):P_11(net),P_9A(unit price),P_10(line discount, if present) - Header (
Fa):P_13_x(taxable bases from lines),P_15(payable)
VAT amounts are not inverted the same way. Document-level Peppol tax subtotals (TaxSubtotal) are copied into P_14_x as they are. Line P_11_VAT is then reconciled to that header VAT. With the usual Peppol convention (positive tax subtotals), P_14_x and the reconciled P_11_VAT stay positive, while net and payable are negative.
If a Peppol line has a negative quantity, xTool takes the absolute quantity and puts the sign on the unit price. KSeF expects a positive quantity and a negative price on credit notes.
Correction reason
Peppol BIS has no dedicated correction-reason or correction-type field in the xTool model. For KOR, xTool always sets PrzyczynaKorekty to Korekta faktury (required by the FA(3) schema). TypKorekty is not set.
Parties (Podmiot)
Podmiot1 (seller)
| Field | Source | Notes |
|---|---|---|
| NIP | Seller VAT ID or tax ID | If missing, derived from Peppol endpoint scheme 9945 |
| Name | Seller name | "-" if empty |
| Address | Seller address | Street, city, postcode, country |
| Contacts | Email and phone | If present |
| GLN | Endpoint ID (scheme 0088) |
If present |
Seller NIP is required. If xTool cannot resolve a 10-digit Polish NIP, conversion fails.
Podmiot2 (buyer)
Buyer identification is chosen in this order:
- Polish NIP (buyer VAT ID starts with
PL, or a 10-digit tax ID) - EU VAT (country code + number, for non-PL VAT IDs)
- Tax ID (country + number)
- Other party ID (country + number)
- Endpoint ID (scheme
9945→ PL NIP; other EU VAT schemes → EU VAT; remaining schemes → ID) Brak ID = 1if nothing is found
Podmiot3 (delivery recipient)
Created when delivery data includes an address. Role = 2 (Odbiorca).
VAT rates
| Peppol rate / code | KSeF P_12 |
Header buckets |
|---|---|---|
| 23% | 23 |
P_13_1 / P_14_1 (standard) |
| 22% | 22 |
P_13_1 / P_14_1 (standard) |
| 8% | 8 |
P_13_2 / P_14_2 (reduced 1) |
| 7% | 7 |
P_13_2 / P_14_2 (reduced 1) |
| 5% | 5 |
P_13_3 / P_14_3 (reduced 2; FA(3) documents this bucket as currently 5%) |
| 4% | 4 |
Line P_12 only — not summed into header P_13_x / P_14_x |
| 3% | 3 |
Line P_12 only — not summed into header P_13_x / P_14_x |
0%, code E / ZW |
zw |
P_13_7 (exempt) |
0%, code AE / OO |
oo |
P_13_10 (reverse charge) |
0%, any other code (including S, G, K, O) |
0 KR |
P_13_6_1 (zero domestic) |
An unsupported percent (for example 19%) fails conversion with an explicit error. A 0% line with a code other than E / ZW / AE / OO does not fail: it is written as 0 KR. Intra-community (K) and export (G) codes are not mapped to 0 WDT or 0 EX.
Line VAT (P_11_VAT) is always calculated as net × rate (skipped for codes E, AE, O). Header VAT (P_14_x) prefers document-level tax subtotals when those exist. If the sum of line VAT differs from the header breakdown, the residual is applied to the last line in that rate bucket.
Lines (FaWiersz)
Each Peppol line becomes one FaWiersz (or ZamowienieWiersz for advance invoices ZAL / KOR_ZAL):
| FA(3) field | Peppol source | Description |
|---|---|---|
P_6A |
Line period | End date, otherwise start date |
P_7 |
Item name | Description if name is empty |
P_8A |
Unit of measure | UN/ECE code (e.g. C62); can be overridden by property unit_of_measure |
P_8B |
Quantity | Absolute value when the Peppol quantity was negative |
P_9A |
Unit price | Net; negative on credit notes |
P_10 |
Line discount | If present; negative on credit notes |
P_11 |
Line net | Negative on credit notes |
P_11_VAT |
Line VAT | Net × rate; then reconciled to header P_14_x (see VAT rates) |
P_12 |
VAT rate code | See the table above |
PKWiU |
Classification | Scheme PKWIU / PKWIUZ |
CN |
CN code | Scheme CN |
PKOB |
PKOB | Scheme PKOB |
GTIN |
Standard item ID | If present |
Indeks |
Seller item ID | If present |
Payment (Platnosc)
The payment block is created when payment details or a due date are present.
| FA(3) field | Source | Description |
|---|---|---|
FormaPlatnosci |
Payment means code / name | 6 if the code is 30, 31, 42 or 58, or the name contains transfer/SEPA/bank. 1–5 and 7 are matched on the name only (cash, card, voucher, cheque/check, credit, mobile/phone). Otherwise the field is omitted. Other UNECE codes (for example 10 cash, 48 card) are not mapped by code. |
RachunekBankowy |
IBAN / account number + BIC | Payee account |
TerminPlatnosci |
Due date (BT-9) | Date by which the invoice should be paid |
Zaplacono |
Paid vs payable | 1 if paid amount ≥ payable amount |
DataZaplaty |
Due date | Set when Zaplacono = 1 |
Currency and exchange rate
If the invoice is not in PLN:
KodWalutyis set to the invoice currency.- If an exchange rate is present, xTool fills
KursWalutyZand VAT amounts in PLN (P_14_1W,P_14_2W,P_14_3W). - If there is no rate, a warning is logged and the PLN conversion fields stay empty (allowed by the XSD).
Extra descriptions (DodatkowyOpis)
| Source | Key | Level |
|---|---|---|
| Sales order reference (BT-14) | sales_order_reference |
Header |
| Other line properties | Property name | Line (with line number) |
If a line property is named unit_of_measure, its value is written to P_8A and is not added as DodatkowyOpis.
Annotations (Adnotacje)
Filled with schema-safe defaults:
| Field | Default | Override |
|---|---|---|
P_16 |
2 (no) |
— |
P_17 |
2 (no) |
1 for types 389 / 527 |
P_18 |
2 (no) |
— |
P_18A |
2 (no) |
— |
Zwolnienie |
P_19N = 1 (no exemption) |
P_19 = 1, P_19A = <text> when an exemption reason is present (BT-6) |
NoweSrodkiTransportu |
P_22_N = 1 (no) |
— |
P_23 |
2 (no) |
— |
PMarzy |
P_PMarzy_N = 1 (no) |
— |
Footer (Stopka)
Document notes (BT-22) are joined and written to Stopka/Informacje.
XSD validation
The converted XML is validated against FA(3). Checks include:
- Element order in sequences
- Required fields
- Choices (for example
NrKSeF/NrKSeFFaKorygowanejorNrKSeFNinDaneFaKorygowanej) - Data types (
TKwotowy,TWybor1,TStawkaPodatku,TFormaPlatnosci) - String lengths
Validation errors stop the conversion with a detailed message.
Current limitations
| Limitation | Description |
|---|---|
| Document-level discounts/charges | Not mapped to KSeF (a warning is logged) |
Advance invoices (ZAL) |
ZamowienieWiersz structure exists; full advance logic is not implemented |
| 4% / 3% header totals | Written to line P_12; not accumulated in P_13_x / P_14_x |
| WDT / export 0% | Peppol codes K / G become 0 KR, not 0 WDT / 0 EX |
| Credit-note header VAT | P_14_x follows Peppol TaxSubtotal (not -abs()); line P_11_VAT is reconciled to it |
| VAT margin scheme | Not supported (P_23 = 2) |
| New means of transport | Not supported (P_22_N = 1) |
| Factoring | Not supported (RachunekBankowyFaktora empty) |
| Authorised representative | Not supported (PodmiotUpowazniony empty) |
Invoice vs credit note
| Aspect | Invoice (380) | Credit note (381) with reference | Credit note (381) without reference |
|---|---|---|---|
RodzajFaktury |
VAT |
KOR |
VAT |
DaneFaKorygowanej |
No | Yes (NrKSeFN = 1) |
No |
PrzyczynaKorekty |
No | Yes (always Korekta faktury) |
No |
TypKorekty |
No | No | No |
| Sign of net / payable | Unchanged | Negative (-abs) |
Negative (-abs) |
Sign of VAT (P_14_x) |
Unchanged | As in Peppol TaxSubtotal |
As in Peppol TaxSubtotal |
| Lines | FaWiersz |
FaWiersz |
FaWiersz |
| XSD validation | Full | Full | Full |
Next steps
- Connect KSeF — Connect to KSeF.
- Convert — Validate and convert.
- Send via SFTP — Send and receive via SFTP.