KSeF Faktury
EN
Join the start list
Early access

The API is in early access: we hand out test keys (KSeF TEST) in batches to people on the launch list. Live mode comes later.

Request access

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.

  1. 1Check the key and the KSeF connection
  2. 2Send a paid order
  3. 3Get the KSeF number
  4. 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.

GET /v1/account
curl https://app.ksefshop.pl/v1/account \
  -H "Authorization: Bearer $KSEF_API_KEY"
Response
{
  "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.

POST /v1/invoices
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"
      }
    ]
  }
}'
Response
{
  "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.

Wait for the KSeF number
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}'
Response
{
  "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.

GET /v1/invoices/{id}/pdf
curl https://app.ksefshop.pl/v1/invoices/inv_4fJ2kP9xQwErTy7uI0oPaSdF/pdf \
  -H "Authorization: Bearer $KSEF_API_KEY" \
  --fail -o invoice.pdf

Next steps

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.

KSeF Faktury

API early access

We hand out test keys in batches. Leave your email: we will write when your access is ready.