Połącz własny system z Zaksio: odczytuj i zapisuj produkty, kontrahentów i faktury, sprawdzaj stany magazynowe i odbieraj zdarzenia webhookiem. Wersja dokumentu: 1.0.0.
Szybki start
Publiczne API Zaksio ma adres bazowy https://api.zaksio.pl i wersję w ścieżce (/v1). Obecna wersja dokumentu: 1.0.0. Zmiany niekompatybilne wprowadzimy tylko jako nową wersję ścieżki; nowe pola w odpowiedziach i nowe adresy mogą pojawiać się w ramach /v1, więc ignoruj pola, których nie znasz.
W aplikacji wejdź w Ustawienia, „API i integracje”, kliknij „Nowy klucz”, wybierz firmę i zakresy, potwierdź hasłem i skopiuj klucz (pokazujemy go tylko raz).
Zapisz klucz w zmiennej środowiskowej, np. ZAKSIO_API_KEY, i sprawdź, że działa:
Odpowiedź pokazuje firmę, do której należy klucz, oraz jego zakresy. Dalej korzystaj z adresów opisanych w sekcji „Adresy API”.
Uwierzytelnianie
Każde zapytanie wymaga nagłówka Authorization: Bearer zk_live_.... Klucz działa w imieniu osoby, która go utworzyła, ale tylko w wybranych zakresach i nigdy nie ma więcej uprawnień niż ta osoba (sprawdzane przy każdym zapytaniu). Klucz jest przypisany do jednej firmy: wszystkie adresy działają na danych tej firmy. Tokeny sesji aplikacji nie działają w /v1, a klucze API nie działają poza /v1.
Brak lub zły klucz: 401 (kody missing_api_key, invalid_api_key).
Klucz odwołany lub wygasły: 401 (api_key_revoked, api_key_expired).
Adres IP spoza listy klucza: 403 (ip_not_allowed).
Brak zakresu: 403 (insufficient_scope).
Licencja w trybie tylko do odczytu: zapisy zwracają 402 (licence_read_only).
Zakresy
Zakres określa, co klucz może robić. Zakresy zamówień i przesyłek są zarezerwowane i jeszcze niedostępne.
Zakresy kluczy API
Zakres
Opis
Stan
products:read
Lista i szczegoly produktow z kartoteki.
dostępny
products:write
Dodawanie i zmiana produktow w kartotece.
dostępny
stock:read
Stany magazynowe produktow (suma i podzial na magazyny).
dostępny
contractors:read
Lista i szczegoly kontrahentow.
dostępny
contractors:write
Dodawanie, zmiana i usuwanie kontrahentow.
dostępny
invoices:read
Lista, szczegoly i PDF faktur sprzedazowych.
dostępny
invoices:write
Tworzenie i wystawianie faktur sprzedazowych (numeracja i ksiegowanie jak w aplikacji).
dostępny
orders:read
Zamowienia ze wszystkich kanalow (w przygotowaniu).
w przygotowaniu
orders:write
Tworzenie i zmiana zamowien (w przygotowaniu).
w przygotowaniu
shipments:read
Przesylki i statusy (w przygotowaniu).
w przygotowaniu
shipments:write
Tworzenie przesylek i etykiet (w przygotowaniu).
w przygotowaniu
Limity i bezpieczeństwo klucza
Limit zapytań: domyślnie 120 na minutę na klucz (konfigurowany przy tworzeniu klucza, do 6000). Każda odpowiedź ma nagłówki RateLimit-Limit, RateLimit-Remaining i RateLimit-Reset (sekundy do końca okna). Po przekroczeniu: 429 (rate_limited) z nagłówkiem Retry-After. Powtórz zapytanie po tym czasie.
Błędne uwierzytelnienia: po 30 nieudanych próbach w minucie z jednego adresu IP kolejne dostają 429 (too_many_failed_auth).
Lista adresów IP, wygasanie, rotacja i odwołanie: ustawiasz je w aplikacji. Rotacja tworzy nowy klucz, a stary działa jeszcze przez wybrany okres przejściowy; odwołanie działa natychmiast.
Przechowywanie: nie umieszczaj klucza w kodzie aplikacji przeglądarkowej ani mobilnej; używaj go tylko po stronie serwera.
Konwencje: kwoty, daty, paginacja
Kwoty są liczbami całkowitymi w groszach (12300 = 123,00 zł).
Ilości to tekst dziesiętny z kropką, np. "2", "14.5", maksymalnie cztery miejsca po przecinku.
Daty to RRRR-MM-DD, a znaczniki czasu to ISO 8601 w UTC. Identyfikatory to UUID.
Paginacja kursorowa: listy przyjmują limit (1 do 200, domyślnie 50), cursor i order (asc od najstarszych, desc od najnowszych). Odpowiedź ma data i pagination z polami hasMore oraz nextCursor; kolejną stronę pobierasz, przekazując nextCursor jako cursor. Kursor jest nieprzezroczysty: nie parsuj go. Dopisanie rekordów w trakcie przeglądania nie przesuwa stron.
Zmiana częściowa (PATCH): wysyłasz tylko pola do zmiany. Pominięte pola zostają bez zmian.
Idempotencja zapisów
Zapisy (POST, PATCH, DELETE) przyjmują nagłówek Idempotency-Key (do 255 znaków ASCII, np. UUID). Pierwsze zapytanie z danym kluczem wykonuje się normalnie, a jego odpowiedź jest zapamiętywana na 24 godziny. Powtórzenie tego samego zapytania z tym samym kluczem zwraca zapamiętaną odpowiedź z nagłówkiem Idempotent-Replayed: true i nie wykonuje zapisu drugi raz. Dzięki temu możesz bezpiecznie ponowić zapytanie po błędzie sieci, np. nie wystawisz dwóch faktur.
Ten sam klucz z innymi danymi: 422 (idempotency_key_reuse).
Ten sam klucz, gdy pierwsze zapytanie jeszcze trwa: 409 (idempotency_in_progress), ponów po chwili.
Błędy 5xx i 429 nie są zapamiętywane, więc ponowienie zostanie wykonane od nowa.
Klucze idempotencji są niezależne dla każdego klucza API.
Błędy
Błędy mają format RFC 9457 (application/problem+json). Opieraj obsługę na polu code i kodzie HTTP, a nie na tekście detail. Pole requestId (również w nagłówku X-Request-Id) podaj w zgłoszeniu do pomocy.
{
"type": "https://zaksio.pl/dokumentacja-api#blad-insufficient-scope",
"title": "Brak dostepu",
"status": 403,
"detail": "Klucz nie ma zakresu products:write (albo tworca klucza utracil odpowiednie uprawnienie)",
"instance": "/v1/products",
"code": "insufficient_scope",
"requestId": "req-1a2b3c"
}
Przy validation_error (400) pole errors zawiera listę nieprawidłowych pól ze wskaźnikiem pointer, np. /lines/0/quantity.
Kody błędów
Kod (code)
HTTP
Znaczenie
missing_api_key
401
Brak naglowka Authorization: Bearer zk_live_...
invalid_api_key
401
Klucz ma zly format albo nie istnieje
api_key_revoked
401
Klucz zostal odwolany
api_key_expired
401
Klucz wygasl (takze po okresie przejsciowym rotacji)
api_key_owner_removed
401
Osoba, ktora utworzyla klucz, nie nalezy juz do organizacji
ip_not_allowed
403
Adres IP zadania nie jest na liscie dozwolonych dla klucza
insufficient_scope
403
Klucz nie ma zakresu wymaganego przez operacje (albo tworca klucza utracil uprawnienie)
feature_disabled
403
Publiczne API jest wylaczone dla organizacji
module_disabled
403
Modul (katalog, magazyn) nie jest wlaczony w profilu firmy
tenant_deleted
403
Organizacja zostala usunieta
licence_read_only
402
Licencja wygasla lub platnosc zalegla: dozwolony tylko odczyt
document_limit_reached
402
Wyczerpano miesieczny limit dokumentow w planie
validation_error
400
Nieprawidlowe dane; lista pol w errors[]
invalid_cursor
400
Kursor paginacji jest nieprawidlowy
invalid_idempotency_key
400
Idempotency-Key ma niedozwolone znaki albo jest za dlugi
not_found
404
Nie ma takiego adresu w API v1
product_not_found
404
Nie znaleziono produktu
contractor_not_found
404
Nie znaleziono kontrahenta (przy tworzeniu faktury: 422)
invoice_not_found
404
Nie znaleziono faktury
sku_or_ean_taken
409
SKU lub kod EAN jest juz uzyty w tej firmie
product_has_stock_history
409
Nie zmienia sie sledzenia partii produktu z historia ruchow
contractor_in_use
409
Kontrahent wystepuje na dokumentach i nie mozna go usunac
invoice_not_draft
409
Wystawionej faktury nie mozna zmieniac ani wystawic ponownie
period_closed
409
Okres ksiegowy jest zamkniety
idempotency_in_progress
409
Zadanie z tym Idempotency-Key jest wlasnie wykonywane; ponow po chwili
idempotency_key_reuse
422
Ten Idempotency-Key byl juz uzyty z innymi danymi zadania
invalid_service
422
Usluga nie ma partii, terminu waznosci ani cech ADR
exemption_basis_required
422
Pozycje zwolnione z VAT (zw) wymagaja podstawy zwolnienia
non_positive_total
422
Kwota faktury musi byc dodatnia
too_many_failed_auth
429
Zbyt wiele blednych prob uwierzytelnienia z jednego adresu
Możliwe błędy: 400, 401, 403, 404, 429, 500 (format opisany w sekcji „Błędy”).
Zmiana produktu
PATCH/v1/products/{id}
Wymagany zakres: products:write
Zmienia tylko przekazane pola. Rodzaju (`kind`) nie zmienia sie; sledzenie partii i terminow waznosci mozna zmienic tylko dla produktu bez historii ruchow magazynowych.
Parametry: Zmiana produktu
Parametr
Gdzie
Typ
Opis
idwymagany
adres
string (uuid)
Idempotency-Key
nagłówek
string
Unikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Stan kazdego produktu, ktory ma ruchy magazynowe: suma oraz podzial na magazyny. Lista jest uporzadkowana po identyfikatorze produktu, a kursor wskazuje ostatni zwrocony produkt.
Parametry: Stany magazynowe
Parametr
Gdzie
Typ
Opis
limit
zapytanie
integer
cursor
zapytanie
string
warehouseId
zapytanie
string (uuid)
Ogranicz do jednego magazynu
Przykład zapytania
curl
curl -X GET "https://api.zaksio.pl/v1/stock?limit=50" \
-H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/stock?limit=50", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests
response = requests.request(
"GET",
"https://api.zaksio.pl/v1/stock",
headers={
"Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
},
params={
"limit": "50",
},
timeout=30,
)
if not response.ok:
problem = response.json() # application/problem+json
raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
Faktury (i korekty) firmy przypisanej do klucza, bez pozycji (szczegoly: `GET /v1/invoices/{id}`). Paginacja kursorowa; do synchronizacji przyrostowej uzyj `order=asc` i zapamietaj ostatni kursor.
Parametry: Lista faktur sprzedazowych
Parametr
Gdzie
Typ
Opis
limit
zapytanie
integer
cursor
zapytanie
string
order
zapytanie
asc | desc
status
zapytanie
draft | issued
contractorId
zapytanie
string (uuid)
issueDateFrom
zapytanie
string (date)
issueDateTo
zapytanie
string (date)
Przykład zapytania
curl
curl -X GET "https://api.zaksio.pl/v1/invoices?status=issued&issueDateFrom=2026-10-01&limit=50" \
-H "Authorization: Bearer $ZAKSIO_API_KEY"
JavaScript (Node.js)
const response = await fetch("https://api.zaksio.pl/v1/invoices?status=issued&issueDateFrom=2026-10-01&limit=50", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.ZAKSIO_API_KEY}`,
},
});
const data = await response.json();
if (!response.ok) throw new Error(`${data.code}: ${data.detail}`); // application/problem+json
console.log(data);
Python
import os
import requests
response = requests.request(
"GET",
"https://api.zaksio.pl/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['ZAKSIO_API_KEY']}",
},
params={
"status": "issued",
"issueDateFrom": "2026-10-01",
"limit": "50",
},
timeout=30,
)
if not response.ok:
problem = response.json() # application/problem+json
raise RuntimeError(f"{problem['code']}: {problem['detail']}")
print(response.json())
Możliwe błędy: 400, 401, 403, 429, 500 (format opisany w sekcji „Błędy”).
Wystawienie faktury
POST/v1/invoices
Wymagany zakres: invoices:write
Tworzy fakture sprzedazowa i (domyslnie) od razu ja wystawia: numeracja bez luk, ksiegowanie i podsumowanie VAT jak w aplikacji. Kwoty w groszach, ilosc jako tekst dziesietny. Wystawienie i zapis to jedna transakcja: przy bledzie nie zostaje szkic. **Zawsze podawaj `Idempotency-Key`**, zeby ponowienie po bledzie sieci nie wystawilo faktury drugi raz.
Parametry: Wystawienie faktury
Parametr
Gdzie
Typ
Opis
Idempotency-Key
nagłówek
string
Unikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Możliwe błędy: 400, 401, 403, 404, 429, 500 (format opisany w sekcji „Błędy”).
PDF faktury
GET/v1/invoices/{id}/pdf
Wymagany zakres: invoices:read
Plik PDF faktury w tym samym ksztalcie co w aplikacji (`Content-Type: application/pdf`). Pobieraj PDF wystawionej faktury: szkic nie ma jeszcze numeru.
Możliwe błędy: 400, 401, 403, 404, 429, 500 (format opisany w sekcji „Błędy”).
Wystawienie szkicu faktury
POST/v1/invoices/{id}/issue
Wymagany zakres: invoices:write
Wystawia fakture zapisana wczesniej jako szkic (z API albo z aplikacji). Wystawionej faktury nie mozna wystawic ponownie (409 `invoice_not_draft`). Obsluguje `Idempotency-Key`.
Parametry: Wystawienie szkicu faktury
Parametr
Gdzie
Typ
Opis
idwymagany
adres
string (uuid)
Idempotency-Key
nagłówek
string
Unikalny klucz zapisu (np. UUID). Powtorzenie zadania z tym samym kluczem zwraca zapamietana odpowiedz zamiast wykonywac zapis drugi raz (24 h).
Możliwe błędy: 400, 401, 402, 403, 404, 409, 422, 429, 500 (format opisany w sekcji „Błędy”).
Webhooki
Webhook to zapytanie POST, które Zaksio wysyła na Twój adres (https, publiczny), gdy wystąpi zdarzenie. Subskrypcje dodajesz w aplikacji (Ustawienia, „API i integracje”, zakładka „Webhooki”): podajesz adres, wybierasz zdarzenia, ewentualnie jedną firmę, i zapisujesz sekret podpisu (widoczny tylko raz).
Koperta i nagłówki
Treść to obiekt JSON z polami id (identyfikator zdarzenia, stały przy ponowieniach), type, apiVersion, createdAt, companyId i data (dane w tym samym kształcie co w odpowiedziach API). Zapytanie ma nagłówki Zaksio-Event, Zaksio-Event-Id, Zaksio-Delivery i Zaksio-Signature.
Zdarzenia webhooków
Zdarzenie
Kiedy
invoice.issued
Faktura wystawiona. Faktura sprzedazowa (lub korekta) zostala wystawiona i zaksiegowana.
invoice.paid
Faktura oplacona. Na fakturze zarejestrowano pierwsza wplate (pole paidAt ma wartosc).
contractor.created
Kontrahent dodany. Dodano kontrahenta (z aplikacji, API albo importu).
contractor.updated
Kontrahent zmieniony. Zmieniono dane kontrahenta.
product.created
Produkt dodany. Dodano produkt do kartoteki.
product.updated
Produkt zmieniony. Zmieniono dane produktu w kartotece (takze dezaktywacja).
product.stock_changed
Stan produktu zmieniony. Zmienil sie stan magazynowy produktu. Kilka ruchow zapisanych w krotkim czasie (zanim zdarzenie zostanie rozeslane) zlewa sie w jedno zdarzenie z aktualnym stanem.
Przykłady danych dla pozostałych zdarzeń znajdziesz w pliku OpenAPI (pole x-webhook-events). Przycisk „Test” wysyła zdarzenie webhook.test z przykładową treścią, żebyś mógł sprawdzić adres i weryfikację podpisu.
Weryfikacja podpisu
Nagłówek Zaksio-Signature ma postać t=<unix>,v1=<hex>. Podpis to HMAC-SHA256 z sekretu subskrypcji nad tekstem <t>.<surowa treść zapytania>. Sprawdź podpis na surowej treści (nie na ponownie zserializowanym JSON), porównaj go w stałym czasie i odrzuć zapytania ze znacznikiem t starszym niż 5 minut. Podczas wymiany sekretu nagłówek może mieć kilka wartości v1=: wystarczy, że jedna pasuje.
Sprawdzanie podpisu
JavaScript (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: surowa tresc zadania (Buffer lub string), NIE ponownie zserializowany JSON
export function verifyZaksioSignature(secret, header, rawBody, toleranceSeconds = 300) {
const parts = header.split(",").map((p) => p.trim());
const timestamp = Number(parts.find((p) => p.startsWith("t="))?.slice(2));
if (!Number.isInteger(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
return parts
.filter((p) => p.startsWith("v1="))
.some((p) => {
const given = Buffer.from(p.slice(3), "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
});
}
Python
import hashlib
import hmac
import time
def verify_zaksio_signature(secret: str, header: str, raw_body: bytes, tolerance: int = 300) -> bool:
parts = [p.strip() for p in header.split(",")]
try:
timestamp = int(next(p[2:] for p in parts if p.startswith("t=")))
except (StopIteration, ValueError):
return False
if abs(time.time() - timestamp) > tolerance:
return False
signed = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return any(
hmac.compare_digest(p[3:], expected) for p in parts if p.startswith("v1=")
)
#!/usr/bin/env bash
# Sprawdzenie podpisu z wiersza polecen (do diagnozy): SECRET, HEADER i plik z surowa trescia zadania
SECRET="whsec_..."
HEADER="t=1760261400,v1=..." # naglowek Zaksio-Signature
BODY_FILE="body.json" # surowa tresc zadania, bajt w bajt
T=$(echo "$HEADER" | tr ',' '\n' | sed -n 's/^t=//p')
EXPECTED=$( (printf '%s.' "$T"; cat "$BODY_FILE") | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
if echo "$HEADER" | tr ',' '\n' | grep -qx "v1=$EXPECTED"; then echo "podpis poprawny"; else echo "podpis NIEPOPRAWNY"; fi
# pamietaj tez o sprawdzeniu, ze t= nie jest starsze niz 5 minut
Dostawa i ponowienia
Dostawa jest udana po odpowiedzi 2xx w ciągu 10 sekund. Przekierowania nie są obsługiwane (traktujemy je jako błąd).
Po błędzie ponawiamy po 1 minucie, 5 minutach, 30 minutach, 2, 6 i 12 godzinach (łącznie 7 prób). Potem dostawa ma stan „nie udało się”; możesz ją ponowić ręcznie w dzienniku dostaw.
Po 25 nieudanych próbach z rzędu subskrypcja jest wyłączana automatycznie.
Dostawa jest „co najmniej raz”: to samo zdarzenie może przyjść więcej niż raz, a kolejność nie jest gwarantowana. Rozpoznawaj zdarzenia po polu id i odpowiadaj szybko (2xx), a ciężką obróbkę rób asynchronicznie.
Zdarzenia o stanie produktu mogą zlewać się: kilka zmian zapisanych w krótkim czasie daje jedno zdarzenie z aktualnym stanem.
Adresy wewnętrzne, lokalne i prywatne są blokowane (ochrona przed SSRF), także po rozwiązaniu nazwy DNS.
Historia zmian
1.0.0: Pierwsza wersja API v1 (12 października 2026)
Uwierzytelnianie kluczem API (Authorization: Bearer zk_live_...), zakresy, lista adresów IP, wygasanie, rotacja z okresem przejściowym i odwołanie.
Produkty: lista, szczegóły, dodawanie i zmiana częściowa.
Stany magazynowe: lista i szczegóły produktu (suma oraz podział na magazyny).
Kontrahenci: lista, szczegóły, dodawanie, zmiana częściowa i usuwanie.
Faktury sprzedażowe: lista, szczegóły, PDF, wystawianie (od razu albo ze szkicu) z numeracją i księgowaniem jak w aplikacji.
Idempotency-Key na zapisach, paginacja kursorowa, błędy w formacie RFC 9457 oraz nagłówki limitów RateLimit-*.
Webhooki: faktura wystawiona i opłacona, kontrahent i produkt dodany lub zmieniony, zmiana stanu produktu; podpis HMAC-SHA256 ze znacznikiem czasu, ponowienia z narastającym odstępem, dziennik dostaw i test połączenia.
Zamówienia i przesyłki: zakresy są zarezerwowane, adresy pojawią się razem z modułem zamówień (nie są częścią tej wersji).