GitHub

Status and UPO

Understand status surfaces, polling, UPO documents, and local timeouts in ksef2 workflows.

On this page

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?

QuestionSDK surfaceTypical handle
Did authentication finish?client.authenticationauthentication operation reference
Is an online or batch session still processing?session client or auth.batchsession reference number
Did one submitted invoice succeed or fail?online/batch session invoice statusinvoice reference number
Are processed invoices visible for search?auth.invoices metadata queriesfilters, pagination, KSeF number
Is an export package ready?auth.invoices export statusexport reference number
Is a token active or revoked?auth.tokenstoken operation/reference number
Did a permission operation finish?auth.permissionspermission 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,
)
python

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)
python

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)
python

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:

  1. Send or upload invoice data.

  2. Wait until KSeF processes the invoice or session.

  3. Read the UPO reference or use the invoice reference/KSeF number.

  4. 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 resolved
python

download_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:

WorkflowTypical wait helper
Online invoice acceptancesubmission.wait()
Batch processingsession.wait()
Export package readinessauth.invoices.export(...).wait()
Token activation or inspectionauth.tokens.generate(...).wait() or auth.tokens.get_status()
Permission grant or revokeoperation.wait() or auth.permissions.get_operation_status()
Certificate issuanceenrollment.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.
  • 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.