Poland — KSeF Setup and Receipts
KSeF (Krajowy System e-Faktur) is Poland's national e-invoicing system, run by the Ministry of Finance. GoRoute's Poland pack sends your invoices to KSeF for you. This page covers the setup calls and the receipt call:
PUT /api/v1/jurisdictions/{code}/settings
POST /api/v1/jurisdictions/pl/token
GET /api/v1/jurisdictions/pl/status
DELETE /api/v1/jurisdictions/pl/token
GET /api/v1/jurisdictions/pl/transactions/{transaction_id}/upo
The first is the generic settings call, with PL as the code. Paths are relative to
https://app.goroute.ai/peppol-api. For the mandate dates and the Polish
VAT rates GoRoute accepts, see the Poland country guide.
On GoRoute's production service, invoices go to KSeF production. There they are legal invoices with tax effect. On a test service they go to KSeF's test environment, which has no legal effect.
Check which environment your service uses before you send anything. Call
GET /api/v1/jurisdictions/pl/status and read ksef_environment: production,
test or demo. If the call answers 404, the Poland pack is not switched on for the service
you are calling.
Who the pack applies to
The pack is opt-in. It applies only to an organisation that meets all of these:
- Its country is
PL. The country is set when your organisation is created. If it is wrong, contact GoRoute support. - It has enrolled: its Poland settings say
"enabled": true.
For such an organisation, every invoice and credit note it sends goes through the pack, apart
from consumer sales when those are switched off. Until its NIP and legal name are saved, its
sends are refused (C5_PL_NOT_READY). Until a KSeF token is connected, its invoices are accepted
but wait (C5_PL_TOKEN_MISSING). Documents it receives are not sent to KSeF: the issuer files
those.
Set up
Setup is two calls, in this order. Both need the settings:manage or the authority:manage
permission.
1. Save your settings
curl -X PUT \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true, "nip": "1112223332", "legal_name": "Przykładowa Firma Sp. z o.o.", "consumer_invoices": false}' \
"https://app.goroute.ai/peppol-api/api/v1/jurisdictions/PL/settings"
The settings hold exactly four fields:
| Field | Type | Meaning |
|---|---|---|
enabled | boolean | Enrols the organisation in the Poland pack. Default false. |
nip | string | Your NIP, Poland's 10-digit tax number. A PL prefix, spaces, dots and dashes are removed before it is stored. A NIP whose check digit is wrong is refused. |
legal_name | string | Your business name as KSeF knows it. Up to 256 characters. |
consumer_invoices | boolean | Whether sales to consumers are sent to KSeF too. KSeF is voluntary for consumer invoices. Default false. |
The call replaces your Poland settings as a whole, so send all four fields every time. A field of
the wrong type, an invalid NIP or any other key is refused with
422 JURISDICTION_SETTINGS_INVALID and an issues list. For the generic settings call, see
Jurisdiction settings.
The NIP in this example is made up. It passes the check digit, but it does not belong to any registered business.
2. Connect your KSeF token
You create a KSeF token in KSeF itself, for the NIP you saved. It needs the InvoiceWrite and InvoiceRead permissions.
curl -X POST \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"token": "YOUR_KSEF_TOKEN"}' \
"https://app.goroute.ai/peppol-api/api/v1/jurisdictions/pl/token"
The token must be 10 to 512 characters. GoRoute first signs in to KSeF with it, for your NIP. Only if KSeF accepts it is the token stored, encrypted. GoRoute never returns the token, in this answer or any other.
{
"connected": true,
"nip": "1112223332",
"ksef_environment": "production",
"peppol_id": "9945:PL1112223332",
"identity_adopted": true
}
ksef_environmentis the KSeF environment your invoices will go to.peppol_idandidentity_adopted: a KSeF token proves that your organisation controls the NIP. So if your organisation had no sending identity, it gets9945:PL<NIP>, andidentity_adoptedistrue. If it already had the same identity, that is confirmed (true). If it already had a different identity, that one is kept,peppol_idshows it, andidentity_adoptedisfalse.- This identity is not registered on the Peppol network. Nobody can send you documents over Peppol through it.
If you connect a new token, it replaces the old one.
Refusals. Each is a 422 with this body:
{
"error": "ksef_setup",
"error_code": "C5_PL_NOT_READY",
"message": "KSeF setup is not complete for this account; finish setup in the app. (save the business NIP first)"
}
error_code | When |
|---|---|
C5_PL_NOT_READY | No NIP is saved yet. Do step 1 first. |
C5_PL_TOKEN_INVALID | KSeF refused the token: revoked, expired or made for another NIP. |
C5_PL_TOKEN_NO_PERMISSION | The token lacks InvoiceWrite or InvoiceRead. |
C5_PL_AUTH_BLOCKED | KSeF has blocked sign-in for this token or NIP. |
C5_PL_KSEF_UNREACHABLE | KSeF could not be reached. Try again later. |
C5_PL_RATE_LIMITED | KSeF asked GoRoute to slow down. Try again shortly. |
The full sentence for each code is in Reason codes.
Check your setup
curl -H "X-API-Key: YOUR_API_KEY" \
"https://app.goroute.ai/peppol-api/api/v1/jurisdictions/pl/status"
Any valid API key of your organisation can call it. No special permission.
{
"ready": true,
"missing": null,
"nip": "1112223332",
"legal_name": "Przykładowa Firma Sp. z o.o.",
"consumer_invoices": false,
"ksef_environment": "production",
"peppol_id": "9945:PL1112223332",
"token": {"connected": true, "connected_at": "2026-10-22T08:15:00+00:00"},
"invoices": {"acknowledged": 41, "pending": 1}
}
| Field | Meaning |
|---|---|
ready | true when setup is complete and invoices can be sent to KSeF. |
missing | null when ready. Otherwise the first thing missing, as one of these sentences: "KSeF is not enabled for this account.", "The business NIP is missing.", "The business name is missing." or "Connect a KSeF token." |
nip, legal_name, consumer_invoices | Your stored settings. |
ksef_environment | The KSeF environment invoices go to: production, test or demo. |
peppol_id | Your organisation's sending identity, or null. |
token.connected, token.connected_at | Whether a token is stored, and when it was connected. |
invoices | How many of your invoices are in each KSeF state. The states are listed under What happens when you send. A state with no invoices is left out. |
Remove your token
curl -X DELETE \
-H "X-API-Key: YOUR_API_KEY" \
"https://app.goroute.ai/peppol-api/api/v1/jurisdictions/pl/token"
Needs settings:manage or authority:manage. The stored token is erased:
{"connected": false, "revoked": 1}
revoked is the number of stored tokens erased: 0 if none was stored. After this, invoices you send are not lost: they wait
(state held, reason C5_PL_TOKEN_MISSING) and are sent once you connect a new token. Removing
the token here does not revoke it in KSeF. Do that in KSeF if you no longer want it used.
Download KSeF's receipt
When KSeF accepts an invoice, it issues a signed receipt, the UPO (Urzędowe Poświadczenie Odbioru). GoRoute keeps it with the transaction.
curl -H "X-API-Key: YOUR_API_KEY" -o upo.xml \
"https://app.goroute.ai/peppol-api/api/v1/jurisdictions/pl/transactions/TRANSACTION_ID/upo"
The answer is the UPO as XML (application/xml), as a file named UPO-<transaction id>.xml.
It answers 404 when:
- the transaction does not exist or belongs to another organisation; or
- no receipt is kept for it. That is the case before KSeF has accepted the invoice. It is also the case if GoRoute could not download the receipt at the moment KSeF accepted the invoice: GoRoute does not try again later. The KSeF number is recorded either way.
What happens when you send
You send invoices and credit notes the usual way, with POST /api/v1/invoices or
POST /api/v1/documents. For an organisation the pack applies to:
- At send, GoRoute builds a KSeF FA(3) invoice from your document and checks it against the Ministry's schema. A credit note becomes a corrective invoice (KOR). If the FA(3) invoice cannot be built, the send is refused and nothing is created. The refusal says which field is wrong; see Refusals at send.
- Sales to consumers are not sent to KSeF unless
consumer_invoicesistrue. GoRoute treats a sale as a consumer sale when the buyer has no tax or company identifier on the document. - A currency other than PLN is allowed. GoRoute states the VAT in złoty at the National Bank of Poland's average rate from the last table before the issue date.
- A credit note must name the invoice it corrects, and that invoice must already have a
KSeF number. Until it has one, the credit note is refused (
C5_PL_CORRECTION_REFERENCE_REQUIRED). Send it again once KSeF has accepted the invoice. - Afterwards, GoRoute sends the FA(3) invoice to KSeF with your token. It keeps trying until KSeF answers; an invoice is never given up on because of the number of attempts. When KSeF accepts it, GoRoute records the KSeF number and the UPO, and the invoice PDF gets the KSeF number and its verification QR code (KOD I).
Where it goes. With the default destination (peppol), the invoice also goes to your
buyer the usual way. With "destination": "authority", it goes to KSeF only and to no buyer. A
consumer sale with consumer_invoices off is not sent to KSeF, so an authority-only send of one
is refused (400). See the destination field.
Where to read the result. GET /api/v1/transactions/{transaction_id} carries an authority
object. Poland's entry is authority.PL:
"authority": {
"PL": {
"regime": "PL",
"status": "acknowledged",
"ksef_number": "KSEF_NUMBER_FROM_KSEF",
"reason_code": null,
"reason": null
}
}
ksef_number is the number KSeF gave the invoice. GoRoute shows it as KSeF issued it. The
status is one of:
status | Meaning |
|---|---|
pending | Waiting to be sent, or KSeF is still processing it, or KSeF was unreachable and GoRoute will try again. |
held | Waiting for a usable KSeF token. reason_code says why. It is sent once you connect a new token. |
acknowledged | KSeF accepted the invoice. ksef_number is set. |
rejected | KSeF rejected the invoice. reason_code is C5_PL_KSEF_REJECTED and reason is that code's fixed sentence; it does not include KSeF's own message. Fix the document and issue it again. |
failed | GoRoute could not send the invoice, for example because the FA(3) invoice could not be rebuilt. reason says why. |
Refusals at send
POST /api/v1/invoices answers a refusal from the Poland pack with 422
JURISDICTION_REFUSED, with regime PL and the reason_code. With
"destination": "authority" the same refusal is a 400 AUTHORITY_REFUSED.
POST /api/v1/documents answers it with 400 INVALID_REQUEST. In each case the message
carries the reason code and its sentence.
Reason codes
These are the codes the Poland pack uses, with the sentence GoRoute shows for each.
| Code | Sentence |
|---|---|
C5_PL_NOT_READY | KSeF setup is not complete for this account; finish setup in the app. |
C5_PL_NIP_REQUIRED | This invoice needs the buyer's NIP or tax ID; add it to the order and issue again. |
C5_PL_RECORD_REFUSED | The invoice could not be built as a KSeF FA(3) invoice; the message says which field, fix the order and issue again. |
C5_PL_CURRENCY_RATE_REQUIRED | The order is not in PLN and no exchange rate to PLN was available; KSeF needs the tax in PLN. |
C5_PL_VAT_RATE_UNSUPPORTED | The order charges a VAT rate that is not a Polish rate (23, 8 or 5 %, or 0 %); set the store's tax for Poland and issue again. |
C5_PL_NBP_RATE_UNAVAILABLE | The NBP exchange rate to PLN could not be read just now; issue the invoice again in a few minutes. |
C5_PL_CORRECTION_REFERENCE_REQUIRED | A corrective invoice needs the KSeF number of the invoice it corrects; it is issued once that invoice has been accepted by KSeF. |
C5_PL_CONSUMER_NOT_ENABLED | A consumer sale: KSeF is optional for consumers and switched off for this account, so nothing is sent. |
C5_PL_TOKEN_MISSING | No KSeF token is connected; connect one in the app. Invoices wait until then. |
C5_PL_TOKEN_INVALID | KSeF refused the token (revoked, expired or for another NIP); connect a new token. Invoices wait and are sent once it is connected. |
C5_PL_TOKEN_NO_PERMISSION | The KSeF token lacks the invoice permissions; create one with InvoiceWrite and InvoiceRead. |
C5_PL_AUTH_BLOCKED | KSeF has blocked sign-in for this token or NIP; check the token in the Ministry's KSeF app and connect a new one. Invoices wait until then. |
C5_PL_KSEF_REJECTED | KSeF rejected the invoice; the KSeF message says why. Fix the order and issue again. |
C5_PL_KSEF_UNREACHABLE | KSeF could not be reached; the invoice is kept and sent automatically when KSeF answers. |
C5_PL_RATE_LIMITED | KSeF asked us to slow down; the invoice is sent automatically shortly. |
C5_PL_RECEIVED | A received invoice is filed in KSeF by its issuer, not by the buyer: nothing to send. |
C5_PL_NOT_RECORDED | This document was issued before KSeF was active for the account, so nothing is sent through GoRoute. |
The sentences are written for merchants using a store app, so some say "in the app". Through the
API, "the app" means the calls on this page. The C5_PL_VAT_RATE_UNSUPPORTED sentence is shorter
than the rule: GoRoute accepts standard-rated lines at 23, 22, 8, 7 or 5 %. The
country guide has the full list.
API Reference
The generated reference for each call: