Błąd KSeF FA(3) nie jest jednym rodzajem błędu. Może oznaczać problem z HTTP, synchroniczny błąd API, asynchroniczny status faktury, status sesji albo status uwierzytelniania.

Do diagnozy potrzebne są zawsze trzy informacje: operacja, przestrzeń nazw statusu i kod. Błędy deterministyczne trzeba naprawić, duplikaty i niejednoznaczne wysyłki uzgodnić z KSeF, a ponawiać tylko operacje, które rzeczywiście nie powiodły się przejściowo.

Ten przewodnik opisuje kontrakt produkcyjnego API KSeF 2.6.1 dostępny 28 sierpnia 2026 roku. Środowisko TEST udostępnia już wersję 2.7.1, dlatego przed uznaniem którejkolwiek tabeli za stałą sprawdź [aktualną specyfikację OpenAPI dla produkcji](https://api.ksef.mf.gov.pl/docs/v2/openapi.json) i limity obowiązujące podczas działania integracji. To przewodnik techniczny, a nie porada podatkowa ani prawna.

## Co w KSeF FA(3) jest błędem walidacji?

Przyjęcie faktury przez KSeF to cały pipeline, a nie pojedyncze sprawdzenie XSD. Faktura wysyłana w sesji interaktywnej może przejść przez sześć osobnych etapów:

1. Klient serializuje fakturę biznesową do XML zgodnego z FA(3).
2. Oblicza hash i szyfruje dokładnie te bajty, które zostaną wysłane.
3. KSeF synchronicznie przyjmuje albo odrzuca żądanie API.
4. Przyjęte żądanie trafia do asynchronicznego przetwarzania faktury.
5. KSeF sprawdza plik, szyfrowanie, uprawnienia, to, czy faktura jest duplikatem, oraz wybrane reguły semantyczne.
6. Poprawna faktura dostaje numer KSeF i można dla niej pobrać UPO.

Najpierw oddziel powodzenie transportu od powodzenia faktury. `POST /sessions/online/{referenceNumber}/invoices` zwraca HTTP `202 Accepted` z numerem referencyjnym faktury.

Oznacza to tylko, że rozpoczęło się przetwarzanie. Nie potwierdza poprawnej walidacji XML, nadania numeru KSeF ani dostępności UPO.

Zapisz zwrócony numer referencyjny faktury, zanim zrobisz cokolwiek więcej. Odpytuj o status faktury, a wynik sesji traktuj jako osobny sygnał.

Produkcyjna specyfikacja OpenAPI pokazuje nawet sesję ze statusem `200`, w której jest dziesięć faktur: osiem poprawnych i dwie odrzucone. Powodzenie sesji nie dowodzi, że każda zawarta w niej faktura została przyjęta.

Równie częsty jest błąd w drugą stronę. HTTP `200` z endpointu statusu mówi, że udało się samo zapytanie o status, a nie że faktura została przyjęta. Treść odpowiedzi nadal może zawierać status przetwarzania `440`, `450` lub inny błąd.

## Dlaczego kod KSeF bez kontekstu nic nie znaczy?

Załóżmy, że w logu widzisz tylko `KSeF error 440`. Nadal nie wiadomo, co się stało.

- Status faktury `440` oznacza duplikat faktury.
- Status sesji `440` oznacza anulowaną sesję, na przykład po przekroczeniu czasu albo wtedy, gdy nie zawierała faktur.
- Status faktury `450` oznacza błąd walidacji semantycznej.
- Status uwierzytelniania `450` oznacza nieprawidłowy token.

Te wartości nie tworzą jednego globalnego rejestru kodów błędów. Należą do konkretnych modeli odpowiedzi. Przydatny zapis diagnostyczny musi więc pokazywać kontekst, w którym wystąpił kod.

| Co zachować | Dlaczego to ważne |
|---|---|
| Środowisko i wersja API | TEST i produkcja mogą działać na różnych kontraktach |
| Operacja lub endpoint | Wskazuje właściwą przestrzeń nazw statusu |
| Status HTTP | Pokazuje, czy udało się samo żądanie i połączenie, a nie przetwarzanie faktury |
| Kod wyjątku lub przetwarzania | Klasyfikuje błąd w danej przestrzeni nazw |
| Opis, `details` i `extensions` | Zawierają przydatną diagnostykę serwera oraz pierwotne numery referencyjne |
| Numery referencyjne sesji i faktury | Pozwalają wznowić odpytywanie o status i uzgodnić niepewny wynik |
| Dokładny hash i rozmiar XML | Łączą odpowiedź z bajtami przeznaczonymi do wysłania |

Nadaj całemu zestawowi danych jeden wewnętrzny identyfikator korelacji i dołączaj go do każdego ponowienia oraz odpytania. Panel może wtedy grupować błędy bez utraty danych potrzebnych do odtworzenia konkretnego przypadku.

Osobno przechowuj też synchroniczne wyjątki żądań i asynchroniczne statusy faktur. Przy wysyłce w sesji interaktywnej aktualna wersja API zwraca w HTTP `400` między innymi nieprawidłowy stan sesji (`21180`), niezgodny rozmiar (`21402`), niezgodny hash (`21403`) i błąd walidacji żądania (`21405`).

Gdy ustawisz `X-Error-Format: problem-details`, obsługiwane błędy żądań mogą korzystać ze struktury Problem Details. Żaden z tych kodów nie należy do tabeli statusów faktury.

## Które statusy faktury oznaczają oczekiwanie, naprawę, uzgodnienie lub ponowienie?

Każdy status faktury powinien prowadzić do jednej z czterech reakcji: czekaj, napraw, uzgodnij albo ponów. Korzystaj ze statusu zwróconego przez endpoint konkretnej faktury, a nie ze statusu HTTP żądania GET.

| Status faktury | Znaczenie w produkcji 2.6.1 | Domyślna reakcja |
|---|---|---|
| `100`, `150` | Przyjęto do dalszego przetwarzania / trwa przetwarzanie | Poczekaj i odpytaj ponownie z wydłużanym opóźnieniem |
| `200` | Przetworzono poprawnie | Zapisz numer KSeF, pobierz i zachowaj UPO |
| `405` | Anulowano z powodu błędu sesji | Najpierw sprawdź błąd sesji |
| `410` | Nieprawidłowy zakres uprawnień | Napraw autoryzację; nie zmieniaj XML na ślepo |
| `415` | Nie można wysłać faktury z załącznikiem | Napraw uprawnienie do załączników albo formę faktury |
| `430` | Weryfikacja pliku faktury nie powiodła się | Sprawdź bajty, XML, schemat, limity, hash i powiązane reguły pliku |
| `435` | Odszyfrowanie nie powiodło się | Napraw obsługę klucza i szyfrowania |
| `440` | Duplikat faktury | Uzgodnij z pierwotną sesją i numerem KSeF |
| `450` | Walidacja semantyczna nie powiodła się | Napraw dane faktury według zwróconych szczegółów |
| `500` | Nieznany status wewnętrzny | Zachowaj diagnostykę i uzgodnij wynik przed kontrolowaną próbą odzyskania |
| `550` | Przetwarzanie anulowano wewnętrznie | Uzgodnij wynik, a potem ponów zgodnie z ograniczoną polityką |

Traktuj tę tabelę jako punkt wyjścia do obsługi, nie zamiennik odpowiedzi. Zachowuj surowy opis, komplet `details` i wszystkie `extensions`.

Ministerstwo nie publikuje stałego, wyczerpującego katalogu, który przypisywałby każdy możliwy szczegół kodu `430` lub `450` do XPath. Nowe i nieznane stany obsługuj defensywnie, zamiast budować kruchy parser zależny od dzisiejszych opisów.

Status sesji wciąż ma znaczenie, ale odpowiada na inne pytanie. Błąd archiwum, odszyfrowania, przekroczenia czasu albo pakietu na poziomie sesji może anulować jej faktury. Po przetworzeniu sesji sprawdź liczbę faktur przyjętych i odrzuconych, a następnie stan każdej z nich. Gdy część faktur z partii została odrzucona, endpoint odrzuconych faktur najszybciej prowadzi do diagnostyki.

## Jak sprawdzić XML FA(3) przed wysyłką?

Od 1 lutego 2026 roku FA(3) jest jedynym schematem faktury ustrukturyzowanej dopuszczonym przy nowych wysyłkach. Dotyczy to także korekt faktur wystawionych wcześniej w FA(1) lub FA(2). Otwórz sesję z `systemCode: "FA (3)"`, `schemaVersion: "1-0E"` oraz `value: "FA"`, po czym waliduj dokument względem [oficjalnego XSD FA(3)](https://github.com/CIRFMF/ksef-api/blob/main/faktury/schemy/FA/schemat_FA%283%29_v1-0E.xsd).

Waliduj dokładnie te bajty, dla których obliczysz hash i które zaszyfrujesz. Ministerialny [przewodnik po weryfikacji faktury](https://github.com/CIRFMF/ksef-api/blob/main/faktury/weryfikacja-faktury.md) wymaga XML 1.0, UTF-8 bez znacznika kolejności bajtów, schematu zadeklarowanego podczas otwierania sesji, braku sprzecznej deklaracji kodowania i instrukcji przetwarzania oraz wyklucza wskazane niezalecane zakresy Unicode.

Jeśli dodasz opcjonalną strukturę, jej wymagane pola potomne stają się obowiązkowe.

W integracji Rails korzystającej z Nokogiri lokalne sprawdzenie może zacząć się tak:

```ruby
schema = Nokogiri::XML::Schema(File.read("schemat_FA(3)_v1-0E.xsd"))
bytes = File.binread("invoice.xml")

raise "UTF-8 BOM is not allowed" if bytes.start_with?("\xEF\xBB\xBF".b)

document = Nokogiri::XML(bytes) { |config| config.strict.nonet }
errors = schema.validate(document)

raise errors.map(&:message).join("\n") if errors.any?
```

Ten kod wykryje błędy składni XML i naruszenia XSD. **Nie** odtwarza jednak wszystkich kontroli wykonywanych po stronie serwera. Dodaj w aplikacji co najmniej sprawdzenie:

- danych identyfikacyjnych sprzedawcy oraz numeru faktury nadanego przez twój system numeracji;
- dat, zwłaszcza tego, czy `P_1` nie przypada później niż przyjęcie faktury przez KSeF;
- warunkowych struktur FA(3) i arytmetyki biznesowej;
- limitów rozmiaru pliku i liczby faktur w sesji;
- uprawnienia do załączników, jeśli ma zastosowanie;
- rozmiarów oraz hashy SHA-256 jawnych i zaszyfrowanych bajtów przekazanych w metadanych;
- szyfrowania z aktualnym kluczem publicznym KSeF i udokumentowanymi algorytmami.

Nie rezygnuj z walidacji reguł biznesowych nawet wtedy, gdy KSeF zwraca `200`. Ministerstwo w [pytaniach i odpowiedziach dotyczących KSeF](https://ksef.podatki.gov.pl/pytania-i-odpowiedzi-ksef-20/) wskazuje, że system może przyjąć fakturę z błędami rachunkowymi lub z nieprawidłowym NIP-em kontrahenta, który ma poprawną sumę kontrolną.

Przyjęcie przez serwer dowodzi, że KSeF przyjął fakturę ustrukturyzowaną, nie że dane księgowe są poprawne.

TEST, DEMO i produkcja również dowodzą różnych rzeczy. TEST jest zanonimizowany i nie wywołuje skutków prawnych. DEMO korzysta z prawdziwego uwierzytelniania, ale także nie wywołuje skutków prawnych. Produkcja je wywołuje. Część kontroli, w tym wybrane kontrole sumy NIP, działa tylko w produkcji. Poprawny wynik w TEST potwierdza działanie integracji w TEST, lecz nie gwarantuje powodzenia w produkcji.

## Jak diagnozować status 450 bez zgadywania?

Status `450` traktuj jako informację o semantyce faktury. Zachowaj tyle danych wejściowych, aby można było dokładnie odtworzyć błąd. Nie zmieniaj pól na chybił trafił tylko po to, by komunikat zniknął.

1. Zapisz cały obiekt statusu wraz z każdym szczegółem zwróconym przez KSeF.
2. Znajdź niezmienny zapis danych źródłowych użyty do zbudowania faktury.
3. Porównaj zapisany hash XML z bajtami wysłanymi pod danym numerem referencyjnym faktury.
4. Ponownie uruchom lokalne walidatory XSD i reguł biznesowych dla tego zapisu.
5. Powiąż zwrócony szczegół z polem FA(3) oraz wartością w systemie źródłowym, z której to pole powstało.
6. Napraw źródło albo mapper, wygeneruj nowy XML i od początku sprawdź nowe bajty.

Odrzucony XML nie został wystawiony. Ministerstwo zaleca naprawę i ponowne przesłanie prawidłowego XML; nie jest to korekta przyjętej faktury.

To rozróżnienie wpływa na sposób ponawiania wysyłki. Naprawa tworzy nową próbę, ale jej tożsamość biznesowa i audit trail nadal muszą prowadzić do nieudanej próby.

Jeśli te same dane przechodzą w TEST, a odpadają w produkcji, najpierw sprawdź różnice między środowiskami w autoryzacji, uprawnieniach, tożsamości i walidacji. Dopiero potem rozważ osłabienie lokalnego walidatora. Nigdy nie wysyłaj próbnej faktury do produkcji tylko po to, żeby zobaczyć wynik: powodzenie wywoła skutki prawne.

## Dlaczego duplikat ze statusem 440 wymaga uzgodnienia?

KSeF rozpoznaje duplikat na podstawie trzech pól biznesowych: NIP-u sprzedawcy (`Podmiot1:NIP`), rodzaju faktury (`RodzajFaktury`) i numeru faktury (`P_2`). Udokumentowany okres unikalności trwa przez dziesięć pełnych lat po końcu roku, w którym wystawiono fakturę.

Status `440` nie dowodzi, że bajty XML są identyczne. Oznacza, że KSeF przyjął już fakturę z tym samym zestawem pól biznesowych. Status może zawierać `originalSessionReferenceNumber` i `originalKsefNumber` — wykorzystaj je.

Postępuj tak:

1. Odczytaj pierwotny numer KSeF i numer referencyjny sesji z rozszerzeń statusu.
2. Porównaj pierwotną fakturę z zamierzoną transakcją źródłową.
3. Pobierz i zweryfikuj pierwotne UPO.
4. Oznacz lokalną próbę jako uzgodnioną z przyjętą fakturą.
5. Eskaluj sprawę, jeśli przyjęta faktura nie odpowiada zamierzonej transakcji biznesowej.

Nie zwiększaj `P_2` tylko po to, żeby pozbyć się błędu. Jeśli pierwotne żądanie się powiodło, lecz jego odpowiedź zginęła, zmiana numeru może utworzyć drugą fakturę wywołującą skutki prawne. Oddziel numerację biznesową od prób transportowych: jedna faktura może mieć kilka zapisów prób, ale ponowienie nie powinno po cichu tworzyć nowego dokumentu biznesowego.

W tym miejscu pojawia się warunek wyścigu. Jeśli osobne zespoły lub jednostki wystawiające korzystają z jednego NIP-u sprzedawcy, muszą wspólnie koordynować numerację faktur. Unikalność w obrębie każdej aplikacji nie chroni przed duplikatem według globalnego kryterium KSeF.

## Które błędy KSeF można bezpiecznie ponawiać?

Bezpieczna polityka ponowień zaczyna się od ustalenia warstwy, w której wystąpił błąd.

**Nie ponawiaj bez zmian deterministycznych błędów danych wejściowych.** Błędy XML/XSD, niezgodne rozmiary lub hashe, problemy z uprawnieniami, brak uprawnienia do załączników, błędy odszyfrowania i status semantyczny `450` wymagają naprawy. Ponowne wysłanie tych samych bajtów w tych samych warunkach tylko zaśmieci diagnostykę, zużyje limity i nie dostarczy nowych informacji.

**Nie ponawiaj na ślepo duplikatu ze statusem `440`.** Uzgodnij go z pierwotnie przyjętą fakturą.

**Po HTTP `429` odczekaj pełny czas z `Retry-After`.** Limity KSeF nakładają się na siebie w przedziałach jednej sekundy, jednej minuty i jednej godziny. Kolejne wywołania w czasie blokady mogą ją wydłużyć.

Koordynuj workery korzystające z tego samego kontekstu uwierzytelniania i adresu IP, dodaj losowe opóźnienie przed wznowieniem zadań z kolejki i podczas działania sprawdzaj `GET /rate-limits`, zamiast zakładać, że opublikowane wartości domyślne dotyczą twojego konta.

**Po przekroczeniu czasu i błędach HTTP `5xx` wynik może być niejednoznaczny.** Odpowiedź może zginąć już po zapisaniu żądania przez serwer.

Produkcyjna specyfikacja OpenAPI nie opisuje dostarczanego przez klienta klucza idempotencji ani dla wysyłki w sesji interaktywnej, ani dla zamknięcia sesji wsadowej. Poniższe kroki są więc zaleceniem technicznym, nie gwarancją KSeF:

- przed wysyłką zapisz numer referencyjny sesji, hash i rozmiar faktury oraz timestamp próby;
- po niejednoznacznym wyniku wysyłki interaktywnej sprawdź tę samą sesję i uzgodnij listę faktur, zanim powtórzysz operację;
- po niejednoznacznym wyniku zamknięcia sesji wsadowej odpytaj o tę sesję i zamknij ją ponownie tylko wtedy, gdy nadal jest otwarta;
- jeśli uzgodnienie nie znajdzie zapisanego wyniku, wydłużaj opóźnienie wykładniczo, dodaj losowy rozrzut i ogranicz liczbę prób;
- po wyczerpaniu budżetu ponowień skieruj próbę do ręcznej analizy, zachowując komplet dowodów.

Status faktury `550` wprost zaleca ponowienie, ale „można ponowić” nie znaczy „ponawiaj bez końca”. Zachowaj diagnostykę, uzgodnij próbę i zastosuj tę samą ograniczoną politykę odzyskiwania. Przy statusie `500` nie pomyl biznesowego kodu przetwarzania z HTTP `500`. Zachowaj przestrzeń nazw i zbadaj przypadek przed podjęciem decyzji.

Odpytywanie o status również wymaga umiaru. Kontynuuj je dla `100` i `150`, wydłużaj przerwy i zakończ po osiągnięciu stanu końcowego. Stała jednosekundowa pętla z przykładowego klienta nie jest oficjalnym SLA przetwarzania.

## Jakie dowody powinna zachowywać integracja produkcyjna?

Gdy faktura zostanie przyjęta, sam zielony status w panelu nie wystarcza. Zachowaj trwały zapis łączący transakcję biznesową z tym, co przyjął KSeF:

- niezmienny zapis danych źródłowych oraz wersję mappera i schematu;
- dokładny hash i rozmiar bajtów jawnego XML;
- metadane szyfrowania oraz hash i rozmiar zaszyfrowanych danych;
- środowisko i zaobserwowaną wersję API;
- numery referencyjne sesji i faktury;
- historię statusów z timestampami, opisami, szczegółami i rozszerzeniami;
- numer faktury KSeF;
- XML UPO oraz jego wartość integralności SHA-256/Base64;
- powiązania między fakturą biznesową, każdą próbą wysyłki i przyjętym wynikiem.

UPO faktury staje się dostępne dopiero po jej poprawnym przetworzeniu i można je pobrać, gdy sesja nadal jest otwarta. Zbiorcze UPO sesji pojawia się po jej zamknięciu i obejmuje tylko przyjęte faktury.

Sesja z częściowym powodzeniem może więc zawierać jednocześnie UPO i odrzucone faktury. Nie używaj skrótu myślowego „sesja ma UPO”, gdy chcesz powiedzieć „wszystkie faktury zostały przyjęte”.

KSeF może zwrócić w odpowiedzi statusowej tymczasowy URL do pobrania UPO. Ten adres wygasa i nie jest trwałym dowodem. Pobierz podpisany XML, zweryfikuj wartość `x-ms-meta-hash` zwróconą przez uwierzytelniony endpoint i zachowaj plik zgodnie z polityką przechowywania dowodów.

## Jak KSeF Kit obsługuje cykl wysyłki

[KSeF Kit](https://ksef.startupkit.app/) to osobny produkt dla zespołów, które wysyłają faktury ze Stripe do polskiego systemu KSeF.

Udokumentowany [proces wysyłki](https://ksef.startupkit.app/docs/how-filing-works) rozdziela te same etapy, które zaleca ten przewodnik: utrwala dane źródłowe, mapuje je do FA(3), wysyła w szyfrowanej sesji interaktywnej, odpytuje o wynik, zapisuje osobne próby wysyłki, wznawia oczekiwanie na podstawie zachowanych numerów referencyjnych, przechowuje UPO i przekazuje numer KSeF do Stripe.

Nie zwalnia to z rozumienia przyczyny odrzucenia. Każdy błąd ma jednak trwałe miejsce w procesie ze śledzeniem stanu, zamiast zniknąć wewnątrz nieudanego żądania HTTP. Zespoły budujące własną integrację mogą zastosować ten sam model: niezmienne dane wejściowe, jawne próby, numery referencyjne pozwalające wznowić pracę, obsługa statusów z uwzględnieniem operacji i uzgodnienie przed powtórzeniem.

Jeśli źródłem faktur jest Stripe i wolisz korzystać z gotowego procesu niż budować go od zera, zobacz [jak KSeF Kit łączy się ze środowiskami](https://ksef.startupkit.app/docs/connecting-ksef) oraz [materiały o rozwiązywaniu problemów](https://ksef.startupkit.app/docs/troubleshooting). Niezależnie od wybranej drogi zasada produkcyjna jest ta sama: kod bez operacji nie jest diagnozą, a ponowienie bez uzgodnienia nie jest planem odzyskania.