Integration guide

KSeF API Python integration guide

Send a KSeF FA(3) invoice from Python with the Notaly API using requests: POST /invoices, error handling and status tracking.

Updated

Overview

Python is a natural fit for issuing KSeF invoices from a backend service, a cron job or a data pipeline. With Notaly you never touch FA(3) XML by hand: you POST a JSON dictionary to https://api.getnotaly.com/invoices and the platform converts it, validates it against the official XSD and forwards it to KSeF. This guide uses the popular requests library, which you can install with pip install requests.

Setup

Grab your API key from the Notaly dashboard after creating a free sandbox account. In the sandbox the seller NIP must be exactly 2745013933; anything else is rejected with 422 seller_nip_not_authorized before the invoice is counted against your three free test invoices. Keep the key in an environment variable (NOTALY_API_KEY) rather than hard-coding it in source.

Create an invoice

import os
import requests

NOTALY_API_KEY = os.environ["NOTALY_API_KEY"]

invoice = {
    "number": "FV/2026/10/001",
    "issueDate": "2026-10-12",
    "currency": "PLN",
    "seller": {"nip": "2745013933", "name": "Example Sp. z o.o. (FICTIONAL)", "address": {"line1": "ul. Testowa 1, 00-001 Warszawa", "country": "PL"}},
    "buyer": {"nip": "1234563218", "name": "Test Customer S.A. (FICTIONAL)", "address": {"line1": "ul. Probna 2, 30-001 Kraków", "country": "PL"}},
    "items": [
        {"name": "SaaS subscription - October", "unit": "pcs.", "quantity": 1, "unitNetPrice": 1000, "vatRate": 23},
        {"name": "Support", "unit": "hrs.", "quantity": 2, "unitNetPrice": 150.5, "vatRate": 8},
    ],
}

response = requests.post(
    "https://api.getnotaly.com/invoices",
    headers={"Authorization": f"Bearer {NOTALY_API_KEY}"},
    json=invoice,
    timeout=30,
)
response.raise_for_status()
created = response.json()
print(f"Invoice {created['id']} is {created['status']}")

requests.post(..., json=invoice) serialises the dictionary for you and sets Content-Type: application/json. The timeout=30 guards against hanging requests. On HTTP errors, raise_for_status() turns a 4xx or 5xx into an exception you can catch and retry.

Understand the response

The response is a JSON object with id and status. status is submitted when KSeF accepted the submission, or draft when KSeF was temporarily unreachable and the invoice was persisted for a later retry. Accepted invoices gain upo and pdfUrl fields. Notaly validates and submits the invoice; VAT rates and tax classification remain your responsibility.

React to failures

Branch on the JSON body rather than parsing messages. validation_failed (422) comes with a details list of field-level problems. quota_exceeded (402) means your three free sandbox invoices are spent. ksef_unavailable (503) is transient — retry with exponential backoff. Authentication problems surface as 401 (missing or invalid key) or 404 (valid key, wrong resource).

Track status

Poll GET /invoices/{id} with the same Authorization header, or configure webhooks. The invoice moves through draft → submitted → accepted or rejected; rejected invoices carry an error code you can log and act on.

Sources

Source: getnotaly.com

Frequently asked questions

Do I need to install anything?

Only the requests library: pip install requests. Python 3.8+ is recommended.

How do I make retries safe?

Send an Idempotency-Key header so the same invoice is not created twice on a retry.

Issue your first KSeF test invoice today

Create a sandbox account: API key in seconds, 3 free test invoices on KSeF TEST, no credit card.