Użyj auth.invoices, gdy potrzebujesz treści faktury po przetworzeniu jej przez
KSeF. Przykłady poniżej zakładają, że masz już uwierzytelnionego klienta
auth.
Pobierz jedną fakturę
Jeśli masz numer KSeF, bezpośrednie pobranie to najkrótsza droga.
from pathlib import Path
ksef_number = "1234567890-20260625-..."
xml_bytes = auth.invoices.download(ksef_number)
Path("invoice.xml").write_bytes(xml_bytes)Jeśli faktura została dopiero wysłana, KSeF może potrzebować czasu, zanim
przetworzony XML będzie dostępny. Podaj timeout, a download() będzie
odpytywać KSeF, aż dokument będzie dostępny.
xml_bytes = auth.invoices.download(ksef_number, timeout=120.0, poll_interval=2.0)
Path("invoice.xml").write_bytes(xml_bytes)Zbuduj filtr eksportu
Eksportów używaj do większych pobrań. Eksporty korzystają z InvoicesFilter,
czyli tego samego kształtu filtra co zapytania o metadane.
from datetime import datetime, timedelta, timezone
from ksef2.models import InvoicesFilter
now = datetime.now(tz=timezone.utc)
filters = InvoicesFilter.for_buyer(
date_type="permanent_storage",
date_from=now - timedelta(days=1),
date_to=now,
restrict_to_permanent_storage_hwm_date=True,
)Wyeksportuj wiele faktur
export() planuje eksport i zwraca ExportJob. Jego wait() odpytuje KSeF,
aż paczka będzie gotowa, pobiera i odszyfrowuje części, łączy je i zwraca obiekt
ExportedInvoices.
job = auth.invoices.export(filters)
package = job.wait(timeout=300.0)
for ksef_number, xml in package.invoices():
print(ksef_number, len(xml))
# Sparsowany _metadata.json (lista InvoiceMetadata) albo None, gdy go brak.
metadata = package.metadata
# Szczegóły paczki do synchronizacji przyrostowej.
print(package.package.is_truncated, package.package.permanent_storage_hwm_date)Zapis do plików
from pathlib import Path
written = package.save(Path("downloads"))save() rozpakowuje archiwum i odrzuca każdy wpis, którego ścieżka wychodziłaby
poza katalog docelowy. Surowe bajty ZIP są dostępne jako package.archive.
Wznowienie po restarcie
job.resume_state() zwraca ExportResumeState z numerem referencyjnym i kluczem
odszyfrowującym paczkę. Zapisz jego JSON, a export(state=...) zwróci z powrotem
ExportJob, więc wait() nadal działa.
job = auth.invoices.export(filters)
saved = job.resume_state().to_json() # przechowuj jak poświadczenie, nie loguj
# po restarcie
job = auth.invoices.export(state=saved)
package = job.wait(timeout=300.0)Podaj dokładnie jedno z filters albo state. to_dict(), from_json() i
from_dict() działają jak w stanach sesji; model_dump() i repr() ukrywają
klucz.
Po wysyłce faktur
Trzymaj wysyłkę, przetwarzanie i pobieranie jako osobne fazy.
-
Wyślij XML faktury przez sesję online albo wsadową.
-
Polluj status sesji albo faktury do końcowego zaakceptowanego wyniku.
-
Zapisz zwrócone wartości
ksef_number. -
Pobierz jeden przetworzony dokument XML po
ksef_numberalbo zbuduj filtr eksportu dla większego okna czasu.
Pobieranie partii przy limitach zapytań
Nie potrzebujesz stałego sleep między wywołaniami download() ani ponownego
logowania po 401. SDK stosuje dokładnie nagłówek Retry-After do wartości
RetryConfig.max_retry_after (domyślnie 120 sekund), ponawia błędy przemijające
i odświeża access token raz po 401.
Jeden przypadek SDK oddaje z powrotem aplikacji: gdy KSeF każe czekać dłużej niż
sufit. Wtedy download() rzuca KSeFRateLimitError natychmiast, bez spania, a
error.retry_after niesie prawdziwą wartość z nagłówka.
Pojedyncze download() nadal blokuje, gdy nagłówek jest na tyle krótki, że SDK
go stosuje. Jeśli KSeF powtarza ten sam Retry-After, takie wywołanie
czeka maksymalnie (max_attempts - 1) * max_retry_after przed rzuceniem błędu,
czyli domyślnie 240 sekund. Uwzględnij to w długiej partii.
from pathlib import Path
from ksef2 import KSeFRateLimitError
downloads = Path("downloads")
try:
for ksef_number in ksef_numbers:
xml_bytes = auth.invoices.download(ksef_number)
downloads.joinpath(f"{ksef_number}.xml").write_bytes(xml_bytes)
except KSeFRateLimitError as error:
# KSeF kazał czekać dłużej niż RetryConfig.max_retry_after sekund.
# SDK nie spał. Zaplanuj pozostałe numery za error.retry_after sekund,
# zamiast ponawiać w pętli.
print(f"zwolnij, wróć za {error.retry_after} sekund")Pełne zachowanie dla 429 i 401 oraz listę rzeczy należących do aplikacji
opisuje Operacje.
Następne przepływy
- Wyszukaj faktury: Znajdź numery KSeF i metadane przed pobraniem treści faktury.
- Wyślij faktury: Wyślij XML faktury przez sesję online albo wsadową.
- Zapytania i eksporty: Zrozum metadane, bezpośrednie pobieranie, paczki eksportu i synchronizację HWM.
- Sprawdź certyfikaty szyfrowania: Sprawdź publiczne certyfikaty KSeF używane przez szyfrowane przepływy.
Eksportuj PDF z weryfikacyjnym kodem QR
Zainstaluj opcjonalne zależności przez pip install 'ksef2[pdf]' oraz biblioteki
systemowe wymagane przez WeasyPrint.
from ksef2 import Environment
from ksef2.renderers import InvoicePDFExporter, PDFPageSize
exporter = InvoicePDFExporter(
page_size=PDFPageSize.A4,
verification_environment=Environment.TEST,
)
output = exporter.export_to_file(
"invoice.xml",
"invoice.pdf",
ksef_number="9999999999-20260215-ABCDEF123456-AB",
)Podaj rzeczywisty numer KSeF nadany tej fakturze. ksef_number dodaje lokalnie
wygenerowany kod QR w formacie SVG oraz numer pod kodem. Pominięcie tego argumentu
zachowuje dotychczasowy PDF bez QR. Ten sam argument obsługują export_from_path()
i export_from_string(). Domyślnym środowiskiem jest Environment.PRODUCTION;
dla faktur testowych lub demo wybierz odpowiednio TEST lub DEMO.
Kod zawiera NIP sprzedawcy, datę wystawienia oraz SHA-256 oryginalnych bajtów XML,
zakodowany jako Base64URL bez dopełnienia, zgodnie z
specyfikacją kodów QR Ministerstwa Finansów.
Do export_from_string() przekazuj bajty; tekst jest kodowany jako UTF-8.
Ponowna serializacja faktury zmienia skrót. Eksport nie sprawdza przyjęcia faktury
ani tego, czy podany numer KSeF należy do niej.
Opcja obsługuje faktury z nadanym numerem KSeF. Nie generuje kodu QR certyfikatu dla trybu offline, nie podpisuje adresów URL ani nie zastępuje własnych układów PDF.
PDFPageSize obsługuje A4, A4_LANDSCAPE, A3, A3_LANDSCAPE, LETTER oraz
LETTER_LANDSCAPE. Bez page_size obowiązuje dotychczasowe ustawienie arkusza,
zwykle A4 poziomo. Jawny rozmiar strony ma pierwszeństwo przed CSS w html_overrides;
pozostałe reguły CSS nadal działają. Szerokie tabele mogą wymagać orientacji poziomej
lub własnego CSS, aby zmieściły się na pionowej stronie.
