Dokumentacja API
Zasoby
Pracownicy
Odczyt kartotek, eksport gotowych plików kadrowych, aktualizacja pól i dwukierunkowa synchronizacja z programem kadrowym. Zapisy nie tworzą nowych pracowników, aktualizują dopasowanych (merge zachowawczy).
Klucz obejmuje jedną spółkę
Klucz API wystawia się dla konkretnej spółki (płatnika) i widzi wyłącznie jej dane: pracowników, listy płac, godziny i faktury. Jeśli obsługujesz kilka spółek, wystaw osobny klucz dla każdej. Podanie w zapytaniu identyfikatora innej spółki zwraca 403 z kodemcompany_out_of_scope.Model kanoniczny
W środku utrzymujemy jeden kanoniczny model pracownika, z którego budowane są wszystkie eksporty. Import i PATCH nigdy nie zerują danych, uzupełniają tylko puste lub zmienione pola. Zakładanie pracownika od zera odbywa się osobnym przepływem onboardingu.GET/employees
Lista pracowników tenanta (dane podsumowujące, bez odszyfrowanego PII). Odpowiedź jest stronicowana.
Domyślnie zwracamy tylko osoby zatrudnione: obecnych pracowników i tych, którym zakończono zatrudnienie. Zgłoszenia w trakcie onboardingu, anulowane i takie, którym wygasł link, nie wychodzą przez API. Pełną listę, razem z lejkiem rekrutacyjnym, zwraca include=all.
Parametry zapytania
includestringopcjonalne | employees (domyślne) albo all. 'all' dokłada zgłoszenia w trakcie onboardingu, anulowane i wygasłe. |
statusstringopcjonalne | filtr po jednym statusie, np. podpisany, zakonczony, oczekuje_kontrasygnaty, wygasly. Wartości są takie jak w panelu, po polsku. |
companyIduuidopcjonalne | ogranicz do jednej firmy (płatnika). Klucz i tak widzi wyłącznie swoją spółkę; podanie innej zwraca 403 company_out_of_scope. |
updatedSinceISO-8601opcjonalne | tylko rekordy zmienione od podanego momentu, do synchronizacji przyrostowej. |
pageintegeropcjonalne | numer strony (domyślnie 1). |
pageSizeintegeropcjonalne | rozmiar strony (domyślnie 50, maks. 200). |
curl -H "Authorization: Bearer onb_..." \
"https://www.onboardly.work/api/v1/employees?updatedSince=2026-07-01&pageSize=50"{
"data": [{
"id": "uuid", "firstName": "Jan", "lastName": "Kowalski",
"email": "jan@example.com", "phone": "+48…", "status": "podpisany",
"contractType": "zlecenie", "isForeigner": false, "nationalityType": null,
"position": "Kelner", "companyId": "uuid",
"startDate": "2026-06-01", "endDate": null,
"salaryNet": null, "salaryGross": 4806, "salaryKind": "brutto",
"hourlyRate": null, "hourlyRateKind": null,
"workTime": { "numerator": 1, "denominator": 2 },
"employmentContractKind": "okreslony",
"projects": [{ "id": "uuid", "name": "Bocca", "clientName": "OCTO", "dateFrom": "2026-06-01", "dateTo": null }],
"projectName": "Bocca",
"updatedAt": "2026-07-10T12:00:00.000Z"
}],
"page": 1, "pageSize": 50, "total": 130, "totalPages": 3
}GET/employees (blok podatkowy)
Każdy wiersz listy niesie dodatkowo blok podatkowy: adresy, urząd skarbowy, pełne oświadczenie PIT-2 i dane o rozwiązaniu umowy. Nie wymaga to nowego klucza ani zmiany uprawnień — pola dochodzą do odpowiedzi, której kształt znacie, więc integracja czytająca wybrane pola działa bez zmian.
{
"address": {
"street": "Hetmańska", "houseNumber": "25", "apartmentNumber": null,
"postalCode": "15-727", "city": "Białystok", "post": "Białystok",
"gmina": "Białystok", "powiat": "Białystok", "voivodeship": "podlaskie",
"country": "PL"
},
"registeredAddress": null, "correspondenceAddress": null,
"taxAddressSource": "residence",
"taxOffice": { "name": "Urząd Skarbowy w Białymstoku", "code": "2001" },
"nip": null,
"pit2": {
"applied": true, "mode": "1/12", "declared": true, "source": "wizard",
"declarations": {
"reliefReturn": false, "reliefReturnFrom": null, "reliefReturnTo": null,
"relief4plus": false, "reliefSenior": false,
"jointSpouse": false, "jointIncomeVariant": null, "singleParent": false,
"noAdvance": false, "noAdvanceYear": null,
"resignYoungRelief": false, "resignKup": false, "resign50Kup": false,
"withdrawReduction": false, "withdrawJoint": false, "withdrawElevatedKup": false,
"withdrawExemptions": false, "withdrawYoungRelief": false, "withdrawKup": false,
"withdraw50Kup": false, "withdrawNoAdvance": false,
"art42eMode": null, "art42eWithdraw": null,
"commuteKup": false
}
},
"ppkResign": false,
"ppk": { "resigned": false, "joinedAt": "2026-07-01", "resignedAt": null },
"termination": {
"type": "dyscyplinarne",
"typeLabel": "Rozwiązanie umowy bez wypowiedzenia (art. 52 KP)",
"date": "2026-09-30",
"zusDeregistrationCode": "100",
"optimaCode": "7"
}
}Na co uważać
pit2.appliedbooleanopcjonalne | czy stosujemy kwotę zmniejszającą u tego płatnika. To jest pole decydujące o naliczeniu. |
pit2.declaredbooleanopcjonalne | czy w systemie jest udokumentowane oświadczenie. UWAGA: declared = true przy applied = false to poprawny stan — pracownik złożył oświadczenie, w którym NIE wnosi o stosowanie PIT-2 u nas (typowe przy zleceniu i dziele albo gdy korzysta z ulgi u innego pracodawcy). |
pit2.modeenumopcjonalne | ułamek kwoty zmniejszającej: 1/12, 1/24 albo 1/36. Wypełniony TYLKO gdy applied = true. null przy applied = false nie znaczy braku oświadczenia — o tym mówi declared. |
pit2.sourceenumopcjonalne | skąd mamy oświadczenie: wizard (kreator), hr (teczka), paper (deklaracja pisemna). null = deklaracji nie ma, a applied pochodzi ze starej kolumny z wartością domyślną; wtedy declared = false. |
pit2.declarationsobjectopcjonalne | oświadczenia z wzoru PIT-2(9). noAdvanceYear mówi, w którym roku złożono wniosek z części J — obowiązuje tylko w nim. jointSpouse i resignKup dotyczą wyłącznie umowy o pracę. |
pit2.thresholdModeenumopcjonalne | pole „Progi podatkowe" z kartoteki Optimy, wyliczone z części E wzoru PIT-2: `standardowe` albo `malzonek_lub_samotny_rodzic`. Trzecia wartość słownika Optimy, `indywidualne`, **nigdy nie wychodzi z Onboardly**: oznacza ręcznie ustawione własne progi na karcie pracownika, a takiej funkcji u nas nie ma. Zostaje w wykazie, żeby mapowanie pokrywało pełny słownik. |
pit2.declarations.withdraw*booleanopcjonalne | wycofania uprzednio złożonych oświadczeń (poz. 7, 11, 13, 15, 17, 19, 21). Wzór nie zna „odznaczenia": zmiana deklaracji następuje przez złożenie nowej, a wycofanie jest osobną pozycją. Bez tych pól nie odróżnisz „nic nie złożył" od „złożył i wycofał", a to są dwa różne stany przy naliczaniu zaliczki. |
pit2.declarations.art42eModeenumopcjonalne | część D wzoru: pomniejszenie u INNEGO płatnika z art. 42e (organ egzekucyjny albo podmiot przejmujący zobowiązania zakładu pracy). Dopuszczalne wyłącznie 1/24 i 1/36, bo 1/12 taki płatnik stosuje z urzędu. null = nie złożono. My tego oświadczenia nie przyjmujemy, pole służy odnotowaniu, że istnieje, bo wpływa na łączną kwotę pomniejszenia. |
pit2.declarations.art42eWithdrawenumopcjonalne | część D poz. 9: „split" = wycofanie oświadczenia o podziale kwoty zmniejszającej, „resign_1_12" = rezygnacja ze stosowania 1/12. Pozycja dopuszcza jedno z dwóch, nie oba. |
ppkobjectopcjonalne | resigned to dotychczasowa flaga tak/nie, joinedAt i resignedAt to daty w formacie YYYY-MM-DD. Wpłaty nalicza się OD DATY, sama flaga nie wystarcza. null znaczy „nie mamy daty", nie „nie było zdarzenia": nie podstawiamy w to miejsce daty zatrudnienia. Rezygnacja nie kasuje daty przystąpienia: oba zdarzenia zaszły naprawdę. |
ppkResignbooleanopcjonalne | zostaje dla kluczy, które już to czytają. Ta sama wartość co ppk.resigned. |
schoolTypeenumopcjonalne | rodzaj ukończonej szkoły: podstawowa, zasadnicza_zawodowa, srednia_ogolnoksztalcaca, srednia_zawodowa, policealna, wyzsza. Uszczegóławia `educationLevel`, bo „średnie" to w prawie dwie różne szkoły. |
schoolYearsForLeavenumberopcjonalne | lata nauki wliczane do stażu urlopowego (art. 155 § 1 KP): 3, 4, 5, 6 albo 8. Dla szkoły podstawowej **0, i to jest wartość prawdziwa**, nie brak danych; brak danych to `null`. Okresów nauki się NIE sumuje, liczy się jedna ukończona szkoła. |
powiatstringopcjonalne | uzupełniany z kodu pocztowego przy trafieniu dokładnym, tak jak miasto i gmina. Przy dopasowaniu po dwucyfrowym prefiksie zostaje pusty. Teczki sprzed 25.09.2026 mają go pustego, dopóki adres nie zostanie zapisany ponownie. |
termination.datedateopcjonalne | faktyczne rozwiązanie; bywa różne od endDate, które opisuje koniec umowy terminowej znany z góry. Dla ZUS to nie to samo zdarzenie. null dopóki umowa trwa. |
termination.zusDeregistrationCodestringopcjonalne | kod przyczyny wyrejestrowania na ZUS ZWUA: 100 = ustanie tytułu, 500 = zgon. |
termination.typeenumopcjonalne | przyczyna rozwiązania umowy. Lista ma 36 wartości: 35 odpowiada pozycjom słownika Comarch ERP Optima, a siedem kluczy sprzed rozszerzenia zostało, bo wiszą na nich zamknięte teczki. Mapuj po `optimaCode`, nie po nazwie klucza. |
termination.optimaCodestringopcjonalne | numer pozycji w słowniku Optimy, od 1 do 35. `null` dla teczek zamkniętych przed wprowadzeniem słownika. Uwaga: dwie wartości wskazują na pozycję 1, bo historyczne „wypowiedzenie bez podania przyczyny" to u Optimy zwykłe wypowiedzenie przez pracodawcę. |
registeredAddressobjectopcjonalne | wypełniony tylko gdy pracownik zadeklarował adres inny niż zamieszkania. null nie znaczy „brak danych", tylko „taki sam jak zamieszkania". |
GET/employees/{id}
Pełna kartoteka kanoniczna z odszyfrowanymi danymi osobowymi. Każde wywołanie loguje dostęp do PII (RODO art. 30).
Odpowiedź niesie też wszystko, co wisi przy pracowniku: assignments (przypisania projektowe ze stawką), documents (metadane teczki, bez zawartości plików), deductions (potrącenia), wageComponents (składniki wynagrodzenia z flagami zusable i taxable), benefits, family (członkowie rodziny zgłoszeni do ZUS), trainings, leave oraz vacationBalance.
{
"data": {
"onboardingId": "uuid", "status": "podpisany",
"firstName": "Jan", "lastName": "Kowalski", "email": "…", "phone": "…",
"pesel": "86010112345", "birthDate": "1986-01-01", "placeOfBirth": "Warszawa",
"nationality": "PL", "isForeigner": false,
"residenceAddress": { "street": "…", "houseNumber": "…", "postalCode": "00-001", "city": "Warszawa", "gmina": "…", "powiat": "…", "voivodeship": "mazowieckie", "country": "PL" },
"registeredAddress": null, "correspondenceAddress": null, "taxAddressSource": "residence",
"contractType": "zlecenie", "position": "Kelner", "educationLevel": "srednie",
"schoolType": "srednia_zawodowa", "schoolYearsForLeave": 5,
"occupationCode": "941201", "startDate": "2026-06-01", "endDate": null,
"workTime": null, "employmentContractKind": null, "probationEndDate": null,
"hourlyRate": 30.0, "bankAccount": "PL…", "bankName": "mBank",
"insurance": { "code": "041100", "confidence": "low", "requiresZusRegistration": true, "document": "ZUA" },
"nfzCode": "07", "pit2": true, "pit2Mode": "1/12", "pit2Declared": true, "pit2Source": "wizard",
"pit2Declarations": { "reliefReturn": false, "noAdvance": false, "…": false },
"ppkResign": false, "ppkJoinedAt": "2026-07-01", "ppkResignedAt": null,
"taxOfficeName": "…", "taxOfficeCode": "…",
"terminationType": null, "terminationDate": null, "zusDeregistrationCode": null,
"employer": { "name": "…", "nip": "…", "regon": "…", "address": "…" }
}
}| HTTP | code | Znaczenie |
|---|---|---|
404 | not_found | brak pracownika w tym tenancie. |
410 | pii_deleted | dane osobowe usunięte (RODO). |
GET/worker-hours/{yearMonth}
Godziny zgłoszone przez pracowników za dany miesiąc, czyli raporty z portalu pracownika. To źródło, a nie wynik: /payroll/{yearMonth} zwraca wyliczone listy płac, a /settlements/{id} zbiorcze rozliczenie projektu. Filtry: companyId, projectId, status, onboardingId.
{
"yearMonth": "2026-09",
"data": [{
"id": "uuid", "onboardingId": "uuid", "worker": "Jan Kowalski",
"companyId": "uuid", "contractType": "zlecenie", "yearMonth": "2026-09",
"status": "approved",
"totalHours": 168.5, "vacationDays": 2, "sickDays": 0,
"netAmountOverride": null,
"byProject": [
{ "projectId": "uuid", "projectName": "Sartoria — Centrum", "hours": 120 },
{ "projectId": "uuid", "projectName": "Hotel — Housekeeping", "hours": 48.5 }
],
"submittedAt": "2026-10-02T09:12:00.000Z",
"approvedAt": "2026-10-03T11:40:00.000Z", "approvedBy": "kadry@firma.pl"
}],
"count": 1
}Na co uważać
statusenumopcjonalne | draft | submitted | client_approved | client_rejected | approved | rejected. Do rozliczeń bierz approved; submitted czeka jeszcze na akceptację. |
byProjectarrayopcjonalne | podział godzin na projekty, liczony z wpisów dziennych (tylko typ „praca"). Raport miesięczny jest jeden na pracownika, więc bez tego nie rozliczysz miesiąca z pracą na dwóch projektach. Suma pozycji może być mniejsza niż totalHours, gdy część dni nie ma wskazanego projektu. |
netAmountOverridenumberopcjonalne | kwota netto wpisana ręcznie przez kadry; wygrywa z wyliczeniem godziny × stawka. null = licz ze stawki. |
GET/employees/{id}/export?format=
Zwraca gotowy plik (nie JSON) dla programu kadrowego. Nagłówek Content-Disposition: attachment; ewentualne ostrzeżenia w nagłówku X-Payroll-Warnings (URL-encoded JSON).
Parametr zapytania
formatenumopcjonalne | kedu | optima | symfonia | csv | xlsx (domyślnie kedu). KEDU to uniwersalne zgłoszenie ZUS czytane przez Płatnik, Optima, Symfonia, enova i Gratyfikant. |
curl -H "Authorization: Bearer onb_..." \
"https://www.onboardly.work/api/v1/employees/{id}/export?format=kedu" -o kedu.xml| HTTP | code | Znaczenie |
|---|---|---|
400 | — | nieznany format. |
422 | incomplete | brak wymaganych pól, ciało zawiera { blockers[], warnings[] }. |
501 | format_not_ready | format jeszcze niegotowy dla tej ścieżki. |
GET/employees/{id}/ift?year=
Dane do informacji IFT-1/IFT-1R (nierezydent): dane osobowe, zagraniczny TIN, adres zagraniczny, rachunek (IBAN + SWIFT/BIC), certyfikat rezydencji, dokumenty legalizacyjne z datami ważności oraz suma przychodów i pobranego podatku z rachunków wskazanego roku.
Parametr zapytania
yearstringopcjonalne | Rok podatkowy (domyślnie bieżący), np. 2026. |
curl -H "Authorization: Bearer onb_..." \
"https://www.onboardly.work/api/v1/employees/{id}/ift?year=2026"| HTTP | code | Znaczenie |
|---|---|---|
400 | not_foreigner | pracownik nie jest cudzoziemcem: IFT nie dotyczy. |
404 | not_found | nie znaleziono pracownika. |
POST/employees/import-result
Informacja zwrotna z importera: wynik zapisu jednej kartoteki w zewnętrznym systemie kadrowym. Jedno wywołanie oznacza jedną kartotekę.
{
"employeeId": "88ab2b33-6849-46ec-8d93-eca5e0446873",
"status": "error",
"message": "Niepoprawny numer PESEL, kartoteka nie została zapisana.",
"system": "optima"
}Pola
employeeIdUUIDopcjonalne | identyfikator pracownika w Onboardly, czyli onboardingId z GET /employees. |
statusenumopcjonalne | ok albo error. Przy ok komunikat jest pomijany. |
messagestringopcjonalne | opis błędu. Nazwa pola i opis problemu, bez wartości danych osobowych. Dłuższy niż 500 znaków jest przycinany. |
systemstringopcjonalne | opcjonalnie, nazwa systemu (np. optima). Pozwala rozróżnić źródła, gdy dojdzie drugie. |
Na co uważać
Zatarcie danych osobowychserweropcjonalne | z `message` usuwamy ciągi wyglądające na PESEL, numer konta, NIP, REGON i adres e-mail, zastępując je [usunięto]. Kolumna z komunikatem nie jest szyfrowana i trafia na ekran bez kontroli dostępu do PII. Nazwa pola i opis problemu zostają nietknięte, a odpowiedź oddaje `message` PO zatarciu, żeby było widać, co zapisaliśmy. |
Pętla poprawekprocesopcjonalne | przy status: error kadry widzą błąd w trzech miejscach: pasek w teczce, oznaczenie na liście pracowników i powiadomienie na pulpicie. Po poprawieniu danych rośnie updatedAt kartoteki, więc importer może ponowić zapis. |
Odhaczenie przez kadryprocesopcjonalne | każdy kolejny wynik, także ok, kasuje wcześniejsze odhaczenie. Odhaczenie sprzed tygodnia nie ukryje dzisiejszego błędu, a udany zapis gasi powiadomienie sam. |
PATCH/employees/{id}
Aktualizacja pól kadrowych (kolumny płaskie). Wszystkie pola opcjonalne. PII i adres nie są edytowane tą drogą.
Ciało żądania
positionstringopcjonalne | stanowisko. |
salaryNetnumberopcjonalne | wynagrodzenie netto. |
hourlyRatenumberopcjonalne | stawka godzinowa. |
startDate / endDateYYYY-MM-DDopcjonalne | daty zatrudnienia. |
nfzCode / nfzBranchstringopcjonalne | kod i oddział NFZ. |
usName / usCodestringopcjonalne | nazwa i kod urzędu skarbowego. |
bankNamestringopcjonalne | nazwa banku. |
nipstringopcjonalne | NIP pracownika (B2B / PIT-11). |
pit2 / ppkResignbooleanopcjonalne | oświadczenie PIT-2 / rezygnacja z PPK. Uwaga: ułamek kwoty zmniejszającej (pit2Mode) i oświadczenia z PIT-2(9) są tylko do odczytu — zmienia je pracownik w portalu albo kadry w teczce. |
ppkJoinedAt / ppkResignedAtYYYY-MM-DDopcjonalne | daty przystąpienia do PPK i rezygnacji z wpłat. Pusty string kasuje datę. Przy onboardingu ustawia je data złożenia oświadczenia, więc zapis zwrotny jest potrzebny głównie dla deklaracji papierowych. |
curl -X PATCH -H "Authorization: Bearer onb_..." -H "Content-Type: application/json" \
-d '{"nfzCode":"07","bankName":"mBank","pit2":true}' \
"https://www.onboardly.work/api/v1/employees/{id}"{ "ok": true, "updated": ["nfzCode", "bankName", "pit2"] }| HTTP | code | Znaczenie |
|---|---|---|
400 | invalid | nieprawidłowa wartość pola. |
400 | empty | brak pól do aktualizacji. |
404 | not_found | brak pracownika w tym tenancie. |
POST/employees/import
Podgląd importu, nic nie zapisuje. Parsuje plik i dopasowuje rekordy po PESEL (fallback: imię + nazwisko + data urodzenia).
Ciało żądania
formatenumwymagane | kedu | symfonia | optima | csv | xlsx. |
contentstringwymagane | treść pliku (maks. 5 MB). |
curl -X POST -H "Authorization: Bearer onb_..." -H "Content-Type: application/json" \
-d '{"format":"kedu","content":"<KEDU>...</KEDU>"}' \
"https://www.onboardly.work/api/v1/employees/import"{
"total": 12, "matched": 10, "new": 2,
"rows": [{
"name": "Jan Kowalski", "pesel": "860…",
"matchedOnboardingId": "uuid", "match": "matched"
}]
}| HTTP | code | Znaczenie |
|---|---|---|
422 | parse_failed | nie udało się sparsować pliku. |
501 | format_not_ready | parser formatu niegotowy. |
POST/employees/import/confirm
Zapis importu, merge zachowawczy (uzupełnia tylko puste lub zmienione pola, nigdy nie zeruje). Upsert wyłącznie do dopasowanych i potwierdzonych identyfikatorów. Nie tworzy nowych pracowników.
Ciało żądania
formatenumwymagane | jak w podglądzie. |
contentstringwymagane | treść pliku. |
confirmIdsuuid[]wymagane | identyfikatory dopasowanych pracowników do zapisania. |
{
"updated": 2, "skipped": 1,
"updatedDetail": [{ "onboardingId": "uuid", "fields": ["personal.pesel", "nfzCode"] }],
"skippedDetail": [{ "reason": "brak zmian", "pesel": "…" }]
}POST/employees/{id}/zus/deregister
Ustawia datę zakończenia zatrudnienia i zwraca plik KEDU ZWUA (wyrejestrowanie z ZUS). Wymaga wcześniejszego zgłoszenia (ZUA/ZZA).
Ciało żądania
endDateYYYY-MM-DDwymagane | data zakończenia zatrudnienia. |
curl -X POST -H "Authorization: Bearer onb_..." -H "Content-Type: application/json" \
-d '{"endDate":"2026-07-31"}' \
"https://www.onboardly.work/api/v1/employees/{id}/zus/deregister" -o zwua.xml| HTTP | code | Znaczenie |
|---|---|---|
409 | not_registered | B2B / dzieło, brak zgłoszenia, nie ma czego wyrejestrować. |
410 | pii_deleted | dane osobowe usunięte (RODO). |
Uwagi implementacyjne
- • Eksport KEDU obsługuje ZUA/ZZA; RUD (dzieło) jest pomijany (składa się przez PUE ZUS); ZWUA wyłącznie przez endpoint wyrejestrowania.
- • Kod tytułu ubezpieczenia dla zleceń bywa
confidence: "low", potwierdź go przed złożeniem w ZUS. - • Import i PATCH nie zakładają pracowników od zera, do tego służy przepływ onboardingu.