@@ -64,6 +64,253 @@ client = Client(authorization=auth, environment=Environment.TEST)
6464- ` Environment.DEMO ` — ` https://api-demo.ksef.mf.gov.pl/api/v2/ `
6565- ` Environment.TEST ` — ` https://api-test.ksef.mf.gov.pl/api/v2/ `
6666
67+ ## Building Invoices
68+
69+ ### Basic invoice structure
70+
71+ ``` python
72+ from datetime import date
73+ from decimal import Decimal
74+
75+ from ksef.models.invoice import (
76+ Address,
77+ Invoice,
78+ InvoiceData,
79+ InvoiceType,
80+ Issuer,
81+ IssuerIdentificationData,
82+ NipIdentification,
83+ Subject,
84+ )
85+ from ksef.models.invoice_rows import InvoiceRow, InvoiceRows
86+ from ksef.models.invoice_annotations import InvoiceAnnotations
87+
88+ invoice = Invoice(
89+ issuer = Issuer(
90+ identification_data = IssuerIdentificationData(
91+ nip = " 1234567890" ,
92+ full_name = " My Company Sp. z o.o." ,
93+ ),
94+ address = Address(
95+ country_code = " PL" ,
96+ city = " Warszawa" ,
97+ street = " Marszałkowska" ,
98+ house_number = " 10" ,
99+ apartment_number = " 5" ,
100+ postal_code = " 00-001" ,
101+ ),
102+ ),
103+ recipient = Subject(
104+ identification_data = NipIdentification(nip = " 0987654321" ),
105+ address = Address(
106+ country_code = " PL" ,
107+ city = " Kraków" ,
108+ street = " Floriańska" ,
109+ house_number = " 1" ,
110+ postal_code = " 30-001" ,
111+ ),
112+ name = " Customer Sp. z o.o." ,
113+ ),
114+ invoice_data = InvoiceData(
115+ currency_code = " PLN" ,
116+ issue_date = date(2026 , 3 , 25 ),
117+ issue_number = " 2026/03/001" ,
118+ sell_date = date(2026 , 3 , 25 ),
119+ total_amount = Decimal(" 123.00" ),
120+ invoice_type = InvoiceType.REGULAR_VAT ,
121+ invoice_annotations = InvoiceAnnotations(),
122+ invoice_rows = InvoiceRows(rows = [
123+ InvoiceRow(
124+ name = " Hosting service" ,
125+ unit_of_measure = " szt." ,
126+ quantity = Decimal(" 1" ),
127+ unit_net_price = Decimal(" 100.00" ),
128+ net_value = Decimal(" 100.00" ),
129+ tax = 23 ,
130+ delivery_date = date(2026 , 3 , 25 ),
131+ ),
132+ ]),
133+ ),
134+ )
135+ ```
136+
137+ ### Recipient identification types
138+
139+ The library supports all KSeF recipient identification methods:
140+
141+ ``` python
142+ from ksef.models.invoice import (
143+ NipIdentification, # Polish NIP
144+ EuVatIdentification, # EU VAT number
145+ ForeignIdentification, # Non-EU tax ID
146+ NoIdentification, # No tax ID (individuals)
147+ )
148+
149+ # Polish company
150+ id_pl = NipIdentification(nip = " 1234567890" )
151+
152+ # EU company
153+ id_eu = EuVatIdentification(eu_country_code = " DE" , eu_vat_number = " 123456789" )
154+
155+ # Non-EU company
156+ id_foreign = ForeignIdentification(country_code = " US" , tax_id = " 12-3456789" )
157+
158+ # Individual (no tax ID)
159+ id_none = NoIdentification()
160+ ```
161+
162+ ### Tax rates
163+
164+ Invoice rows support all valid KSeF tax rates via the ` tax ` field, plus OSS/IOSS rates via ` tax_oss ` :
165+
166+ ``` python
167+ from ksef.models.invoice_rows import (
168+ InvoiceRow,
169+ # Standard rates
170+ TAX_23 , TAX_22 , TAX_8 , TAX_7 , TAX_5 , TAX_4 , TAX_3 ,
171+ # Zero rates
172+ TAX_0_KR , # 0% domestic
173+ TAX_0_WDT , # 0% intra-Community supply
174+ TAX_0_EX , # 0% export
175+ # Special rates
176+ TAX_ZW , # exempt from tax
177+ TAX_OO , # reverse charge
178+ TAX_NP_I , # not subject to taxation
179+ TAX_NP_II , # not subject (art. 100)
180+ )
181+
182+ # Standard 23% rate
183+ row = InvoiceRow(name = " Service" , tax = TAX_23 )
184+
185+ # Intra-Community supply at 0%
186+ row = InvoiceRow(name = " Goods to EU" , tax = TAX_0_WDT )
187+
188+ # OSS rate for EU consumer (e.g. 21% Belgian VAT)
189+ row = InvoiceRow(name = " Digital service" , tax_oss = Decimal(" 21" ))
190+ ```
191+
192+ ### Tax summary (P_13/P_14 fields)
193+
194+ For KSeF to display Netto/VAT totals, provide a ` TaxSummary ` on ` InvoiceData ` :
195+
196+ ``` python
197+ from ksef.models.invoice import TaxSummary
198+
199+ tax_summary = TaxSummary(
200+ net_standard = Decimal(" 100.00" ), # P_13_1 — net at 23%/22%
201+ vat_standard = Decimal(" 23.00" ), # P_14_1 — VAT at 23%/22%
202+ )
203+ ```
204+
205+ Available fields:
206+
207+ | Fields | Rate | XML |
208+ | --------| ------| -----|
209+ | ` net_standard ` / ` vat_standard ` | 23% or 22% | P_13_1 / P_14_1 |
210+ | ` net_reduced_1 ` / ` vat_reduced_1 ` | 8% or 7% | P_13_2 / P_14_2 |
211+ | ` net_reduced_2 ` / ` vat_reduced_2 ` | 5% | P_13_3 / P_14_3 |
212+ | ` net_flat_rate ` / ` vat_flat_rate ` | 4% or 3% | P_13_4 / P_14_4 |
213+ | ` net_oss ` / ` vat_oss ` | OSS/IOSS | P_13_5 / P_14_5 |
214+ | ` net_zero_domestic ` | 0% domestic | P_13_6_1 |
215+ | ` net_zero_wdt ` | 0% intra-Community | P_13_6_2 |
216+ | ` net_zero_export ` | 0% export | P_13_6_3 |
217+ | ` net_exempt ` | exempt (zw) | P_13_7 |
218+ | ` net_not_subject ` | not subject (np I) | P_13_8 |
219+ | ` net_not_subject_art100 ` | not subject (np II) | P_13_9 |
220+ | ` net_reverse_charge ` | reverse charge (oo) | P_13_10 |
221+
222+ For foreign currency invoices, use the ` *_pln ` fields to provide VAT converted to PLN:
223+
224+ ``` python
225+ TaxSummary(
226+ net_standard = Decimal(" 100.00" ), # EUR
227+ vat_standard = Decimal(" 23.00" ), # EUR
228+ vat_standard_pln = Decimal(" 98.31" ), # P_14_1W — VAT in PLN
229+ )
230+ ```
231+
232+ ### Foreign currency invoices
233+
234+ For non-PLN invoices, set the exchange rate per row and add descriptions for the rate source:
235+
236+ ``` python
237+ from ksef.models.invoice import AdditionalDescription
238+
239+ row = InvoiceRow(
240+ name = " Service" ,
241+ tax = 23 ,
242+ unit_net_price = Decimal(" 100.00" ),
243+ net_value = Decimal(" 100.00" ),
244+ quantity = Decimal(" 1" ),
245+ exchange_rate = Decimal(" 4.2867" ), # KursWaluty per row
246+ )
247+
248+ # NBP rate source as a key-value note
249+ desc = AdditionalDescription(
250+ key = " Kurs waluty" ,
251+ value = " 4.2867 PLN/EUR, tabela kursów średnich NBP nr 056/A/NBP/2026 z dnia 23.03.2026" ,
252+ )
253+
254+ invoice_data = InvoiceData(
255+ currency_code = " EUR" ,
256+ additional_descriptions = [desc],
257+ # ...
258+ )
259+ ```
260+
261+ ### Additional recipients (Podmiot3)
262+
263+ For invoices with a third party (e.g. a government receiver/school when the buyer is a city hall):
264+
265+ ``` python
266+ from ksef.models.invoice import (
267+ AdditionalRecipient,
268+ ROLE_RECEIVER , # 2 — internal unit/branch of the buyer
269+ ROLE_JST_RECEIVER , # 8 — government unit receiver
270+ ROLE_FAKTOR , # 1 — factoring entity
271+ ROLE_ADDITIONAL_BUYER , # 4 — additional buyer
272+ )
273+
274+ receiver = AdditionalRecipient(
275+ identification_data = NipIdentification(nip = " 9876543210" ),
276+ name = " Szkoła Podstawowa nr 1" ,
277+ address = Address(
278+ country_code = " PL" ,
279+ city = " Tarnów" ,
280+ street = " Słoneczna" ,
281+ house_number = " 15" ,
282+ postal_code = " 33-100" ,
283+ ),
284+ role = ROLE_RECEIVER ,
285+ )
286+
287+ invoice = Invoice(
288+ issuer = issuer,
289+ recipient = buyer,
290+ additional_recipients = [receiver],
291+ invoice_data = invoice_data,
292+ )
293+ ```
294+
295+ ### Sending an invoice
296+
297+ ``` python
298+ from ksef.xml_converters import convert_invoice_to_xml
299+
300+ # Convert to XML and send
301+ result = client.send_invoice(invoice)
302+ print (result.reference_number)
303+ print (result.session_reference_number)
304+
305+ # Check status later
306+ status = client.get_invoice_status(
307+ session_reference_number = result.session_reference_number,
308+ reference_number = result.reference_number,
309+ )
310+ print (status.status.code) # 200 = accepted
311+ print (status.ksef_number) # e.g. "1234567890-20260325-ABCDEF-01"
312+ ```
313+
67314## Integration Tests
68315
69316Integration tests connect to the live KSEF test environment using real credentials. They are excluded from the default test run and must be invoked explicitly:
0 commit comments