Poland and KSeF
Polish entities default to PLN and pl-PL. Space Invoices covers online FA(3) standard invoices and linked reducing corrections in PLN or a foreign currency for active-VAT and VAT-exempt Polish B2B sellers. KSeF obligation is modeled independently of the connection switch. Sandbox entities submit to KSeF TEST and live entities submit to KSeF production.
Configure access
Supply a valid Polish NIP on the entity. The API normalizes an optional PL prefix, spaces and hyphens, validates the checksum, and stores ten digits. Customer NIP validation follows the customer’s country, independently of the seller. Ordinary invoicing permits an omitted NIP; KSeF requires one.
All setup routes require entity administration access and a bearer token. Send x-entity-id on document requests.
| Method and route | Body | Result |
|---|---|---|
GET /entities/{id}/ksef | None | Setup and release-switch state |
PUT /entities/{id}/ksef/credentials | {"environment":"test","token":"<KSEF_TOKEN>"} | Stores an encrypted, write-only token |
PUT /entities/{id}/ksef | {"enabled":true} | Authenticates the token and enables submission |
PUT /entities/{id}/ksef | {"enabled":false} | Disables new submissions |
DELETE /entities/{id}/ksef/credentials | None | Removes credentials after KSeF is disabled |
The token needs the InvoiceWrite and InvoiceRead permissions. Space Invoices checks the token’s status, NIP and permissions with KSeF when you enable KSeF, and when you replace the token while KSeF is enabled or submissions are pending; if the check fails, the previous token stays in place. Enabling returns 422 if submission isn’t switched on or authentication fails.
- The token is tied to the entity’s NIP and environment. An environment mismatch returns
422. - To change the NIP, disable KSeF first and let pending submissions finish (including UPO retrieval).
- To remove the token, disable KSeF first (otherwise
422) and let pending submissions finish (otherwise409). Local preparation failures that never reserved an XML hash don’t block removal. - Generic entity updates ignore
settings.ksef; use the KSeF endpoints.
The settings response tells you:
credential_configured: a token for the current NIP and environment is stored.credential_status:missing,unverified,verifiedorfailed. See Credential health.last_verified_at: when the stored token last authenticated with KSeF, ornull.last_error:{ code, message, occurred_at }for the last time KSeF rejected the token, ornull.submission_available: KSeF submission is currently available for this environment. This is a platform switch, separate from yourenabledsetting, and submission can be switched off for an environment.
KSeF is required by default, whether or not enabled is on. An entity admin can record obligation: "exempt" with an exemption_reason of at least 10 characters; this records your declaration and isn’t a legal determination. Disabling KSeF doesn’t lift the obligation or unlock pending submissions. Omitting enabled, or sending null, leaves the setting unchanged.
In the app or embed, open Settings → KSeF to store a token and enable submissions. Invoice and credit-note views show authority status, eligible retry actions and UPO download. Disabling new submissions or the operational switch preserves status and receipt recovery for existing submissions. Ordinary B2C invoicing remains available outside KSeF submission when the customer is explicitly marked with is_end_consumer: true; those invoices do not enter the KSeF submission flow.
Issue an invoice or correction
Create an invoice with POST /invoices, or finalize a draft. When KSeF is enabled, issued B2B documents are submitted automatically; drafts are not.
Only standard invoices from active VAT payers, and credit notes that reduce them, are supported, in PLN or a foreign currency (see below). Advances, offline24, and domestic invoices above PLN 15,000 gross are rejected before the invoice is saved. For a foreign-currency invoice the PLN 15,000 limit is applied to the gross total in PLN. The PLN 15,000 limit is a conservative product limit, not a statement about split-payment law. While KSeF is required, disabling it doesn’t let a final Polish B2B invoice skip submission.
Provide complete seller and buyer names, addresses, country codes and tax identifiers. Use explicit tax rates. Cross-border lines require classification: "product" for goods or "service" for services and zero VAT. The mapper distinguishes intra-EU goods, exports and foreign services; it does not guess a tax treatment from an unclassified line. Domestic supported rates are 23%, 22%, 8%, 7%, 5% and explicit 0%.
Create a reducing correction through POST /credit-notes with linked_documents: ["<INVOICE_ID>"]. It must reference exactly one accepted invoice. The FA(3) correction includes the original KSeF number; stored positive credit-note amounts become negative correction amounts in XML and Polish output.
Foreign-currency invoices
Set currency_code to a foreign currency on a Polish entity (PLN). KSeF invoices can use PLN or a currency from the FA(3) currency list; crypto assets are rejected. The exchange_rate is resolved automatically from the NBP Table A average rate published on the last business day before the tax point, and is returned on the document as rate (PLN per 1 unit of the document currency), source: "nbp", quote_currency: "PLN" and date. The date is the date of the NBP table, not the tax-point date. The tax point is the existing exchange-rate reference date: the end of the service period or the service date when it is not after the issue date, otherwise the issue date. If no NBP rate can be determined, the document has no exchange rate and KSeF issuance is refused.
Issued documents keep their stored rate.
The KSeF XML keeps line and total amounts in the document currency and adds the VAT amount in PLN for each rate bracket plus the exchange rate on each invoice line. A foreign-currency KSeF invoice is rejected before it is saved if it has no PLN exchange rate or if a VAT row has no PLN amount.
A correction of a foreign-currency invoice must use the corrected invoice’s currency and exchange rate. When you create it through POST /credit-notes with linked_documents, the corrected invoice’s rate is carried over automatically instead of resolving a new one; a correction in another currency, or with a different rate, is rejected.
Invoice and credit-note responses expose server-managed ksef lifecycle state. Do not send it in create or update requests.
| State | Meaning |
|---|---|
reserved / processing | The submission reservation is durable; XML preparation is in progress |
dispatching | Sending started; its outcome requires reconciliation |
submitted | KSeF returned an invoice reference; acceptance is pending |
accepted | The authority invoice and its hash were verified |
rejected | KSeF rejected this submission; automatic resending is blocked |
retryable_error | Preparation failed before invoice dispatch |
Credential health
credential_status on GET /entities/{id}/ksef shows whether KSeF accepts the stored token:
| Value | Meaning |
|---|---|
missing | No token is stored for this environment and NIP |
unverified | A token is stored but hasn’t been used with KSeF yet |
verified | The last login with the token succeeded; last_verified_at has the time |
failed | KSeF rejected the token; last_error.code is KSEF_AUTH_FAILED |
A KSeF outage doesn’t mark the token failed. Replacing the token clears last_error.
When the token is rejected:
- Issuing a final Polish B2B invoice or credit note, or finalizing a draft, returns
422withcode: "KSEF_AUTH_FAILED"before a document number is used. Drafts can still be saved. POST /invoices/{id}/ksef/submitreturns the same422.- Documents already issued keep their place in the queue.
GET /invoices/{id}/ksefstill returns200witherror_code: "KSEF_AUTH_FAILED", and submission resumes automatically after you replace the token withPUT /entities/{id}/ksef/credentials.
Status, receipts and delivery
Instead of polling GET /invoices/{id}/ksef, subscribe to the ksef.submission.accepted, ksef.submission.rejected and ksef.submission.upo_available webhooks. Each carries { id, type, ksef }, where ksef is the same state object.
| Method and route | Purpose |
|---|---|
GET /invoices/{id}/ksef | Read status and reconcile pending authority results |
POST /invoices/{id}/ksef/submit | Submit or resume the existing reservation; no body |
POST /invoices/{id}/ksef/recover | Return a definitively unaccepted document to draft after an audited proof check; no body |
GET /invoices/{id}/ksef/upo | Download the verified UPO XML |
KSeF credit notes affect automatic invoice settlement and revenue reports only after verified acceptance. Pending and rejected corrections do not reduce the original invoice balance or reported revenue. Acceptance processing retries safely without applying the same credit twice. Use positive amounts for reducing corrections; increasing or mixed-direction corrections are outside this integration’s supported scope, including through /credit-notes/custom and draft finalization.
The same operations exist under /credit-notes/{id}/ksef. Status polling is rate limited. Background reconciliation uses the existing e-invoicing processing schedule. UPO may arrive after acceptance; its download returns 409 until available.
Submission is safe to repeat: repeated calls reuse the same reservation, and an interrupted submission is checked against the saved session and invoice hash, never blindly resent. A duplicate response alone doesn’t prove acceptance.
- Once reserved for KSeF, an invoice can’t be edited or voided. Correct it with a linked credit note.
- If KSeF definitively didn’t accept it, preparation failed before dispatch, or the session was cancelled and verified empty, you can recover it: the attempt is archived and the document returns to draft.
- Accepted submissions, and those whose outcome is still unknown, stay locked.
Deliver PDFs through the existing /documents/{id}/pdf endpoint after acceptance. KSeF output contains the accepted number and a verification QR code, including for foreign buyers receiving the PDF outside KSeF. Polish credit notes are titled FAKTURA KORYGUJĄCA. Ordinary Polish date, currency, character and email formatting remains available independently of KSeF.
Scope
This integration covers standard, online, outgoing B2B invoices in PLN or a foreign currency and linked reducing corrections for active VAT payers and VAT-exempt sellers.
VAT-exempt sellers (entity is_tax_subject: false) are supported for domestic invoices, cross-border invoices to business buyers, and their reducing corrections, in PLN or a foreign currency (a foreign-currency exempt invoice carries the exchange rate on each line and no VAT amounts). Lines must not charge VAT.
The invoice tax clause becomes the exemption’s legal basis in the FA(3) file, so set it to the provision of the Polish VAT Act that applies (for example through the entity’s default domestic tax clause). The clause is required when the document has exempt (zw) lines: every domestic document, and any cross-border goods line. A cross-border document with services only needs none. A clause longer than 256 characters is always refused with HTTP 422, as is an empty one on a document that needs it.
Cross-border documents of an exempt seller are supported for business buyers only. A business buyer is an EU buyer whose VAT number VIES did not reject, or a non-EU buyer with a tax_number; the stored transaction_type of such a document is intra_eu_b2b or 3w_b2b. A buyer without a tax number, or an EU buyer whose VAT number VIES rejected is stored as intra_eu_b2c or 3w_b2c and is refused with HTTP 422, and so is a buyer marked is_end_consumer: true. This classification applies to Polish exempt sellers only. Each line also needs a classification of product or service. Domestic lines and goods are sent as exempt (zw, totalled as exempt sales). Services supplied abroad are sent as outside Polish VAT: np II for an EU business buyer, np I for a non-EU one, with the reverse-charge annotation. Registering for VAT-UE before supplying services to EU businesses is your responsibility.
A correction must have the same seller VAT status as the invoice it corrects.
Not covered: advances, offline24 and emergency modes, domestic invoices above PLN 15,000 gross, self-billing, B2C submission to KSeF, VAT RR, attachments, special VAT procedures, and advanced corrections.
Invoices explicitly marked for end consumers can still be issued normally, outside KSeF. A PDF or a stored token alone isn’t KSeF acceptance, and this guide isn’t a certification of Polish tax compliance or legal advice.
The Ministry publishes the KSeF API documentation, including TEST authentication and verification QR rules.