Invoice Submission (Single)
Submit invoices to KSeF — JSON, raw XML, and synchronous submission with automatic session management.
Submission Methods Overview
| # | Endpoint | Input | Mode | Response | Best for |
|---|---|---|---|---|---|
| 1 | POST /api/invoices/submit-sync | JSON | Sync | 201 + reference | Simple integrations, real-time |
| 2 | POST /api/invoices/submit-raw-xml | XML | Sync | 201 + reference | ERP with own XML generation |
| 3 | POST /api/invoices | JSON | Async (Kafka) | 202 + invoice_id | High volume, fire-and-forget |
| 4 | POST /api/invoices/queue-raw-xml | XML | Async (Kafka) | 202 + invoice_id | ERP with own XML, high volume |
Sync = Gateway sends to KSeF, waits for reference number, returns it in response.
Async = Gateway queues to Kafka, returns immediately. Worker processes in background. Poll for status.
Session management is automatic. The Gateway opens/reuses KSeF sessions transparently. You don't need to call Sessions endpoints before submitting — only ensure a credential exists (Tokens & Certificates).
JSON Invoice Schema (InvoiceCreate)
Used by endpoints #1 and #3. The Gateway generates FA(3) XML from this JSON.
{
"company_id": "22919080-f3fe-4aff-8d71-f6493afc956a",
"invoice_type_code": "VAT",
"invoice_number": "FV/2026/03/001",
"invoice_date": "2026-03-14",
"sale_date": "2026-03-14",
"seller": {
"nip": "8133793363",
"name": "Bizneto Mirosław Dykiel",
"street": "Mieszka I 38",
"city": "Rzeszów",
"postal_code": "35-308",
"country": "PL"
},
"buyer": {
"nip": "8132952463",
"name": "Test Buyer Sp. z o.o.",
"street": "ul. Testowa 1",
"city": "Warszawa",
"postal_code": "00-001",
"country": "PL"
},
"items": [{
"position": 1,
"name": "Usługa integracji KSeF",
"quantity": "1",
"unit": "szt",
"unit_price": "5000.00",
"vat_rate": "23",
"net_amount": "5000.00",
"vat_amount": "1150.00",
"gross_amount": "6150.00"
}],
"summary": {
"net_total": "5000.00",
"vat_total": "1150.00",
"gross_total": "6150.00"
},
"tax_breakdowns": [{
"name": "PTU",
"category": "S",
"percent": "23",
"taxable_base": "5000.00",
"tax_amount": "1150.00"
}],
"currency": "PLN"
}Required Fields
| Field | Type | Maps to XML | Description |
|---|---|---|---|
company_id | UUID | — | Company UUID (must match seller NIP) |
invoice_type_code | string | Fa/RodzajFaktury | VAT, KOR, ZAL, ROZ, UPR, KOR_ZAL, KOR_ROZ |
invoice_number | string | Fa/P_2 | Invoice number |
invoice_date | date | Fa/P_1 | Issue date (YYYY-MM-DD) |
sale_date | date | Fa/P_6 | Sale/delivery date |
seller | Party | Podmiot1 | Seller info (NIP must match company) |
buyer | Party | Podmiot2 | Buyer info |
items[] | array | Fa/FaWiersz | Line items (min 1) |
summary | object | Fa/P_13_*, P_14_*, P_15 | Totals |
tax_breakdowns[] | array | Fa/P_13_*, P_14_* | VAT breakdown by rate |
Optional Fields
| Field | Type | Maps to XML | Description |
|---|---|---|---|
currency | string | Fa/KodWaluty | ISO 4217, default: PLN |
exchange_rate | number | Fa/KursWaluty | Required if non-PLN currency |
payment_date | date | Fa/TerminPlatnosci/Termin | Payment due date |
description | string | Stopka/Informacje/StopkaFaktury | Footer text (max 3500 chars) |
type_operation | string | — | services (default) or goods |
amended_ksef_number | string | Fa/NrKSeFFaKorygowanej | For corrections (KOR) |
amended_date | date | — | Original invoice date |
amended_number | string | — | Original invoice number |
amend_type | string | Fa/P_15ZK | Correction effect: 1=original date, 2=correction date, 3=other |
Party (seller/buyer)
| Field | Type | Required | Description |
|---|---|---|---|
nip | string | ✅ | 10-digit Polish NIP |
name | string | ✅ | Legal name |
street | string | ✅ | Street + number |
city | string | ✅ | City |
postal_code | string | ✅ | Format: XX-XXX |
country | string | ❌ | ISO alpha-2, default PL |
Line Item
| Field | Type | Required | Maps to | Description |
|---|---|---|---|---|
position | int | ✅ | NrWierszaFa | Line number (from 1) |
name | string | ✅ | P_7 | Item/service description |
quantity | decimal | ✅ | P_8A | Quantity |
unit | string | ✅ | P_8B | Unit: szt, godz, kg, m2... |
unit_price | decimal | ✅ | P_9A | Net unit price |
vat_rate | string | ✅ | P_12 | 23, 8, 5, 0, zw, np, oo |
net_amount | decimal | ✅ | P_11 | Net total for line |
vat_amount | decimal | ✅ | P_11Vat | VAT for line |
gross_amount | decimal | ✅ | — | Gross total for line |
pkwiu | string | ❌ | PKWiU | Classification code |
cn | string | ❌ | CN | Combined Nomenclature |
Tax Breakdown
| Field | Type | Required | Description |
|---|---|---|---|
category | string | ✅ | S=Standard, E=Exempt, Z=Zero, O=Reverse charge, AE=RC EU |
percent | decimal | ✅ | VAT rate (23, 8, 5, 0) |
taxable_base | decimal | ✅ | Net amount for this rate |
tax_amount | decimal | ✅ | VAT amount for this rate |
name | string | ❌ | Default: PTU |
comment | string | ❌ | Exemption reason etc. |
VAT Rate Mapping
| Rate | vat_rate | category | XML net field | XML VAT field |
|---|---|---|---|---|
| 23% | 23 | S | P_13_1 | P_14_1 |
| 8% | 8 | S | P_13_2 | P_14_2 |
| 5% | 5 | S | P_13_3 | P_14_3 |
| 0% domestic | 0 | Z | P_13_6 | — |
| Exempt | zw | E | P_13_7 | — |
| Reverse charge | np | O / AE | P_13_9 | — |
Endpoint #1: Submit Sync (JSON)
POST /api/invoices/submit-sync
Authorization: Bearer {token}
Content-Type: application/jsonRequest: Full InvoiceCreate JSON (see section 2).
Response (201):
{
"invoice_id": "698ac8e1-2355-4e08-bda1-b29054263fee",
"company_id": "22919080-f3fe-4aff-8d71-f6493afc956a",
"status": "sending",
"invoice_type_code": "VAT",
"invoice_number": "FV/TEST/2026/03/001",
"invoice_date": "2026-03-14",
"sale_date": "2026-03-14",
"created_at": "2026-03-14T09:44:27.189297Z",
"sent_at": "2026-03-14T09:44:27.353080Z",
"ksef_reference_number": "20260314-EE-21715A2000-D05678D784-5A",
"fingerprint": "a7bd1e6d37f5a07bd18f9ee5f16388e4cc731bd9fe57f2c90b500ecb0887c8e3",
"identifier": "https://ksef-demo.mf.gov.pl/client-app/invoice/8133793363/2026-03-14/p70ebTf1oHvRj57l8WOI5MxzG9n...",
"qr_code": "iVBORw0KGgo...(base64 PNG)",
"qr_label": "OFFLINE",
"xml_url": "/api/invoices/698ac8e1-.../xml",
"qr_url": "/api/invoices/698ac8e1-.../qr"
}What happens: Gateway validates JSON → generates FA(3) XML → opens/reuses KSeF session → encrypts with AES-256-CBC → sends to KSeF → returns reference number. Background worker then polls for KSeF number.
Endpoint #2: Submit Raw XML (Sync)
POST /api/invoices/submit-raw-xml
Authorization: Bearer {token}
Content-Type: application/jsonRequest:
{
"company_id": "22919080-f3fe-4aff-8d71-f6493afc956a",
"xml_content": "<?xml version='1.0' encoding='UTF-8'?><Faktura xmlns=\"http://crd.gov.pl/wzor/2025/06/25/13775/\">...</Faktura>"
}Response (201):
{
"status": "submitted",
"invoice_id": "1d69f57c-3c5e-4928-840c-9920e787adcc",
"ksef_response": {
"referenceNumber": "20260314-EE-220FD3B000-D0580027F0-3D"
},
"xml_hash": "f5edddd131138c0cdb2876416e6ab878ffd99b21071b225462cd9a4ae7365123",
"xml_size": 1919
}What happens: Gateway parses XML (extracts P_2, P_1, P_6) → saves to DB → opens/reuses session → encrypts → sends to KSeF. Client is responsible for XML validity.
Endpoint #3: Submit Async JSON (Kafka)
POST /api/invoices
Authorization: Bearer {token}
Content-Type: application/jsonRequest: Same InvoiceCreate JSON as submit-sync.
Response (202 Accepted):
{
"invoice_id": "5bf577a5-3335-4c63-a5e0-5d8248b10c67",
"status": "queued",
"invoice_number": "FV/ASYNC/2026/03/001"
}Returns immediately. Invoice queued to Kafka topic invoice.submit. Worker picks it up, processes identically to sync. Poll GET /api/invoices/{id} for final status.
Endpoint #4: Queue Raw XML Async (Kafka)
POST /api/invoices/queue-raw-xml
Authorization: Bearer {token}
Content-Type: application/jsonRequest: Same RawXMLRequest as submit-raw-xml.
Response (202):
{
"status": "queued",
"invoice_id": "73591981-527c-49fb-bb05-e481f358c75d",
"invoice_number": "FV/ASYNC-XML/2026/03/001",
"message": "Invoice queued for async submission to KSeF. Poll GET /invoices/{invoice_id} for status."
}Invoice Lifecycle
┌─────────────────────────────────────────────────┐
│ SUBMISSION FLOW │
│ │
JSON ──┐ │ ┌────────┐ ┌─────────┐ ┌────────────┐ │
sync ├──▶│ │SENDING │──▶│ KSeF │──▶│REGISTERED │ │
XML ┘ │ └────────┘ │ encrypt │ │ksef_number │ │
│ │ + send │ └────────────┘ │
JSON ──┐ │ ┌────────┐ └────┬─────┘ ┌────────────┐ │
async ├──▶│ │QUEUED │───────┘ ┌───▶│ FAILED │ │
XML ┘ │ │(Kafka) │ │ │error_message│ │
│ └────────┘ │ └────────────┘ │
│ │ │
incoming ──▶ ┌────────┐ │ ┌────────────┐ │
webhook │ │ DRAFT │───submit──┘ │ CANCELLED │ │
│ └────────┘ └────────────┘ │
└─────────────────────────────────────────────────┘| Status | Description | Final? | Has ksef_number? |
|---|---|---|---|
draft | Created via incoming webhook (auto_submit=false) | ❌ | ❌ |
queued | In Kafka queue, waiting for Worker | ❌ | ❌ |
sending | Worker sending to KSeF | ❌ | ❌ |
registered | Registered in KSeF | ✅ | ✅ |
failed | Error (check error_message) | ✅ | ❌ |
cancelled | Cancelled/deleted before registration | ✅ | ❌ |
Retrieval Endpoints
8.1 Get Invoice
GET /api/invoices/{invoice_id}
Authorization: Bearer {token}Response (200):
{
"invoice_id": "4fc06985-2b02-4389-8d89-40a9d1333883",
"invoice_number": "FV/QR-TEST/2026/03/001",
"status": "registered",
"ksef_number": "8133793363-20260314-56B828800000-62",
"ksef_url": "https://qr-demo.ksef.mf.gov.pl/client-app/invoice/.../verify-download",
"identifier": "https://qr-demo.ksef.mf.gov.pl/invoice/8133793363/14-03-2026/Q6MltDPzEhCc...",
"qr_label": "8133793363-20260314-56B828800000-62",
"fingerprint": "43a325b433f312109ce02568c9844d07d1cff609745db61f168913136aa62fd4",
"xml_url": "/api/invoices/4fc06985-.../xml",
"qr_url": "/api/invoices/4fc06985-.../qr",
"upo_url": "/api/invoice-status/4fc06985-.../download-upo",
"created_at": "2026-03-14T11:19:58.885183Z",
"sent_at": "2026-03-14T11:20:00.794107Z",
"registered_at": "2026-03-14T11:20:14.107706Z"
}8.2 List Invoices
GET /api/invoices?company_id={id}&invoice_status={status}&skip={n}&limit={n}
Authorization: Bearer {token}| Parameter | Type | Description |
|---|---|---|
company_id | UUID | Filter by company |
invoice_status | string | draft, queued, sending, registered, failed, cancelled |
skip | int | Offset for pagination |
limit | int | Results per page |
Response (200):
{
"total": 92,
"skip": 0,
"limit": 3,
"invoices": [
{ "invoice_id": "...", "invoice_number": "INC/2026/03/002", "status": "registered", "ksef_number": "..." },
{ "invoice_id": "...", "invoice_number": "FV/WEBHOOK/2026/03/001", "status": "registered", "ksef_number": "..." }
]
}8.3 Get Invoice XML
GET /api/invoices/{invoice_id}/xml
Authorization: Bearer {token}Returns raw FA(3) XML (Content-Type: application/xml).
8.4 Get QR Code
GET /api/invoices/{invoice_id}/qr
Authorization: Bearer {token}Returns PNG image (Content-Type: image/png). QR encodes the identifier URL.
8.5 Get UPO
GET /api/invoices/{invoice_id}/upo
Authorization: Bearer {token}Returns UPO XML (Urzędowe Poświadczenie Odbioru — Official Receipt).
Invoice Actions
9.1 Submit Draft to KSeF
For invoices created via incoming webhook with auto_submit: false.
POST /api/invoices/{invoice_id}/submit
Authorization: Bearer {token}Response (202): Invoice moves from draft → queued → processing pipeline.
Error (400): "Cannot submit invoice with status 'cancelled'. Only DRAFT invoices can be submitted."
9.2 Retry Failed Invoice
POST /api/invoices/{invoice_id}/retry
Authorization: Bearer {token}Response (202): Invoice re-queued for submission.
Error (409): "Invoice already registered in KSeF: 8133793363-20260314-56B828800000-62"
9.3 Cancel / Delete Invoice
DELETE /api/invoices/{invoice_id}?hard={true|false}
Authorization: Bearer {token}| Parameter | Default | Description |
|---|---|---|
hard | false | false = soft delete (status → cancelled), true = physical delete |
Response: 204 No Content
Registered invoices can be cancelled in the Gateway database (status →
cancelled) but cannot be un-registered from KSeF. The KSeF number remains valid. Use corrective invoices (KOR) for corrections.
FA(3) XML Structure (for raw XML endpoints)
Namespace (CRITICAL)
<?xml version="1.0" encoding="UTF-8"?>
<Faktura xmlns="http://crd.gov.pl/wzor/2025/06/25/13775/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">Wrong namespace = silent rejection. Always use
2025/06/25/13775/.
Complete Tested Example
<?xml version="1.0" encoding="UTF-8"?>
<Faktura xmlns="http://crd.gov.pl/wzor/2025/06/25/13775/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Naglowek>
<KodFormularza kodSystemowy="FA (3)" wersjaSchemy="1-0E">FA</KodFormularza>
<WariantFormularza>3</WariantFormularza>
<DataWytworzeniaFa>2026-03-14T10:00:00Z</DataWytworzeniaFa>
<SystemInfo>KSeF API Gateway v1.0.0</SystemInfo>
</Naglowek>
<Podmiot1>
<DaneIdentyfikacyjne>
<NIP>8133793363</NIP>
<Nazwa>Bizneto Mirosław Dykiel</Nazwa>
</DaneIdentyfikacyjne>
<Adres>
<KodKraju>PL</KodKraju>
<AdresL1>Mieszka I 38</AdresL1>
<AdresL2>35-308 Rzeszów</AdresL2>
</Adres>
</Podmiot1>
<Podmiot2>
<DaneIdentyfikacyjne>
<NIP>8132952463</NIP>
<Nazwa>Test Buyer Sp. z o.o.</Nazwa>
</DaneIdentyfikacyjne>
<Adres>
<KodKraju>PL</KodKraju>
<AdresL1>ul. Testowa 1</AdresL1>
<AdresL2>00-001 Warszawa</AdresL2>
</Adres>
<JST>2</JST>
<GV>2</GV>
</Podmiot2>
<Fa>
<KodWaluty>PLN</KodWaluty>
<P_1>2026-03-14</P_1>
<P_2>FV/2026/03/001</P_2>
<P_6>2026-03-14</P_6>
<P_13_1>2500.00</P_13_1>
<P_14_1>575.00</P_14_1>
<P_15>3075.00</P_15>
<Adnotacje>
<P_16>2</P_16>
<P_17>2</P_17>
<P_18>2</P_18>
<P_18A>2</P_18A>
<Zwolnienie><P_19N>1</P_19N></Zwolnienie>
<NoweSrodkiTransportu><P_22N>1</P_22N></NoweSrodkiTransportu>
<P_23>2</P_23>
<PMarzy><P_PMarzyN>1</P_PMarzyN></PMarzy>
</Adnotacje>
<RodzajFaktury>VAT</RodzajFaktury>
<FaWiersz>
<NrWierszaFa>1</NrWierszaFa>
<P_7>Integracja API KSeF - pakiet Enterprise</P_7>
<P_8A>szt</P_8A>
<P_8B>1</P_8B>
<P_9A>2500.00</P_9A>
<P_11>2500.00</P_11>
<P_11Vat>575.00</P_11Vat>
<P_12>23</P_12>
</FaWiersz>
</Fa>
</Faktura>XML Adnotacje Section (REQUIRED)
Missing <Adnotacje> = rejection. Default values for a standard VAT invoice:
<Adnotacje>
<P_16>2</P_16> <!-- cash method: 1=yes, 2=no -->
<P_17>2</P_17> <!-- self-billing: 1=yes, 2=no -->
<P_18>2</P_18> <!-- reverse charge: 1=yes, 2=no -->
<P_18A>2</P_18A> <!-- split payment: 1=yes, 2=no -->
<Zwolnienie><P_19N>1</P_19N></Zwolnienie>
<NoweSrodkiTransportu><P_22N>1</P_22N></NoweSrodkiTransportu>
<P_23>2</P_23> <!-- margin invoice: 1=yes, 2=no -->
<PMarzy><P_PMarzyN>1</P_PMarzyN></PMarzy>
</Adnotacje>XML Size Limits
| Limit | Value |
|---|---|
| Single invoice file | 1 MB |
| With attachments (batch) | 3 MB |
| DodatkowyOpis value | 256 chars |
| StopkaFaktury text | 3,500 chars |
Common XML Validation Errors
| Problem | Cause | Fix |
|---|---|---|
| Silent rejection | Wrong namespace | Use 2025/06/25/13775/ |
| Missing Adnotacje | Section required | Add full block with defaults |
Empty <Stopka/> | Empty element not allowed | Remove or fill with content |
<FormaPlatnosci> | Text instead of code | Use: 1=cash, 2=transfer, 6=card |
<DodatkowyOpis> | Missing child elements | Must have both <Klucz> + <Wartosc> |
| NIP mismatch | Podmiot1/NIP ≠ company.nip | Verify seller NIP |
Invoice Type Codes
| Code | XML RodzajFaktury | Description |
|---|---|---|
VAT | VAT | Standard VAT invoice |
KOR | KOR | Corrective invoice |
ZAL | ZAL | Advance payment invoice |
ROZ | ROZ | Settlement (final after advance) |
UPR | UPR | Simplified invoice |
KOR_ZAL | KOR_ZAL | Correction of advance |
KOR_ROZ | KOR_ROZ | Correction of settlement |
Error Responses
| HTTP | Error | Cause |
|---|---|---|
| 201 | — | Invoice submitted (sync) |
| 202 | — | Invoice queued (async) |
| 400 | Only DRAFT invoices can be submitted | Trying to submit non-draft |
| 400 | XML parse error | Invalid XML in raw-xml endpoint |
| 401 | Not authenticated | Missing/expired JWT |
| 403 | Access denied to this company | Wrong tenant |
| 409 | Invoice already registered in KSeF | Retry on registered invoice |
| 422 | Field required | Missing required fields in JSON |
| 422 | Validation error | Totals don't match, invalid NIP, etc. |
| 503 | Circuit breaker open | KSeF system unavailable |
Endpoint Reference
Submission
| Method | Endpoint | Input | Mode | HTTP |
|---|---|---|---|---|
| POST | /api/invoices/submit-sync | JSON | Sync | 201 |
| POST | /api/invoices/submit-raw-xml | XML | Sync | 201 |
| POST | /api/invoices | JSON | Async | 202 |
| POST | /api/invoices/queue-raw-xml | XML | Async | 202 |
Retrieval
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/invoices | List invoices (filter, paginate) |
| GET | /api/invoices/{id} | Get invoice details + all links |
| GET | /api/invoices/{id}/xml | Download FA(3) XML |
| GET | /api/invoices/{id}/qr | Download QR code (PNG) |
| GET | /api/invoices/{id}/upo | Download UPO (XML) |
Actions
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/invoices/{id}/submit | Submit draft to KSeF |
| POST | /api/invoices/{id}/retry | Retry failed invoice |
| DELETE | /api/invoices/{id} | Cancel/delete invoice |
Complete Examples
JSON Sync (simplest)
TOKEN="your_jwt_token"
BASE="https://demo.ksefapi.com"
curl -s -X POST "$BASE/api/invoices/submit-sync" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"company_id": "22919080-f3fe-4aff-8d71-f6493afc956a",
"invoice_type_code": "VAT",
"invoice_number": "FV/2026/03/001",
"invoice_date": "2026-03-14",
"sale_date": "2026-03-14",
"seller": {"nip":"8133793363","name":"Bizneto","street":"Mieszka I 38","city":"Rzeszów","postal_code":"35-308"},
"buyer": {"nip":"8132952463","name":"Buyer","street":"ul. Test 1","city":"Warszawa","postal_code":"00-001"},
"items": [{"position":1,"name":"Service","quantity":"1","unit":"szt","unit_price":"1000.00","vat_rate":"23","net_amount":"1000.00","vat_amount":"230.00","gross_amount":"1230.00"}],
"summary": {"net_total":"1000.00","vat_total":"230.00","gross_total":"1230.00"},
"tax_breakdowns": [{"category":"S","percent":"23","taxable_base":"1000.00","tax_amount":"230.00"}],
"currency": "PLN"
}' | jq '{invoice_id, status, ksef_reference_number}'Raw XML Async (fire-and-forget)
# Prepare XML in variable or file
XML='<?xml version="1.0" encoding="UTF-8"?><Faktura xmlns="http://crd.gov.pl/wzor/2025/06/25/13775/">...</Faktura>'
# Submit (escaping handled by jq)
jq -n --arg cid "22919080-..." --arg xml "$XML" '{company_id: $cid, xml_content: $xml}' | \
curl -s -X POST "$BASE/api/invoices/queue-raw-xml" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d @- | jq .
# Poll for result
INVOICE_ID="returned_id"
sleep 5
curl -s "$BASE/api/invoices/$INVOICE_ID" \
-H "Authorization: Bearer $TOKEN" | jq '{status, ksef_number}'Test Results (2026-03-14, demo.ksefapi.com)
| Endpoint | Invoice | Status | KSeF Number | Time |
|---|---|---|---|---|
| submit-sync (JSON) | FV/TEST/.../001 | ✅ registered | ...4B85A3000000-CE | ~18s |
| submit-raw-xml (XML) | FV/RAW-XML/.../001 | ✅ registered | ...4CCA28C00000-32 | ~12s |
| POST /invoices (JSON async) | FV/ASYNC/.../001 | ✅ registered | ...4FFAA8C00000-2E | ~6s |
| queue-raw-xml (XML async) | FV/ASYNC-XML/.../001 | ✅ registered | ...506CA3000000-DB | ~5s |
All 4 submission methods verified end-to-end. 92 total invoices in tenant.
Next steps:
- Batch submission → Batch Submission
- Status polling, QR & UPO → Status, QR & UPO