Quickstart · about 5 minutes
Your first KSeF invoice in 5 minutes
Four requests: check the key, send a paid order, get the KSeF number and download the PDF. All in test mode, on KSeF TEST, with no legal effect.
You need: a test key sk_test_... (we issue it during early access), seller data and a KSeF TEST connection set up in the customer panel (a KSeF test certificate is one click), and curl, PHP with ext-curl, Node.js 18+ or Python 3 with requests. Keep the key in an environment variable:
export KSEF_API_KEY=sk_test_...
Test mode sends invoices to the Ministry of Finance's KSeF TEST: you get a real KSeF number and PDF, but the invoices have no legal effect and do not count towards any limit.
- 1Check the key and the KSeF connection
- 2Send a paid order
- 3Get the KSeF number
- 4Download the PDF with the QR code
130 seconds
Check the key and the KSeF connection
Ask for the account. When seller_configured and ksef_connected are true, you can issue invoices. A 401 means the key is wrong; if ksef_connected is false, ksef_check_error says why.
curl https://app.ksefshop.pl/v1/account \
-H "Authorization: Bearer $KSEF_API_KEY"<?php
$ch = curl_init('https://app.ksefshop.pl/v1/account');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
echo var_export($data['seller_configured'], true), ' ', var_export($data['ksef_connected'], true), PHP_EOL;const res = await fetch("https://app.ksefshop.pl/v1/account", {
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data.seller_configured, data.ksef_connected);import os
import requests
resp = requests.get(
"https://app.ksefshop.pl/v1/account",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
},
timeout=30,
)
data = resp.json()
if not resp.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data["seller_configured"], data["ksef_connected"]){
"id": "acct_Xy7Qw2Er4Ty6Ui8Op0As2Df4",
"object": "account",
"livemode": false,
"email": "sklep@example.com",
"platform": "woocommerce",
"ksef_environment": "test",
"seller": {
"nip": "4235405606",
"name": "Sklep Woo sp. z o.o.",
"address_line1": "ul. Testowa 1",
"address_line2": "00-001 Warszawa"
},
"seller_configured": true,
"ksef_connected": true,
"ksef_check_error": null,
"plan": {
"name": "Unlimited",
"monthly_limit": null,
"used_this_month": 12
}
}21 minute
Send a paid order
Send the order with the buyer's NIP, lines at gross prices and VAT rates. The API answers 201 at once with an invoice in status pending; keep its id.
Idempotency-Key makes a retry after a network error safe: no second invoice. A wrong NIP returns 400 buyer_nip_invalid right away, with param: order.buyer.nip.
curl -X POST https://app.ksefshop.pl/v1/invoices \
-H "Authorization: Bearer $KSEF_API_KEY" \
-H "Idempotency-Key: order-SO-1001" \
-H "Content-Type: application/json" \
-d '{
"order": {
"external_id": "SO-1001",
"number": "1001",
"paid_at": "2026-10-19T09:13:05Z",
"currency": "PLN",
"payment_method": "card",
"buyer": {
"nip": "5260250274",
"name": "Testowa Firma sp. z o.o.",
"address_line1": "ul. Prosta 20",
"address_line2": "00-850 Warszawa",
"email": "jan@example.com"
},
"lines": [
{
"name": "Kubek ceramiczny 350 ml",
"sku": "KUB-350",
"quantity": 2,
"unit_price": "49.00",
"vat_rate": "23"
},
{
"name": "Książka: Podstawy księgowości",
"sku": "KS-001",
"quantity": 1,
"unit_price": "59.90",
"discount": "9.90",
"vat_rate": "5"
}
],
"shipping": [
{
"name": "Kurier InPost",
"price": "15.99",
"vat_rate": "23"
}
]
}
}'<?php
$payload = [
'order' => [
'external_id' => 'SO-1001',
'number' => '1001',
'paid_at' => '2026-10-19T09:13:05Z',
'currency' => 'PLN',
'payment_method' => 'card',
'buyer' => [
'nip' => '5260250274',
'name' => 'Testowa Firma sp. z o.o.',
'address_line1' => 'ul. Prosta 20',
'address_line2' => '00-850 Warszawa',
'email' => 'jan@example.com',
],
'lines' => [
[
'name' => 'Kubek ceramiczny 350 ml',
'sku' => 'KUB-350',
'quantity' => 2,
'unit_price' => '49.00',
'vat_rate' => '23',
],
[
'name' => 'Książka: Podstawy księgowości',
'sku' => 'KS-001',
'quantity' => 1,
'unit_price' => '59.90',
'discount' => '9.90',
'vat_rate' => '5',
],
],
'shipping' => [
['name' => 'Kurier InPost', 'price' => '15.99', 'vat_rate' => '23'],
],
],
];
$ch = curl_init('https://app.ksefshop.pl/v1/invoices');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
'Idempotency-Key: order-SO-1001',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status >= 400) {
throw new RuntimeException($data['error']['code'] . ': ' . $data['error']['message']);
}
echo var_export($data['id'], true), ' ', var_export($data['status'], true), PHP_EOL;const res = await fetch("https://app.ksefshop.pl/v1/invoices", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
"Idempotency-Key": "order-SO-1001",
"Content-Type": "application/json",
},
body: JSON.stringify({
order: {
external_id: "SO-1001",
number: "1001",
paid_at: "2026-10-19T09:13:05Z",
currency: "PLN",
payment_method: "card",
buyer: {
nip: "5260250274",
name: "Testowa Firma sp. z o.o.",
address_line1: "ul. Prosta 20",
address_line2: "00-850 Warszawa",
email: "jan@example.com",
},
lines: [
{
name: "Kubek ceramiczny 350 ml",
sku: "KUB-350",
quantity: 2,
unit_price: "49.00",
vat_rate: "23",
},
{
name: "Książka: Podstawy księgowości",
sku: "KS-001",
quantity: 1,
unit_price: "59.90",
discount: "9.90",
vat_rate: "5",
},
],
shipping: [
{ name: "Kurier InPost", price: "15.99", vat_rate: "23" },
],
},
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data.id, data.status);import os
import requests
resp = requests.post(
"https://app.ksefshop.pl/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
"Idempotency-Key": "order-SO-1001",
},
json={
"order": {
"external_id": "SO-1001",
"number": "1001",
"paid_at": "2026-10-19T09:13:05Z",
"currency": "PLN",
"payment_method": "card",
"buyer": {
"nip": "5260250274",
"name": "Testowa Firma sp. z o.o.",
"address_line1": "ul. Prosta 20",
"address_line2": "00-850 Warszawa",
"email": "jan@example.com",
},
"lines": [
{
"name": "Kubek ceramiczny 350 ml",
"sku": "KUB-350",
"quantity": 2,
"unit_price": "49.00",
"vat_rate": "23",
},
{
"name": "Książka: Podstawy księgowości",
"sku": "KS-001",
"quantity": 1,
"unit_price": "59.90",
"discount": "9.90",
"vat_rate": "5",
},
],
"shipping": [
{"name": "Kurier InPost", "price": "15.99", "vat_rate": "23"},
],
},
},
timeout=30,
)
data = resp.json()
if not resp.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data["id"], data["status"]){
"id": "inv_4fJ2kP9xQwErTy7uI0oPaSdF",
"object": "invoice",
"livemode": false,
"status": "pending",
"order": {
"external_id": "SO-1001",
"number": null
},
"number": null,
"ksef_number": null,
"ksef_environment": "test",
"issue_date": null,
"currency": null,
"total_gross": null,
"buyer_nip": null,
"pdf_url": null,
"last_error": null,
"created": 1760865185,
"updated": 1760865185,
"corrections": []
}310-60 seconds
Get the KSeF number
The queue assigns the number (queued), sends the XML to KSeF (sending), and once accepted the status becomes accepted with a ksef_number. In production, receive webhooks invoice.accepted and invoice.rejected instead of polling.
ID=inv_4fJ2kP9xQwErTy7uI0oPaSdF
for i in $(seq 1 30); do
STATUS=$(curl -s https://app.ksefshop.pl/v1/invoices/$ID \
-H "Authorization: Bearer $KSEF_API_KEY" | jq -r .status)
echo "$STATUS"
case "$STATUS" in accepted|rejected|skipped|error) break ;; esac
sleep 3
done
curl -s https://app.ksefshop.pl/v1/invoices/$ID \
-H "Authorization: Bearer $KSEF_API_KEY" | jq '{status, number, ksef_number, last_error}'<?php
function ksef_get(string $path): array
{
$ch = curl_init('https://app.ksefshop.pl' . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('KSEF_API_KEY')],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
return $data;
}
$id = 'inv_4fJ2kP9xQwErTy7uI0oPaSdF';
$final = ['accepted', 'rejected', 'skipped', 'error'];
for ($i = 0; $i < 30; $i++) {
$invoice = ksef_get('/v1/invoices/' . $id);
if (in_array($invoice['status'], $final, true)) {
break;
}
sleep(3); // usually 10-60 s until accepted
}
echo $invoice['status'], ' ', $invoice['ksef_number'] ?? '-', PHP_EOL;const id = "inv_4fJ2kP9xQwErTy7uI0oPaSdF";
const final = ["accepted", "rejected", "skipped", "error"];
let invoice;
for (let i = 0; i < 30; i++) {
const res = await fetch(`https://app.ksefshop.pl/v1/invoices/${id}`, {
headers: { Authorization: `Bearer ${process.env.KSEF_API_KEY}` },
});
invoice = await res.json();
if (final.includes(invoice.status)) break;
await new Promise((r) => setTimeout(r, 3000)); // usually 10-60 s until accepted
}
console.log(invoice.status, invoice.ksef_number, invoice.last_error);import os
import time
import requests
invoice_id = "inv_4fJ2kP9xQwErTy7uI0oPaSdF"
headers = {"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}"}
for _ in range(30):
invoice = requests.get(
f"https://app.ksefshop.pl/v1/invoices/{invoice_id}", headers=headers, timeout=30
).json()
if invoice["status"] in ("accepted", "rejected", "skipped", "error"):
break
time.sleep(3) # usually 10-60 s until accepted
print(invoice["status"], invoice["ksef_number"], invoice["last_error"]){
"id": "inv_4fJ2kP9xQwErTy7uI0oPaSdF",
"object": "invoice",
"livemode": false,
"status": "accepted",
"order": {
"external_id": "SO-1001",
"number": "1001"
},
"number": "FV/2026/10/1",
"ksef_number": "4235405606-20261019-0E7570400000-57",
"ksef_environment": "test",
"issue_date": "2026-10-19",
"currency": "PLN",
"total_gross": "163.99",
"buyer_nip": "5260250274",
"pdf_url": "https://app.ksefshop.pl/pdf/cmgxa1b2c0000/Qk9tZ2FsX3ZhbGlkX3Rva2VuX2V4YW1w",
"last_error": null,
"created": 1760865185,
"updated": 1760865214,
"corrections": []
}410 seconds
Download the PDF with the QR code
The PDF carries the KSeF number and the QR code for verifying the invoice in KSeF. For a few seconds after acceptance you may get 409 pdf_not_ready; just retry. Give the buyer the invoice's pdf_url: it works without an API key, and when you send buyer.email, we email the PDF ourselves.
curl https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/pdf \
-H "Authorization: Bearer $KSEF_API_KEY" \
--fail -o invoice.pdf<?php
$ch = curl_init('https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/pdf');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('KSEF_API_KEY'),
],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status !== 200) {
throw new RuntimeException('PDF: HTTP ' . $status); // 409 pdf_not_ready: retry in a few seconds
}
file_put_contents('invoice.pdf', $body);import { writeFile } from "node:fs/promises";
const res = await fetch("https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/pdf", {
headers: {
Authorization: `Bearer ${process.env.KSEF_API_KEY}`,
},
});
if (!res.ok) throw new Error(`PDF: HTTP ${res.status}`); // 409 pdf_not_ready: retry in a few seconds
await writeFile("invoice.pdf", Buffer.from(await res.arrayBuffer()));import os
import requests
resp = requests.get(
"https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/pdf",
headers={
"Authorization": f"Bearer {os.environ['KSEF_API_KEY']}",
},
timeout=30,
)
resp.raise_for_status() # 409 pdf_not_ready: retry in a few seconds
with open("invoice.pdf", "wb") as f:
f.write(resp.content)Next steps
- Webhooks: the KSeF number in your system without polling.
- Idempotency: safe retries after network errors and 5xx.
- Corrections: a returned item as a correction invoice.
- Foreign currency: orders in EUR and other currencies.
- API reference: every field and response.
When something fails
| Response | What to do |
|---|---|
401 api_key_invalid |
Check the header has Bearer and the whole key. |
400 buyer_nip_invalid |
The NIP fails the checksum; for testing use 5260250274. |
status skipped, seller_not_configured |
Complete the seller data in the panel. |
status queued, ksef_not_connected |
Connect KSeF TEST in the panel (test certificate). |
403 live_mode_disabled |
You used an sk_live_ key; during early access use a test key. |
Full list: errors and codes.