Blog— Anton Ignashev

KSeF API 2.0 error codes: 440, 450, 21405 and the rest — what they mean and how to fix them in enova365, Optima and Subiekt

KSeF API 2.0 error codes: 440, 450, 21405 and the rest — what they mean and how to fix them in enova365, Optima and Subiekt

Why This Post Exists

Seven months into the KSeF mandate, the questions I get from accounting teams and integrators have changed. Nobody asks any more whether their ERP can send. They paste a number. "We got 440." "Optima says rejected, what's 450?" "The integrator says 21405, is that us or them?"

The answers are in the Ministry of Finance's own API specification, the open-api.json and the documentation in the official CIRFMF/ksef-docs repository on GitHub. But they're scattered across schema descriptions, and the one thing that trips everyone up isn't written anywhere in plain words: KSeF reports errors on three separate levels, and the same number means different things on each.

This post puts them in one place. Everything in the tables comes from the spec as of November 2026. Where I describe what enova365, Optima or Subiekt show on screen, that's from working with them, and versions differ, so check yours.

Three Levels, One Number Space

When something goes wrong, KSeF tells you on one of three levels:

  1. An exception on the HTTP response. The request itself was refused. The body carries exception.exceptionDetailList[] with a five-digit exceptionCode (21xxx, 25xxx). Nothing was queued. There is no invoice status, because there is no invoice in the system.
  2. A session status. You opened an interactive or batch session, and the session as a whole failed, for example it timed out with nothing in it.
  3. An invoice status. The invoice was accepted for processing and then rejected. The status object has a three-digit code, a description, details[], and for some codes extensions.

The three-digit codes overlap. 440 on an invoice is a duplicate. 440 on a session is a cancelled session. 440 on a permissions operation is "not allowed for these identifier relationships". 450 on an invoice is a semantic error; 450 on an authentication is a bad token. If your log line says "KSeF error 440" without saying which object it came from, the log line is useless, and I see that in most homemade integrations.

Invoice Status Codes

These are the ones accountants actually see, because ERPs map them to "rejected".

Code What the spec says What it usually means Fix
100 / 150 Accepted / processing Not an error. Still in the queue Wait, poll again
200 Success KSeF number assigned Store number, fetch UPO
405 Cancelled due to session error The session failed, not this invoice Fix the session, resend
410 Wrong permission scope The credential can't issue for this NIP Grant the right permission in KSeF
415 Can't send an invoice with an attachment Attachments not enabled, or sent in an interactive session Enable in e-Urząd Skarbowy, send in batch
430 Invoice file verification error Not valid XML, BOM, wrong encoding, schema mismatch Fix the generator
435 File decryption error AES/RSA encryption done wrong Integration bug, not the accountant's
440 Duplicate invoice Same NIP + RodzajFaktury + P_2 already in KSeF Read originalKsefNumber first
450 Semantic verification error Dates, NIP checksums, business rules Fix the data, resend
500 Unknown error Their side, or something unclassified Retry later, then report with reference number
550 Cancelled by the system Internal interruption Retry

A few of these need more than a table cell.

430 is almost always the generator. The verification doc is strict: UTF-8 without a BOM (the three bytes 0xEF 0xBB 0xBF at the start of a file are enough to fail it), no processing instructions, no prolog declaring another encoding, and a set of discouraged Unicode ranges that turn up when someone pastes a product name from a PDF. Size limits sit here too: 1 MB without attachments, 3 MB with.

450 is the one you fix in the data. Two rules cause most of the cases I've seen. The issue date P_1 can't be later than the moment KSeF accepts the document, so an invoice post-dated to tomorrow fails. And NIP checksums are validated for Podmiot1, Podmiot2, Podmiot3 and PodmiotUpowazniony, but only on production. That last detail means a file can pass every test on the test environment and fail on day one in production. If your test data uses made-up NIPs, you haven't tested this rule.

415 surprises people because the fix isn't technical. Sending invoices with attachments has to be registered in advance in e-Urząd Skarbowy, and it only works in batch mode. The single exception is a technical correction of an offline invoice.

The 440 Trap

This is the error that justifies the whole post.

KSeF detects duplicates globally, on the combination of seller NIP (Podmiot1:NIP), invoice type (RodzajFaktury) and invoice number (P_2), and it keeps that uniqueness for 10 full years from the end of the year the invoice was issued. So 440 means: an invoice with this number from this seller already exists in KSeF.

The reflex fix is to give the invoice a new number and resend. That's usually wrong.

The most common route to a 440 goes like this: the first send succeeded, KSeF assigned a number, and your ERP never recorded the answer. A timeout, a service restart, a status poll that never ran. The document sits in the books without a KSeF number, someone clicks "send" again, and KSeF correctly says it already has it. Renumber and resend at that point, and you've issued a second invoice for the same sale. Now there are two legal invoices in the national system, the buyer can see both, and one of them needs a correction to get rid of.

The spec gives you the way out. A 440 invoice status carries extensions with originalKsefNumber and originalSessionReferenceNumber. Read them. If the original is this same invoice, attach that KSeF number to the document and download its UPO. Done, no new invoice.

The other cause is a genuine collision: two units issuing for the same NIP with colliding series. Branches, organisational units of a local authority, an online shop and an ERP numbering in parallel. The spec says outright that such units have to agree on numbering. There, the invoice really does need a new number, and the series need fixing so it doesn't happen next week.

Here's the decision as code, the way I write it in a reconciliation job:

def handle_invoice_status(doc, status):
    code = status["code"]
    if code == 200:
        return mark_sent(doc, ksef_number=status_ksef_number(status))
    if code in (100, 150):
        return poll_later(doc)
    if code == 440:
        ext = status.get("extensions") or {}
        original = ext.get("originalKsefNumber")
        if original and same_invoice(doc, original):   # compare hash, amounts, buyer
            return mark_sent(doc, ksef_number=original)  # recover, don't renumber
        return escalate(doc, "number collision: fix the series, then renumber")
    if code in (500, 550):
        return retry_later(doc)
    return escalate(doc, f"{code}: {status['description']} {status.get('details')}")

same_invoice is where the care goes: fetch the original by its KSeF number and compare the buyer, the totals and ideally the hash of the XML you hold. Matching only on the number is the bug you're trying to avoid.

Session Codes and the 21xxx Exceptions

Session statuses matter mostly to whoever runs the integration. In a batch session, 405 is a package verification error, 415 and 435 are decryption failures, 420 means the session limit of 10,000 invoices was exceeded, 430 is a decompression error, 440 is a cancelled session (upload timed out, or no invoices were sent), and 445 means verification found no valid invoices at all. An interactive session has the same 415, 440 and 445.

The five-digit exceptions arrive on the HTTP response and mean the request never got in:

Exception Meaning
21401 Document doesn't match the XSD schema
21402 / 21403 File size or hash doesn't match what you declared
21405 Input data validation error (details carry the validator's message)
21155 Invoice limit for the session reached
21180 Session status doesn't allow the operation (e.g. sending into a closed session)
21208 Upload/finish wait time exceeded, session cancelled
21164 / 21165 Invoice with that KSeF number doesn't exist / isn't available yet
21184 Session temporarily unavailable, maintenance

21165 deserves a note for anyone building purchase-invoice import: an invoice can be processed and still not be downloadable for a while. Treat it as "retry later", not as "doesn't exist".

429 Is Not a Bug

KSeF rate-limits every protected endpoint. Requests are counted per pair of context and IP address: the NIP you authenticated for, plus the address you connect from. Windows slide per second, minute and hour, and all three apply at once. Exceed any of them and you get HTTP 429 with a Retry-After header in seconds. The block length is dynamic and grows with repeated violations.

Two consequences for an accounting office. An office pulling invoices for a client from its own IP has a separate budget from the client pulling from theirs, so they don't starve each other. Rotating many IPs on one context to get around the limit is another matter: the documentation says outright that it may be treated as a security threat. The spec also mentions higher download limits between 20:00 and 06:00, which is where nightly import jobs belong anyway.

What enova365, Optima and Subiekt Show You

None of the three shows you the full picture on the invoice list. In practice, that's the real problem.

Optima's documented KSeF filter has statuses like "Nie wysłano", "Oczekuje na odbiór UPO", "Odebrano UPO", "Awaria" and "Odrzucono przez KSeF". enova365 and Subiekt likewise collapse the outcome into a document-level state. That works for 200. For everything else, "rejected" hides whether you're looking at a 430 (call the vendor or integrator), a 450 (fix the data), a 410 (fix permissions) or a 440 (probably already sent, don't touch the number). The code and the details are in the KSeF communication log or the document's KSeF history in all three, not in the list column.

So my standing advice to offices: when a client says "KSeF rejected it", ask for the three-digit code and the details line before anything else. It takes the client one minute to find and saves an hour of guessing. And for the integrations I build, whether a WebAPI job in enova365, a Sfera service on Subiekt GT or anything on Optima, every rejection is logged with the level, the code, the details and the extensions, in one place someone actually reads.

That last part is the same lesson as the three quiet KSeF states from September: a rejection that nobody reads is just an invisible invoice with extra steps. A nightly read-only job that lists every document with a non-200 status, its code and its age in days is the cheapest KSeF control there is. For 440s, it can propose the recovered KSeF number for a person to confirm.

If you're seeing KSeF rejections you can't classify, or 440s that someone has been "fixing" with new numbers, get in touch. The reconciliation job that sorts them takes a day or two to set up on enova365, Optima or Subiekt, and it's read-only from the first hour.

Check a Polish VAT counterparty in seconds, free

KsięgoAI checks a NIP against the VAT white list and VIES, scores the risk and saves proof of due diligence. Works in Telegram and Claude.

Try KsięgoAI→

Let’s talk about your project

Free 30-minute consultation. We’ll figure out if and how I can help.

Book a Free 30-Minute Call

Select a date

November 2026
Mon
Tue
Wed
Thu
Fri
Sat
Sun
← Back to Blog

Related Posts

What Symfonia costs in 2026: the price list for Finanse i Księgowość, the cloud, eBiuro and accounting-office bundles
Blog

What Symfonia costs in 2026: the price list for Finanse i Księgowość, the cloud, eBiuro and accounting-office bundles

Symfonia Finanse i Księgowość costs 1 620 to 3 550 zł net a year in 2026 and is sold only as a subscription. The cloud ERP is 490 zł per user per month, eBiuro for accounting offices starts at 409 zł a year, and the office bundles cost 1 890, 3 696 or 11 694 zł. The full list from symfonia.pl, KSeF Plus limits from 2027 and a three-year comparison.

Read more→
PIT-11, PIT-4R, PIT-8AR and IFT-1R for 2026: the 1 February and 1 March 2027 deadlines and the three totals that must agree
Blog

PIT-11, PIT-4R, PIT-8AR and IFT-1R for 2026: the 1 February and 1 March 2027 deadlines and the three totals that must agree

The payer's annual returns for 2026 go to the tax office by Monday 1 February 2027, because 31 January is a Sunday. PIT-11 to employees and IFT-1R follow by 1 March. Before anything goes out, three totals have to agree: PIT-11s against PIT-4R, PIT-4R against the bank statement, and flat-rate tax against PIT-8AR. Correct a PIT-11 after mid-February and it lands on an employee whose Twój e-PIT is already prefilled.

Read more→
Accounting policy for 2027: KSeF, automatic entries and AI agents — what's missing from the IT system description
Blog

Accounting policy for 2027: KSeF, automatic entries and AI agents — what's missing from the IT system description

A KSeF invoice is an XML file nobody can write a posting instruction on. Art. 21(1a) of the Accounting Act lets you skip it, but only when the way entries are documented makes it unnecessary. Art. 20(5) sets four conditions for automatic entries, and art. 14(4) requires a responsible person for every entry. What to add to a client's accounting policy before 1 January 2027.

Read more→