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