Żeby bezpiecznie pobrać UPO z KSeF, zachowaj numer referencyjny faktury zwrócony po wysyłce, a potem sprawdzaj jej status aż do stanu końcowego. Za przyjętą uznaj dopiero fakturę ze statusem `200` i numerem KSeF. Następnie pobierz XML UPO, sprawdź hash odpowiedzi, zgodność ze schemą i podpis XAdES, po czym zarchiwizuj dokładnie te same bajty. Poprawna odpowiedź na wysyłkę ani wygasający adres UPO nie dowodzą jeszcze, że KSeF przyjął fakturę.

## Co dowodzi, że KSeF przyjął fakturę?

**O przyjęciu świadczy zakończony wynik operacji asynchronicznej, a nie udane żądanie HTTP.** Odpowiedź na wysyłkę zawiera `referenceNumber` faktury i uruchamia jej weryfikację. Nie nadaje jeszcze ostatecznego numeru KSeF ani nie potwierdza, że dokument przeszedł kontrolę w systemie.

Na tym rozróżnieniu opiera się niezawodna integracja. Endpoint wysyłkowy może odpowiedzieć poprawnie, choć faktura nadal czeka na walidację. Jeśli już wtedy oznaczysz ją jako przyjętą, lokalny stan wyprzedzi stan urzędowy. Klient zobaczy fałszywy sukces, księgowość ruszy za wcześnie, a później może zabraknąć identyfikatorów potrzebnych do znalezienia odrzucenia.

[Oficjalny przewodnik po sesji interaktywnej](https://github.com/CIRFMF/ksef-api/blob/main/sesja-interaktywna.md#2-wys%C5%82anie-faktury) opisuje weryfikację po wysyłce jako operację asynchroniczną. Zapisz zwrócony numer referencyjny faktury obok lokalnej faktury i numeru referencyjnego sesji od razu po wysłaniu. Każdy z tych identyfikatorów ma inne zadanie:

- Lokalny identyfikator faktury łączy proces z rekordem biznesowym w twoim systemie.
- Numer referencyjny sesji wskazuje sesję KSeF używaną do przesłania dokumentu.
- Numer referencyjny faktury identyfikuje tę wysyłkę podczas przetwarzania przez KSeF.
- Numer KSeF pojawia się dopiero po przyjęciu i staje się częścią materiału dowodowego.

Przy wyniku pozytywnym poczekaj na status faktury `200`, sprawdź, czy odpowiedź zawiera numer KSeF, i pobierz UPO. [Podręcznik KSeF 2.0, część II](https://ksef.podatki.gov.pl/media/cark5buy/podrecznik-ksef-20-czesc-ii-wystawianie-i-otrzymywanie-faktur-w-ksef-19022026.pdf) wyjaśnia, że po przyjęciu faktura dostaje numer KSeF, a UPO jest dostępne jako osobny dokument XML.

Wystarczy więc prosty model stanów: **wysłana**, **przetwarzana**, **przyjęta** albo **odrzucona**. Nie łącz wysłania z przyjęciem. Odpowiedź HTTP potwierdza transport; status `200`, numer KSeF i zweryfikowane UPO potwierdzają zakończony wynik.

## Który endpoint statusu KSeF odpytywać?

**Gdy potrzebujesz wyniku konkretnej faktury, sprawdzaj status właśnie tej faktury.** Użyj `GET /sessions/{referenceNumber}/invoices/{invoiceReferenceNumber}` z numerem sesji i numerem faktury zapisanymi podczas wysyłki.

Według [oficjalnej specyfikacji OpenAPI](https://github.com/CIRFMF/ksef-api/blob/main/open-api.json) odpowiedź może zawierać status faktury w KSeF, odczytany numer lokalny, numer KSeF, hash faktury, datę nadania numeru, datę przyjęcia do przetwarzania, datę trwałego zapisu, tryb fakturowania oraz wygasający adres pobrania UPO wraz z terminem ważności. Zapisuj przydatne pola, gdy tylko się pojawią, zamiast czekać na zakończenie całej sesji.

Endpoint sesji, `GET /sessions/{referenceNumber}`, rozwiązuje inny problem. Zwraca stan sesji oraz wartości zbiorcze, takie jak `invoiceCount`, `successfulInvoiceCount` i `failedInvoiceCount`. Po zamknięciu sesji może również podać referencje i adresy pobrania stron zbiorczego UPO.

Te sumy służą do uzgodnienia danych. Pokazują, czy liczba zapisanych przez ciebie wyników jednostkowych zgadza się z KSeF. Nie mówią jednak, która lokalna faktura dostała dany numer KSeF. W jednej sesji mogą też znaleźć się faktury poprawne i odrzucone, więc wynik całej sesji nie zastępuje rekordu pojedynczej faktury.

Korzystaj z obu perspektyw świadomie:

1. Status pojedynczej faktury steruje stanem widocznym w produkcie lub systemie ERP.
2. Status sesji służy do uzgadniania sum i, gdy ma to sens, pobierania stron zbiorczego UPO.
3. Każdą różnicę między licznikami sesji a zapisanymi wynikami faktur trzeba wyjaśnić.

[Oficjalny przewodnik po statusie sesji i UPO](https://github.com/CIRFMF/ksef-api/blob/main/faktury/sesje/sesja-sprawdzenie-stanu-i-pobranie-upo.md) opisuje oba poziomy. Ich rozdzielenie chroni przed częstym błędem: traktowaniem numeru sesji, numeru referencyjnego faktury i numeru KSeF jak tej samej wartości.

## Które statusy faktury są końcowe?

**Mechanizm sprawdzający status musi zakończyć pracę przy każdym udokumentowanym stanie końcowym, nie tylko przy sukcesie.** Kody `100` i `150` oznaczają dalsze przetwarzanie. Kod `200` oznacza powodzenie. Poniższe wyniki `4xx` i `5xx` wymagają obsługi błędu albo analizy.

| Kod | Znaczenie | Klasyfikacja w produkcji |
|---|---|---|
| `100` | Faktura przyjęta do dalszego przetwarzania | Stan niekońcowy |
| `150` | Trwa przetwarzanie | Stan niekońcowy |
| `200` | Sukces | Sukces końcowy |
| `405` | Przetwarzanie anulowane z powodu błędu sesji | Błąd końcowy |
| `410` | Nieprawidłowy zakres uprawnień | Błąd końcowy |
| `415` | Brak możliwości wysłania faktury z załącznikiem | Błąd końcowy |
| `430` | Błąd weryfikacji pliku faktury | Błąd końcowy |
| `435` | Błąd odszyfrowania pliku | Błąd końcowy |
| `440` | Duplikat faktury | Błąd końcowy; sprawdź ustrukturyzowane rozszerzenia |
| `450` | Błąd weryfikacji semantyki dokumentu faktury | Błąd końcowy |
| `500` | Nieznany błąd | Błąd końcowy; analiza ręczna |
| `550` | Operacja anulowana przez system | Zbadaj przyczynę, potem ewentualnie ponów całą operację |

Znaczenia pochodzą z modelu `SessionInvoiceStatusResponse` w [aktualnym kontrakcie OpenAPI](https://github.com/CIRFMF/ksef-api/blob/main/open-api.json). Zapisuj otrzymany kod liczbowy, opis, szczegóły i ustrukturyzowane rozszerzenia. Tekst pomaga osobie obsługującej problem, a kod daje aplikacji stabilny punkt decyzyjny.

Szczególnej uwagi wymaga status duplikatu `440`. Może zawierać uporządkowane dane o pierwotnej sesji i numerze KSeF. To materiał do analizy, nie zgoda na ciche zamienienie nowej próby w sukces. Połącz rekord z oryginałem dopiero wtedy, gdy własne reguły tożsamości faktury i porównanie hashy potwierdzą, że chodzi o ten sam dokument.

Parser powinien tolerować nieznane pola. [Changelog API](https://github.com/CIRFMF/ksef-api/blob/main/api-changelog.md) stwierdza, że dodatkowe właściwości mogą pojawić się bez uznania ich za zmianę łamiącą kompatybilność. Nieznany kod statusu powinien skierować fakturę do ręcznej analizy. Nigdy nie może domyślnie oznaczać przyjęcia.

## Jak powinno działać sprawdzanie statusu w produkcji?

**W produkcji sprawdzanie statusu musi dać się wznowić, mieć granice i przestrzegać limitów.** Zapisuj każdą próbę, stosuj wykładnicze wydłużanie odstępów z losowym rozproszeniem, respektuj `Retry-After`, a nietypowo długo przetwarzane pozycje kieruj do kolejki uzgodnień zamiast odpytywać bez końca.

API publikuje limity, ale nie narzuca częstotliwości odpytywania. [Oficjalny przewodnik po limitach](https://github.com/CIRFMF/ksef-api/blob/main/limity/limity-api.md) podaje obecnie 30 żądań na sekundę, 120 na minutę i 1200 na godzinę dla statusu pojedynczej faktury. Pozostałe trasy `/sessions/*`, w tym status sesji i endpointy UPO, pozwalają na 10 żądań na sekundę, 120 na minutę i 1200 na godzinę. Odpowiedź `429` zawiera `Retry-After`.

To zestawienie ma znaczenie. Pętla wykonywana co sekundę może mieścić się w limicie sekundowym małego testu, a mimo to wyczerpać budżet godzinowy w produkcji. Wiele workerów może też uderzyć jednocześnie po wdrożeniu lub awarii. Losowe rozproszenie rozkłada te żądania w czasie, a zapisane `next_attempt_at` pozwala po restarcie wrócić do tego samego harmonogramu.

```text
wyślij fakturę
zapisz local_id, session_reference, invoice_reference, submitted_hash

powtarzaj z ograniczonym wykładniczym wydłużaniem odstępów i losowym rozproszeniem:
  response = pobierz status faktury
  zapisz code, details, extensions, checked_at

  jeśli response to 429:
    zaplanuj następną próbę według Retry-After
  w przeciwnym razie, jeśli code to 100 lub 150:
    zaplanuj następną próbę
  w przeciwnym razie, jeśli code to 200 i istnieje ksef_number:
    zapisz ksef_number i czasy statusu
    pobierz, zweryfikuj i zarchiwizuj UPO
    zakończ jako przyjętą
  w przeciwnym razie, jeśli code to udokumentowany błąd końcowy:
    zakończ jako odrzuconą i skieruj do właściwej ścieżki naprawy
  w przeciwnym razie:
    zablokuj automatyczne przyjęcie i zażądaj ręcznej analizy

jeśli przetwarzanie przekroczy termin operacyjny:
  przenieś rekord do kolejki uzgodnień
```

Termin operacyjny jest zabezpieczeniem twojego systemu, a nie wymyślonym statusem KSeF. Ma przerwać nieskończoną pracę pojedynczego workera, ale zachować rekord do późniejszego sprawdzenia. Zapisuj co najmniej czas ostatniej próby, liczbę prób, szczegóły ostatniej odpowiedzi i termin następnej. To wystarczy, żeby proces przetrwał awarię.

## Jak pobrać UPO faktury i sesji?

**UPO faktury pobieraj po osiągnięciu przez nią statusu `200`; strony zbiorczego UPO dopiero po spełnieniu warunków sesji.** UPO pojedynczej faktury może być dostępne jeszcze przed zamknięciem sesji interaktywnej.

Aktualny kontrakt OpenAPI udostępnia trzy uwierzytelnione trasy:

- `GET /sessions/{sessionReferenceNumber}/invoices/{invoiceReferenceNumber}/upo`
- `GET /sessions/{sessionReferenceNumber}/invoices/ksef/{ksefNumber}/upo`
- `GET /sessions/{sessionReferenceNumber}/upo/{upoReferenceNumber}` dla strony zbiorczego UPO

Odpowiedź statusowa może też zawierać podpisany `upoDownloadUrl` albo `downloadUrl`. Taki adres magazynu pobierz zwykłym żądaniem HTTP GET i **nie dołączaj tokenu dostępowego KSeF**. [Opisy OpenAPI](https://github.com/CIRFMF/ksef-api/blob/main/open-api.json) mówią, że pobrania przez podpisany adres nie podlegają limitom API i wygasają w terminie podanym w odpowiedzi.

To ważna granica bezpieczeństwa. Trasy API wymagają jednego z uprawnień wymienionych w aktualnym kontrakcie, między innymi `InvoiceWrite`, `Introspection`, `PefInvoiceWrite` lub `EnforcementOperations`. Starsze materiały opisujące pobranie z API bez uwierzytelnienia nie są dziś kontraktem wdrożeniowym. Uwierzytelnienie jest potrzebne dla trasy API KSeF; podpisany adres magazynu pobiera się bez tokenu dostępowego.

UPO faktury staje się dostępne, gdy dokument zostanie przyjęty i dostanie numer KSeF. Zbiorcze UPO pojawia się po zamknięciu sesji i przetworzeniu wszystkich dokumentów, gdy co najmniej jeden z nich ma numer KSeF i został trwale zapisany. Dlatego zamknięcie sesji interaktywnej nie powinno być warunkiem pobrania dostępnego już UPO pojedynczej faktury.

## Jak zweryfikować UPO przed archiwizacją?

**Archiwizuj UPO dopiero po sprawdzeniu dokładnych bajtów odpowiedzi względem hasha transportowego, schemy XML, podpisu i własnego rekordu wysyłki.** Samo poprawne sparsowanie XML jest przydatne, ale nie stanowi pełnego testu integralności ani tożsamości dokumentu.

Każda poprawna odpowiedź z fakturą lub UPO udostępnia `x-ms-meta-hash`, czyli hash SHA-256 dokumentu zakodowany w Base64. Odczytaj body jako bajty, policz SHA-256 na niezmienionej treści, zakoduj wynik w Base64 i porównaj z nagłówkiem, zanim zaczniesz przekształcać lub normalizować XML.

Następnie sprawdź dokument względem oficjalnej [schemy UPO v4-3](https://github.com/CIRFMF/ksef-api/blob/main/faktury/upo/schemy/upo-v4-3.xsd). UPO v4-3 jest wersją domyślną od 22 grudnia 2025 roku i korzysta z jednej schemy dla UPO faktury i sesji. Zawiera między innymi `TrybWysylki`, który rozróżnia tryby `Online` i `Offline`. [Changelog API](https://github.com/CIRFMF/ksef-api/blob/main/api-changelog.md) dokumentuje zmianę wersji i zachowanie hasha.

Potem zweryfikuj podpis XAdES oraz łańcuch zaufania przy użyciu właściwych materiałów Ministerstwa Finansów. Na końcu porównaj pola biznesowe podpisanego dokumentu z rekordem wysyłki: numer sesji, hash faktury, NIP sprzedawcy, lokalny numer faktury, numer KSeF, datę wystawienia, czasy przesłania i nadania numeru oraz tryb wysyłki.

Każdy z tych testów wykonuj osobno i zachowuj jego wynik. Poprawny hash odpowiedzi dowodzi, że zapisano bajty przekazane w tej odpowiedzi. Walidacja schemy potwierdza zgodność struktury. Walidacja podpisu sprawdza autentyczność i integralność w ramach modelu zaufania. Porównanie pól biznesowych potwierdza, że dokument dotyczy faktury, którą system zamierzał obsłużyć.

## Co powinien zawierać rekord dowodowy KSeF?

**Trwały rekord KSeF powinien zachować identyfikatory, historię stanów, dokładne bajty UPO oraz wyniki weryfikacji.** Sam numer KSeF nie pozwala odtworzyć, skąd system wziął swoją decyzję.

| Pole | Po co je zachować |
|---|---|
| Lokalny klucz główny i numer biznesowy faktury | Łączy dowód KSeF z rekordem księgowym |
| Hash dokładnego XML FA(3) wysłanego do KSeF | Identyfikuje dokument i pomaga sprawdzać duplikaty |
| Numery referencyjne sesji i faktury | Pozwalają sprawdzać status, pobierać UPO i analizować zgłoszenia do wsparcia |
| Numer KSeF | Zachowuje identyfikator nadany po przyjęciu |
| Ostatni kod, opis, szczegóły i rozszerzenia statusu | Utrwala oficjalny wynik oraz ustrukturyzowany kontekst błędu |
| Daty przyjęcia do przetwarzania, nadania numeru i trwałego zapisu | Nie pozwala zlać różnych zdarzeń w jeden znacznik czasu |
| Dokładne bajty XML UPO | Uniezależnia dowód od infrastruktury dostarczającej plik |
| Obliczony SHA-256 i `x-ms-meta-hash` | Dokumentuje porównanie integralności odpowiedzi |
| Wersja schemy UPO i wynik walidacji | Pokazuje, który kontrakt strukturalny sprawdzono |
| Wynik weryfikacji podpisu XAdES i łańcucha zaufania | Dokumentuje sprawdzenie autentyczności |
| Czasy pobrania i weryfikacji | Pokazuje, kiedy dowód został zebrany i sprawdzony |
| Historia prób, napraw, identyfikatorów korelacji i śledzenia | Pozwala odtworzyć awarie i je diagnozować |

Trzy czasy KSeF przechowuj w oddzielnych polach. `invoicingDate`, `acquisitionDate` i `permanentStorageDate` opisują różne zdarzenia. Nie buduj automatu stanów na ich pozornej kolejności. Podstawą decyzji pozostają kod statusu i trwałe identyfikatory.

Zachowuj również surowe bajty, nawet jeśli wyciągasz z nich pola wygodne do wyszukiwania. Klucz w magazynie obiektowym razem z hashem treści może być dobrym rozwiązaniem, o ile retencja i kontrola dostępu odpowiadają wymaganiom wobec dowodów. Wygasający adres jest tylko jedną z dróg dostarczenia obiektu, nie zastępuje samego pliku.

## Jak wrócić do pracy po błędach i wygaśnięciu linku?

**Odtwarzanie procesu zaczyna się od zapisanych identyfikatorów i statusu, nie od zapamiętanego podpisanego adresu.** Jeśli link wygaśnie, ponownie odpytaj KSeF w uwierzytelnionym procesie i pobierz aktualną ścieżkę do dokumentu.

Po problemie transportowym lub restarcie wznów pracę na podstawie zapisanego numeru referencyjnego faktury i ostatniego stanu odpytywania. Przy `429` dokładnie respektuj `Retry-After`. Dla statusów `100` i `150` kontynuuj ograniczony harmonogram. Gdy pojawi się udokumentowany błąd końcowy, przestań odpytywać i uruchom ścieżkę naprawczą dopasowaną do problemu zamiast ponawiać wysyłkę w ciemno.

Status `440` potrzebuje osobnego procesu uzgodnienia. Sprawdź ustrukturyzowane rozszerzenia, znajdź pierwotną wysyłkę i porównaj lokalną tożsamość dokumentu z niezmiennym hashem faktury. Dopiero wtedy zdecyduj, czy wcześniej przyjęty dokument jest właściwym wynikiem dla lokalnej faktury. Sam duplikat nadal oznacza błąd bieżącej próby.

Używaj sum sesji jako drugiej linii kontroli. Porównuj `invoiceCount`, `successfulInvoiceCount` i `failedInvoiceCount` z własnymi rekordami faktur. Różnica może ujawnić utracone zadanie, wynik niezapisany po callbacku albo fakturę skierowaną do analizy ręcznej. [Przewodnik po sesji i UPO](https://github.com/CIRFMF/ksef-api/blob/main/faktury/sesje/sesja-sprawdzenie-stanu-i-pobranie-upo.md) podaje oficjalne pola poziomu sesji oraz warunki dostępności dokumentów.

Na 28 sierpnia 2026 roku **KSeF API 2.6.1 jest najnowszą wersją oznaczoną jako wdrożona na PRD**. Wersja 2.7.1 trafiła na TEST 26 sierpnia 2026 roku; wdrożenie na DEMO zaplanowano na 15 września, a na PRD na 23 września. Opisane zmiany nie modyfikują procesu statusów i UPO, ale przed tą datą dokumentacja produkcyjna nie powinna nazywać 2.7.1 wersją produkcyjną. Przy wdrożeniu i każdej późniejszej aktualizacji sprawdź [oficjalny changelog](https://github.com/CIRFMF/ksef-api/blob/main/api-changelog.md).

## Jak KSeF Kit domyka obieg dowodów

**KSeF Kit stosuje ten sam proces oparty na dowodach wobec faktur ze Stripe.** Zamienia sfinalizowane faktury na FA(3), wysyła je, czeka na przyjęcie, zapisuje UPO i wpisuje numer KSeF z powrotem do metadanych Stripe.

To wdrożenie jasno zachowuje granicę operacji asynchronicznej. Finalizacja w Stripe nie staje się deklaracją, że KSeF przyjął fakturę. KSeF Kit czeka na urzędowy wynik i zachowuje dokument, który go potwierdza. [Dokumentacja procesu wysyłki](https://ksef.startupkit.app/docs/how-filing-works) pokazuje sekwencję z perspektywy produktu, a [strona KSeF Kit](https://ksef.startupkit.app/) opisuje obsługiwaną integrację ze Stripe.

Ogólna zasada jest taka sama niezależnie od tego, czy integrację budujesz, czy kupujesz: zachowaj każdą referencję, zatrzymaj automat przy każdym stanie końcowym, zweryfikuj pobrany dokument i zarchiwizuj dowód, który przetrwa swój adres URL. Dopiero wtedy udane wywołanie API staje się wynikiem, który da się później uzgodnić i obronić.

> [!CTA]
> Jeśli wystawiasz faktury przez Stripe i nie chcesz samodzielnie budować odpytywania ani archiwum dowodów, zobacz, [jak KSeF Kit wysyła i zapisuje faktury](https://ksef.startupkit.app/docs/how-filing-works).