KSeF workflows are asynchronous. Many SDK calls start work and return a reference number; a different call tells you what happened later.
Do not reduce this to one done flag. Each status surface answers a different
question and has a different reference number.
Which status do you need?
| Question | SDK surface | Typical handle |
|---|---|---|
| Did authentication finish? | client.authentication | authentication operation reference |
| Is an online or batch session still processing? | session client or auth.batch | session reference number |
| Did one submitted invoice succeed or fail? | online/batch session invoice status | invoice reference number |
| Are processed invoices visible for search? | auth.invoices metadata queries | filters, pagination, KSeF number |
| Is an export package ready? | auth.invoices export status | export reference number |
| Is a token active or revoked? | auth.tokens | token operation/reference number |
| Did a permission operation finish? | auth.permissions | permission operation reference |
Use the surface that matches the question. A session can be complete while one invoice inside it failed. An export can still be preparing even though invoice metadata is already visible. A token operation can finish independently from any invoice-sending session.
Session status
Session status is KSeF's summary of a remote online or batch workflow. After processing, it can include counters and UPO page references.
status = session.get_status()
print(
status.status.code,
status.invoice_count,
status.successful_invoice_count,
status.failed_invoice_count,
)The status code belongs to the session workflow, not necessarily to every
invoice inside it. A session summary can say that processing completed and still
report failed invoices in failed_invoice_count. Inspect session invoices when
you need per-invoice details.
failed = session.list_failed_invoices()
for invoice in failed.invoices:
print(invoice.reference_number, invoice.status.code, invoice.status.details)Invoice status
Invoice status is scoped to one invoice in one session. For online sending, you
usually get the invoice reference number immediately from send_invoice(). For
batch sending, per-invoice status becomes visible after KSeF processes the
uploaded package.
An accepted invoice status has a ksef_number. A failed invoice status carries
the KSeF status code, description, and optional details.
invoice_status = session.get_invoice_status(
invoice_reference_number=sent.reference_number,
)
print(invoice_status.ksef_number, invoice_status.status.description)UPO documents
UPO is the official confirmation document. It is XML signed by the Ministry of Finance. Treat the returned bytes as an audit artifact and store them durably.
UPO is not available at send time:
-
Send or upload invoice data.
-
Wait until KSeF processes the invoice or session.
-
Read the UPO reference or use the invoice reference/KSeF number.
-
Download and store the UPO XML bytes.
The SDK exposes the common UPO paths through session clients:
invoice_upo = submission.download_upo()
session_upo_pages = session.download_upo() # every page, references resolveddownload_upo() never waits. Call submission.wait() first for an invoice UPO,
or session.wait() (after the session is closed) for session UPO pages. Asked
too early, submission.download_upo() raises KSeFNotReadyError, and the session
variants raise it while KSeF is still processing the closed session. A session that is
still open raises KSeFSessionError. Both carry a hint that names wait().
submission.download_upo() makes exactly one request; the session variants read
the session status once and then download each UPO page.
Status responses can also expose UPO page data such as
download_url_expiration_date. Use references for SDK calls and store the
downloaded XML once you have it.
Polling and timeouts
Wait helpers poll KSeF until one of three things happens:
- the target state appears;
- KSeF reports a terminal failure that the SDK can classify;
- the local timeout expires.
A local timeout does not prove the remote operation failed. It only means the target state was not observed before your deadline.
Choose polling settings based on the workflow:
| Workflow | Typical wait helper |
|---|---|
| Online invoice acceptance | submission.wait() |
| Batch processing | session.wait() |
| Export package readiness | auth.invoices.export(...).wait() |
| Token activation or inspection | auth.tokens.generate(...).wait() or auth.tokens.get_status() |
| Permission grant or revoke | operation.wait() or auth.permissions.get_operation_status() |
| Certificate issuance | enrollment.wait() or auth.certificates.get_enrollment_status() |
What to persist
Persist enough information to inspect status later without the original Python object:
- the session reference number;
- the invoice reference number;
- the KSeF number after acceptance;
- the UPO reference or UPO XML bytes;
- the export or operation reference number for non-session workflows;
- the last observed status payload for audit.
Related pages
- Get status and UPO: Poll online and batch sessions and retrieve UPO XML.
- Sessions: Understand the remote workflow contexts that expose status.
- Invoice lifecycle: Follow an invoice from submission through KSeF number, metadata, download, and UPO.
- Error handling: Handle rejected, failed, expired, and retryable KSeF operations.
