Przewodnik integracji

Integracja KSeF API w PHP

Jak wysłać fakturę KSeF FA(3) z PHP przez API Notaly (cURL): POST /invoices, obsługa błędów i śledzenie statusu.

Aktualizacja

Wprowadzenie

PHP wciąż napędza mnóstwo systemów fakturowania, a Notaly pozwala podłączyć KSeF do istniejącej aplikacji w kilku linijkach. Wysyłasz dane faktury jako tablicę JSON pod adres https://api.getnotaly.com/invoices, a Notaly tworzy plik FA(3), weryfikuje go względem schematu i przekazuje do KSeF. Przykład korzysta z wbudowanego rozszerzenia cURL, więc działa bez Composera i zewnętrznych pakietów.

Co przygotować

Załóż darmowe konto sandbox i skopiuj klucz API. Sandbox akceptuje wyłącznie NIP sprzedawcy 2745013933 (testowy NIP platformy); inny NIP kończy się błędem 422 seller_nip_not_authorized i nie zużywa żadnej z trzech darmowych faktur testowych. Klucz trzymaj w zmiennej środowiskowej odczytywanej przez getenv().

Wysyłka faktury

<?php
$key = getenv('NOTALY_API_KEY');

$faktura = [
    '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],
    ],
];

$ch = curl_init('https://api.getnotaly.com/invoices');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . $key,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($faktura),
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

$utworzona = json_decode($body, true);
if ($status >= 400) {
    fwrite(STDERR, $status . ': ' . ($utworzona['error'] ?? 'nieznany') . PHP_EOL);
    exit(1);
}
echo 'Faktura ' . $utworzona['id'] . ' ma status ' . $utworzona['status'] . PHP_EOL;

curl_setopt_array ustawia wszystko w jednym miejscu: metodę POST, nagłówki JSON i zakodowaną treść. Po wywołaniu curl_getinfo($ch, CURLINFO_RESPONSE_CODE) zwraca kod HTTP, więc oddzielisz sukces od błędu jeszcze przed dekodowaniem odpowiedzi.

Odpowiedź API

Przy sukcesie serwer zwraca kod 201 i obiekt JSON. status to submitted, gdy KSeF przyjął zgłoszenie, albo draft, gdy KSeF był chwilowo niedostępny i fakturę zapisano do ponowienia. Pole id posłuży Ci później do sprawdzania statusu; zaakceptowana faktura zawiera też upo i pdfUrl. Notaly weryfikuje i wysyła fakturę; stawki VAT i klasyfikacja podatkowa pozostają po Twojej stronie.

Obsługa błędów

Czytaj ustrukturyzowane pole error, a nie treść komunikatu. validation_failed (422) zawiera tablicę details z konkretnymi polami do poprawy. quota_exceeded (402) oznacza wyczerpanie trzech darmowych faktur sandbox. 401 to brakujący lub błędny klucz, 404 to poprawny klucz przy cudzym zasobie, a 503 ksef_unavailable to stan przejściowy — ponów z backoffem.

Doprowadź fakturę do końca

Status odczytasz przez GET /invoices/{id} z tym samym tokenem albo przez webhooki. Cykl życia to draft → submitted → accepted lub rejected; odrzucona faktura niesie kod error, np. ksef_rejected:<kod>.

Źródła

Źródło: getnotaly.com

Najczęstsze pytania

Jakiej wersji PHP potrzebuję?

PHP 7.4+ z włączonym rozszerzeniem cURL, które jest standardowe w większości środowisk.

Czy mogę użyć Guzzle zamiast cURL?

Tak. Każdy klient HTTP zadziała, o ile wysyła JSON z nagłówkiem Authorization: Bearer.

Wystaw pierwszą fakturę testową w KSeF jeszcze dziś

Załóż konto sandbox: klucz API w kilka sekund, 3 darmowe faktury testowe w KSeF TEST, bez karty płatniczej.