What KSeF is, in one paragraph
KSeF — Krajowy System e-Faktur — is the Polish Ministry of Finance's central e-invoicing gateway. Every B2B invoice issued by Polish taxpayers is moving onto it, both directions. GetUp speaks KSeF API v2 directly: you authenticate once per company, the platform builds or validates an FA(3) XML, encrypts it inside an interactive session, ships it to the gateway, and stores both the structured XML and a metadata row you can search.
Quick facts:
- Schema: FA(3) · 1-0E (CRD 2025/06/25/13775).
- Currency: PLN.
- Up to 500 line items per invoice.
- VAT bands aggregated automatically: 23/22, 8/7, 5.
- One connection per company NIP.
- Inbound + outbound + audit log + ZIP export.
Before you start — four prerequisites
Each maps to something tangible in the workspace; none of this is paperwork-only.
- An active company with a Polish NIP. The company you'll be invoicing from must have a 10-digit Polish NIP saved on its profile (Settings → Company → Tax IDs). KSeF binds every session to a single NIP — multi-company workspaces register one connection per company.
- An authentication method. Choose one: a KSeF-issued token tied to your NIP (created in the Ministry of Finance portal, encrypted client-side with the MF public key before being sent), or a qualified XAdES electronic signature / e-seal. The KSeF token is the simpler path for most operators.
- A refresh token from the KSeF gateway. After the first successful authentication, KSeF issues a long-lived refresh token plus a short-lived access token. GetUp stores the refresh token AES-256-GCM-encrypted at rest and uses it to mint fresh access tokens on demand — you never paste the access token by hand.
- The KSeF module enabled. KSeF is part of the Pro and Enterprise plans. Once your plan includes it, the Inbound and Outbound pages appear automatically in the company portal sidebar and on the accountant portal for assigned accountants — no separate per-module toggle.
Pick the right environment
KSeF exposes three independent gateways. Choose by intent — and never go to PROD before you've sent a real test invoice through DEMO.
| Environment | Base URL | When to use |
|---|---|---|
| DEMO | api-demo.ksef.mf.gov.pl/v2 | First connection, smoke tests, screen demos. No real fiscal effect. Authenticate with MF demo credentials or the KSeF-published test profile. |
| TEST | api-test.ksef.mf.gov.pl/v2 | End-to-end rehearsals against test taxpayer entities. Closer to production behaviour but invoices are not fiscalised. |
| PROD | api.ksef.mf.gov.pl/v2 | Live, fiscally binding invoices. Switch only after DEMO + TEST are green and your team has signed off on the FA(3) layout. |
Step 1 — Get connected (handled by GetUp)
KSeF connection is not self-service. After you sign up for Pro with a Polish NIP, request the KSeF onboarding by emailing support@getup.dev — we run the connection on your behalf and let you know once your company shows Connected in the panel.
What happens on our side: we run the KSeF health check (POST /auth/challenge) and the authentication exchange against the environment you picked. The gateway returns a long-lived refresh token, which GetUp stores AES-256-GCM-encrypted at rest and uses to mint short-lived access tokens on demand, so you never paste an access token by hand. Once the company shows Connected, the Inbound and Outbound pages appear in your sidebar and you can send your first invoice. Connection lives at the company level; multi-company workspaces register one connection per NIP.
Step 2 — Send an outbound invoice
Once a company is connected, anyone with company-portal access can issue invoices. Two modes are available — pick by how much control you want over the XML.
Template mode (recommended)
Form-driven. Fill seller, buyer (with NIP lookup), invoice number, dates, and up to 500 line items. The platform produces compliant FA(3) XML, applies VAT bands (23/22, 8/7, 5), and validates against the official XSD before submission. This is the right path for 90% of operators.
Raw XML mode
Paste a pre-built FA(3) document. Useful when an upstream ERP already produces compliant XML and you only need GetUp to handle auth, encryption, and archival. Well-formedness, size and XSD checks still run before send.
The outbound flow, step by step
- 01Sign in via /company/login and open KSeF → Outbound (/company/ksef-outbound). The page shows every prior submission with KSeF number, status, and a downloadable copy of the XML.
- 02Pick a mode. Template builder collects seller, buyer (with NIP lookup), invoice number, dates, and line items — up to 500 rows; VAT bands 23/22, 8/7, 5 are aggregated into the FA(3) totals automatically. Raw XML lets you paste a pre-built FA(3) document for full control.
- 03The platform validates server-side. Both modes run an XSD check against the FA(3) schema shipped under docs/ksef/schemas/fa3-2025-06-25-13775/ plus a well-formedness + size check; if the XSD bundle is missing the API returns 503 ksef_xsd_schema_missing, which is a deployment-side issue we fix on our end.
- 04Submit. GetUp opens an interactive session with POST /sessions/online (FA(3) + RSA-OAEP-wrapped AES-256-CBC session key), encrypts your invoice content, and posts it to .../invoices. A 202 returns a reference number; the gateway hashes are recomputed and compared before persisting.
- 05Track status. The Outbound row polls until KSeF assigns a final number (Accepted or Rejected). Accepted invoices appear in your archive — XML stored in Storage, metadata in Firestore — with the same idempotency key you supplied so you can safely retry.
Step 3 — Pull inbound invoices
Inbound is reading rather than writing. GetUp keeps a synced metadata index per company and fetches full XML on demand. You don't have to babysit the sync — there's a daily cron — but you can also force it from the UI.
- 01Open KSeF → Inbound (/company/ksef-inbound). Accountants assigned to the company see the same data, scoped to their assignments, at /accountant/ksef-inbound.
- 02Sync metadata. Click Sync (or wait for the daily cron). The page calls POST /invoices/query/metadata, paginates with a cursor, and writes lastInboundSyncAt against the company doc.
- 03Drill into a row. From the metadata list, Fetch XML calls /invoices/ksef/{ksefNumber} and stores the full structured invoice in Storage. Counterparty, line items, and totals become searchable.
- 04Bulk export. For audits, Export package builds a ZIP from POST /invoices/exports — the gateway returns encrypted parts, GetUp decrypts and stitches them server-side (route maxDuration is 300s; very large windows may need a second pass).
Who sees what
KSeF surfaces are scoped per portal. The company sends and receives; accountants read what they're assigned to.
| Role | What they can do | Routes |
|---|---|---|
| Company session | Send and receive invoices for the current company, view its archive, retry submissions with the same idempotency key. Cannot edit the connection itself. | /company/ksef-outbound /company/ksef-inbound /invoices |
| Accountant | Read-only inbound + outbound for companies explicitly assigned to them. No payroll, no employees, no connection editing. | /accountant/ksef-outbound /accountant/ksef-inbound |
The five common errors and how to fix them
If something blows up, it's almost always one of these five. Check the audit log at /api/ksef/audit and match the code below.
401 unauthorized on a business endpoint
What it means: your access token has expired (they're short-lived) or the system accidentally sent the refresh token in its place.
How to fix: GetUp's HTTP layer auto-refreshes via POST /auth/token/refresh. If you see this in the audit log it usually clears on retry; persistent 401s mean the refresh token was revoked — reconnect the company.
422 / XSD validation error
What it means: the submitted XML doesn't satisfy the FA(3) schema. Common causes: missing P_1 / P_2 fields, malformed VAT band totals, more than 500 line items, currency other than PLN.
How to fix: use Template mode for guided field validation. Fix the offending field and resubmit with the same idempotency key.
429 rate limit
What it means: KSeF rate-limits per endpoint group. Auth endpoints are ~60/s; sessions are stricter (10/s, 30/min, 120/hour).
How to fix: respect the Retry-After header. The GetUp client already backs off on 429 — don't pummel the cron retry. Spread bulk imports across hours rather than minutes.
503 ksef_xsd_schema_missing
What it means: outbound validation tried to load the FA(3) XSD but the schema files weren't on disk in this deployment.
How to fix: this is a deployment-side issue. Email support@getup.dev with the company NIP and the timestamp; we redeploy with the schema bundle and your retry succeeds.
Connection lost / token revoked
What it means: the taxpayer revoked the KSeF token from the MF portal, or the qualified certificate behind XAdES expired.
How to fix: reach out to GetUp support to reconnect the company. The previous archive is unaffected; only future submissions are blocked until the new tokens are minted.