Changelog

2026-10-07: GET /api/v1/payouts/ready zwraca strony

Niezgodna wstecz zmiana struktury istniejącego endpointu.

  • Odpowiedź to standardowa koperta ze stronami. GET /api/v1/payouts/ready zwraca { items, page, pageSize, total, totalPages } jak wszystkie inne endpointy list, zamiast samej tablicy. Wiersze w items się nie zmieniają.
  • Parametry zapytania page i pageSize. Domyślnie page to 1, a pageSize 20, maksymalnie 100. Pobieraj kolejne strony, dopóki nie zachodzi page >= totalPages, aby odczytać wszystkie wpisy gotowe do wypłaty.
  • Przed wypłatą pobierz wszystkie strony. Wypłacone wpisy znikają z listy, więc pozostałe wpisy przesuwają się na wcześniejsze strony. Najpierw pobierz wszystkie strony, usuń duplikaty według id, a dopiero potem utwórz wypłatę przez POST /api/v1/payouts. Wypłacanie strona po stronie pomija wpisy.
  • Kolejność się nie zmienia. Ostatnio zatwierdzone wpisy są pierwsze.

2026-10-05: vehicleId przy wydatkach innych niż kilometrówka jest ignorowany

Zmiana zachowania w istniejących endpointach; brak zmian w strukturze.

  • Pojazd jest zapisywany tylko przy wydatkach z tytułu kilometrówki. POST /api/v1/travel-entries/{id}/expenses, PUT /api/v1/travel-entries/{id}/expenses/{expenseId} oraz expenses w POST /api/v1/travel-entries zapisują wydatek na paliwo, opłatę drogową, parking, nocleg lub inny wydatek bez pojazdu, a każdy odczyt zwraca dla niego vehicle: null. vehicleId wysłany przy takim wydatku był dotąd zapisywany i zwracany w odpowiedzi.
  • Istniejące wartości są czyszczone. Przy aktualizacji usuwana jest wartość pola vehicle przy istniejących wydatkach innych niż kilometrówka, więc i one zwracają vehicle: null.
  • Pojazd spoza Twojej firmy jest odrzucany przy każdym rodzaju wydatku. Jeśli vehicleId wskazuje pojazd spoza Twojej firmy, API zwraca 400 VALIDATION_FAILED, także przy wydatkach innych niż kilometrówka. Przy tych wydatkach endpointy pojedynczego wydatku zapisywały dotąd vehicleId bez sprawdzenia. Zarchiwizowany samochód służbowy przy wydatku innym niż kilometrówka nie jest już odrzucany przez POST /api/v1/travel-entries; jest tam ignorowany jak każdy inny pojazd.

2026-10-03: tworzenie pojazdu odrzuca nieznany type

Zmiana zachowania w istniejącym endpoincie; brak zmian w strukturze.

  • POST /api/v1/vehicles z wartością type spoza Company i Personal zwraca 400. Wartość liczbowa, taka jak 2 (usunięty typ transportu publicznego), była dotąd zapisywana. Teraz jest odrzucana odpowiedzią 400 VALIDATION_FAILED w errors.Type, tak jak już robił to PUT /api/v1/vehicles/{id}.

2026-10-03: nowe uprawnienie ManageVehicles, zapisy pojazdów go wymagają

Nowa wartość enuma i zmiany uprawnień w istniejących endpointach dotyczących pojazdów; brak zmian w strukturze.

  • ManageVehicles w CompanyPermissions. Nowa flaga atomowa do zarządzania samochodami służbowymi: same pojazdy, ich koszty, dokumenty, terminy i przypomnienia e-mail. Odpowiedzi dotyczące pracowników i komunikaty webhook wymieniają ją jak każdą inną flagę, a każdy CompanyAdmin ma ją teraz przyznaną. ManageCompanyData nadal obejmuje działy, miejsca powstawania kosztów i kursy walut, ale już nie pojazdy.
  • Zapisy pojazdów wymagają ManageVehicles. POST /api/v1/vehicles, PUT /api/v1/vehicles/{id}, DELETE /api/v1/vehicles/{id}, POST /api/v1/vehicles/{id}/archive i POST /api/v1/vehicles/{id}/unarchive zwracają 403 wywołującemu bez tego uprawnienia, niezależnie od pozostałych uprawnień. Każdy pracownik, który przed tym wydaniem miał ManageCompanyData, otrzymał ManageVehicles, więc żaden istniejący użytkownik nie traci dostępu.
  • Odczyt prywatnych pojazdów innych pracowników wymaga ManageVehicles. GET /api/v1/vehicles i GET /api/v1/vehicles/{id} zwracają prywatne pojazdy innych pracowników tylko wywołującemu z uprawnieniem ManageVehicles lub EditAllEntries (dotąd ManageCompanyData).
  • Odsyłaj całą listę. Klient, który odrzuca nieznane nazwy flag, a potem zapisuje uprawnienia pracownika z powrotem, usuwa temu pracownikowi ManageVehicles. Przy odsyłaniu uprawnień zachowuj nieznane nazwy.
  • Zestaw FleetManager na wejściu. Zapis uprawnień akceptuje FleetManager, który serwer zapisuje jako ReadAllEntries i ManageVehicles. Wynik pozostaje listą flag atomowych.

2026-10-03: moc silnika w pojazdach

Dodatkowe pole kontraktu; brak zmian w strukturze.

  • enginePowerKw w pojazdach. Żądania utworzenia i aktualizacji pojazdu akceptują opcjonalną maksymalną moc silnika jako liczbę całkowitą w kilowatach (pole P.2 dowodu rejestracyjnego, od 1 do 2000), a odpowiedź ze szczegółami oraz lista pojazdów ją zwracają. Przy aktualizacji pominięcie pola zachowuje zapisaną wartość, a 0 ją czyści.

2026-10-03: uszkodzone bajty obrazu w załącznikach multipart zwracają 400

Zmiana działania dwóch istniejących endpointów; brak zmian w strukturze.

  • POST /api/v1/travel-entries załącznik multipart z uszkodzonymi bajtami obrazu zwraca 400. Załącznik, którego zadeklarowany typ to obraz, ale którego bajty nie dają się zdekodować jako obraz, wcześniej kończył się 500. Teraz jest odrzucany odpowiedzią 400 VALIDATION_FAILED pod errors.attachments, w tej samej strukturze co każdy inny odrzucony typ załącznika.
  • POST /api/v1/travel-entries/{id}/attachments z tym samym uszkodzonym obrazem również zwraca 400. Endpoint załączników wcześniej w tym przypadku również kończył się 500. Teraz żądanie jest odrzucane odpowiedzią 400 VALIDATION_FAILED pod errors.File, w tej samej strukturze co każdy inny odrzucony typ załącznika.

2026-10-02: usunięcie pojazdu odrzuca pojazd w użyciu, nowy endpoint archiwizacji

Zmiana działania istniejącego endpointu i jeden nowy endpoint; brak zmian w strukturze.

  • DELETE /api/v1/vehicles/{id} zwraca 409 dla pojazdu w użyciu. Pojazd, który występuje w wydatku delegacji lub ma zapisany koszt, nie jest już archiwizowany zamiast usunięcia. Usunięte delegacje oraz usunięte wydatki i koszty nie są liczone, więc pojazd można usunąć po odpięciu go od wydatków i usunięciu jego kosztów. Żądanie jest odrzucane odpowiedzią 409 CONFLICT, a pojazd pozostaje bez zmian. Pojazd bez takich zapisów, zarchiwizowany lub nie, nadal jest usuwany z odpowiedzią 204.
  • POST /api/v1/vehicles/{id}/archive. Archiwizuje pojazd, w użyciu lub nie, i zwraca go z isArchived: true. W ten sposób archiwizuje się pojazd, którego usunięcie jest odrzucane. Archiwizacja zarchiwizowanego pojazdu niczego nie zmienia; POST /api/v1/vehicles/{id}/unarchive ją cofa. Wymaga tego samego uprawnienia ManageCompanyData co usunięcie.

2026-09-28: wycofane nadpisanie poza oknem nocnym

Przestarzałe pola; brak zmian w strukturze.

  • nightOffWindowOverride i nightOverrideReason przy ryczałtach delegacji. Oba pola pozostają w odpowiedzi, jeden element na noc, ale każdy element to teraz odpowiednio false i null. Nadpisanie, które zwracały, zostało wycofane: odpowiedź dotyczącą nocy krótszej niż sześć godzin podaje się przez nightAllocation i żadne nadpisanie nie zmienia kwoty. Przestań je odczytywać; mogą zostać usunięte w jednej z kolejnych wersji.

2026-09-28: dane do wyceny paliwa w wydatkach z tytułu kilometrówki

Dodatkowe pola kontraktu; brak zmian w strukturze.

  • fuelUnitPriceOverride i mileagePetrolGrade przy wydatkach. Żądania utworzenia i aktualizacji wydatku przyjmują oba pola, także dla wydatków zagnieżdżonych w żądaniu utworzenia wpisu podróży, a każda odpowiedź z wydatkiem je zwraca, również wewnątrz wpisu podróży. fuelUnitPriceOverride to cena paliwa za litr (za kWh dla pojazdu elektrycznego) potwierdzona paragonem, w walucie bazowej firmy; w pozycji kilometrówki w CZ lub SK zastępuje średnią cenę ustawową w składniku paliwowym. mileagePetrolGrade (Octane95 lub Octane98) wybiera czeską średnią cenę ustawową benzyny Natural 95 lub Natural 98 dla własnego pojazdu benzynowego bez ceny z paragonu. Każde pole jest zapisywane tylko tam, gdzie wpływa na wycenę pozycji, a wszędzie indziej ma wartość null. Aktualizacja to pełne zastąpienie: pominięcie pól powoduje przeliczenie pozycji po średniej cenie ustawowej, dla benzyny po cenie Naturalu 95, więc aby zachować cenę z paragonu lub wybór benzyny 98, odeślij wartości zwrócone przez wydatek.

2026-09-16 – miejsca powstawania kosztów przy pracownikach

Dodatkowe pole kontraktu; brak zmian w strukturze.

  • costCenterIds przy pracownikach. Odpowiedź ze szczegółami pracownika oraz lista pracowników zwracają miejsca powstawania kosztów, które pracownik może obciążać, a żądania utworzenia i aktualizacji pracownika je przyjmują. Pusta lista oznacza brak ograniczeń – dostępne są wszystkie aktywne miejsca powstawania kosztów firmy. Przy tworzeniu pominięcie pola jest tym samym co przesłanie pustej listy. Przy aktualizacji pominięcie zachowuje zapisaną listę, pusta lista ją czyści, a lista z wartościami zastępuje ją w całości. Każdy identyfikator musi należeć do Twojej firmy, w przeciwnym razie żądanie zostanie odrzucone z kodem 400. Dodanie do wpisu miejsca powstawania kosztów spoza listy nadal jest odrzucane z kodem 400 – właśnie z powodu tego odrzucenia pole istnieje. Ograniczenie dotyczy wyłącznie dodawania: przypisania już obecne we wpisie nie są usuwane wstecznie, gdy miejsce powstawania kosztów zniknie z listy. Przy zapisie obowiązuje granica bezpieczeństwa 100 różnych miejsc powstawania kosztów na pracownika – duplikaty liczą się raz, a schemat publikuje ją jako maxItems w treści obu żądań. Kontroluje rozmiar żądania i nie jest limitem biznesowym: lista może wskazać tylko te miejsca powstawania kosztów, które firma faktycznie ma, więc w praktyce nie będzie ograniczeniem.

2026-09-13 – kwoty diet z planu podróży według czasu urzędowego

Poprawione kwoty w istniejących endpointach diet z planu podróży; brak zmian w strukturze.

  • Wyliczanie diet z planu podróży i from-itinerary. Wartości time przystanków nadal są odczytywane jako UTC, a zwracane przedziały nadal są w UTC. Firmom czeskim i słowackim dni kalendarzowe dzielą się teraz o północy czasu praskiego lub bratysławskiego, a nie o północy UTC. Firmy polskie liczą noce (21:00–07:00), doby i daty rachunków według czasu warszawskiego. To samo żądanie może więc dla tych rynków zwrócić inne godziny i kwoty niż wcześniej.

2026-09-12 – category jest opcjonalne przy aktualizacji pojazdu

Złagodzone pole w istniejącym żądaniu; brak zmian w strukturze.

  • category przy aktualizacji pojazdu. Pole jest teraz opcjonalne. Jego pominięcie zachowuje zapisaną kategorię; należy je przesłać, aby ją zmienić, w tym wartość 0, aby celowo ją wyczyścić. Żądania utworzenia nadal wymagają tego pola.

2026-08-23 – pojemność silnika w pojazdach

Dodatkowe pole kontraktu; brak zmian w strukturze.

  • engineCapacityCc w pojazdach. Żądania utworzenia i aktualizacji pojazdu akceptują opcjonalną pojemność silnika jako liczbę całkowitą w centymetrach sześciennych, a odpowiedź ze szczegółami oraz lista pojazdów ją zwracają. Przy aktualizacji pominięcie pola zachowuje zapisaną wartość, a 0 ją czyści.

2026-08-17 – locked raportuje teraz blokady dokumentów

Skorygowana semantyka wartości w istniejącym polu odpowiedzi; brak zmian w strukturze.

  • locked w odpowiedziach ze szczegółami i listą wpisów wcześniej raportowało tylko przestarzałą blokadę importu sprzed 2023 roku i miało wartość false dla dokumentów zablokowanych w aplikacji, w tym automatycznej blokady nakładanej przy wypłacie. Obecnie pole to przenosi ten sam pochodny stan blokady, który webhook entry.updated raportuje już w isLocked: true, gdy dokument jest zamknięty do zapisu. Należy interpretować to jako “ten wpis nie może być modyfikowany”, a nie jako znacznik konkretnego pochodzenia blokady.

2026-07-13 – identyfikatory miejsc, ręczne kursy wymiany walut, nadpisania diet, kierunek kursu wymiany

Dodatkowe, wstecznie kompatybilne pola kontraktu oraz nowy dyskryminator kursu wymiany walut.

  • Identyfikatory miejsc we wpisach. Żądania utworzenia i aktualizacji akceptują departureLocationPlaceId oraz destinationPlaceId (Google Places place_id), a odpowiedź dla wpisu je zwraca. Należy przesłać zapisane wartości ponownie przy pełnej aktualizacji (pełne zastąpienie), aby zachować tożsamość miejsca podczas edycji tekstu docelowego.
  • Kraj docelowy we wpisach. Żądania akceptują opcjonalny destinationCountryCode (ISO 3166-1 alpha-2). Po jego podaniu jest on zapisywany bezpośrednio, a zapasowe geokodowanie po stronie serwera jest pomijane.
  • Ręczne kursy wymiany walut. Żądania dotyczące wydatków i diet akceptują opcjonalny manualExchangeRate, a żądania dotyczące wpisów akceptują manualAdvanceExchangeRate dla zaliczki. Każdy z nich jest uwzględniany tylko wtedy, gdy dostawca kursów walut nie publikuje kursu dla daty referencyjnej; podanie go w sytuacji, gdy istnieje oficjalny kurs, skutkuje odrzuceniem żądania z błędem EXCHANGE_RATE_UNAVAILABLE.
  • Nadpisania diet. Żądanie dotyczące diety akceptuje overrideAmount oraz overrideReason, a modele odczytu diety je zwracają. Aby zachować istniejące nadpisanie przy pełnej aktualizacji (pełne zastąpienie), należy przesłać te wartości ponownie; ich pominięcie usuwa nadpisanie i przywraca stawkę ustawową wypłaty.
  • Kierunek kursu wymiany walut. GET /api/v1/reference/exchange-rates zawiera teraz dyskryminator direction: ForeignPerBase (HNB / ECB) oznacza, że należy podzielić przez kurs, aby przeliczyć na walutę bazową, a BasePerForeign (CNB) oznacza, że należy pomnożyć.

2026-07-08 – data wypłaty zaliczki, dostawca kursów walut dla CZ/SK

Dwie dodatkowe, wstecznie kompatybilne zmiany.

  • Data wypłaty zaliczki we wpisach. Żądania utworzenia i aktualizacji akceptują teraz opcjonalną advancePaymentDate (yyyy-MM-dd), a odpowiedź dla wpisu ją zwraca. W przypadku pominięcia przy tworzeniu, serwer domyślnie ustawia ją na datę wpisu. Określa ona datę kursu wymiany dla zaliczki oraz, w przypadku organizacji czeskich (tenants), referencyjną datę kursu wymiany dla diety.
  • Dostawca kursów walut dla CZ/SK. GET /api/v1/reference/exchange-rates dokumentuje teraz pełny zestaw dostawców: HNB dla HR, ECB dla SI i SK, CNB dla CZ. Pole source może mieć wartość "CNB", a baseCurrency to natywna waluta bazowa dostawcy (EUR dla HNB/ECB, CZK dla CNB). Zachowanie pozostaje bez zmian; dokumentacja odpowiada teraz rzeczywistemu działaniu systemu.

2026-06-11 – liczba posiłków, pochodzenie kursu wymiany, kod niedostępności kursu walut

Trzy dodatkowe zmiany w interfejsie. Wszystkie są wstecznie kompatybilne: dotychczasowe payloady działają bez zmian, a nowe pola odpowiedzi to dodatki, które można zignorować, dopóki nie będą potrzebne.

  • Liczba posiłków przy tworzeniu/aktualizacji diety. Żądania dotyczące diet akceptują teraz pola breakfastsProvided, lunchesProvided oraz dinnersProvided (liczba poszczególnych posiłków jako liczba całkowita) oraz opcjonalne dzienne pole mealsProvidedAllocation. Stanowią one kanoniczny model zapisu: serwer ponownie oblicza pomniejszenie diety na podstawie tych wartości. Dotychczasowe zagregowane pole mealsProvided z wersji v1 jest nadal akceptowane; w przypadku braku szczegółowych pól posiłków serwer mapuje agregat tak jak dotychczas, dzięki czemu starsi klienci nie odczują zmian. Odpowiedzi nadal zwracają wyliczone mealsProvided (obiad + kolacja, zgodnie z chorwackim Pravilnikiem) obok szczegółowej liczby posiłków, więc ścieżki odczytu pozostają bez zmian.
  • Pochodzenie kursu wymiany w odpowiedziach. Odpowiedzi dla wydatków i diet otrzymują pole exchangeRateSource, a odpowiedź dla wpisu otrzymuje pole advancePaymentExchangeRateSource, rejestrujące pochodzenie zastosowanego kursu: kod dostawcy ("HNB", "ECB") dla oficjalnego kursu, "MANUAL" dla kursu podanego przez użytkownika lub null dla wiersza w tej samej walucie (brak konwersji) bądź historycznego wiersza zapisanego przed wprowadzeniem śledzenia pochodzenia. Pola tylko do odczytu; brak zmian w żądaniach.
  • Nowy kod błędu EXCHANGE_RATE_UNAVAILABLE (HTTP 400). Występuje, gdy dostawca kursów walut organizacji nie publikuje kursu dla danej waluty w żądanym dniu i nie istnieje żaden kurs historyczny, którego można użyć jako zapasowego. Jest to błąd z rodziny walidacji ze standardową strukturą errors kluczowaną polami, co pozwala na precyzyjne odróżnienie przypadku niedostępności kursu i poproszenie o podanie kursu ręcznego. Dodano do katalogu błędów.

2026-05-23 – czasy przebiegu podróży w dietach zapisywane jako rzeczywiste UTC (poprawka)

Czasy diet zapisywane za pomocą POST /api/v1/travel-entries/{id}/per-diems/from-itinerary są teraz przechowywane jako rzeczywiste punkty w czasie UTC. Poprzednio odjazd/przyjazd dla każdego odcinka trasy był zapisywany przy użyciu czasu zegarowego oznaczonego jako UTC bez przesunięcia (odcinek o godzinie 08:00 czasu lokalnego był zapisywany jako 08:00Z zamiast 07:00Z/06:00Z), przez co wiersze wygenerowane na podstawie przebiegu podróży różniły się od wierszy dodanych ręcznie o 1-2 godziny i wyświetlały się z opóźnieniem. Schematy żądań i odpowiedzi pozostają bez zmian, ale wartości departureTime/arrivalTime zwracane przez ten endpoint (oraz zwracane przy późniejszych odczytach tych samych wierszy) odzwierciedlają teraz poprawiony punkt w czasie UTC. Bezstanowy podgląd kalkulatora nadal zwraca czasy zegarowe. Kwoty diet pozostają bez zmian.

2026-05-23 – kalkulator diet + zapis całej podróży

Interfejs obliczania diet został skonsolidowany i zyskał możliwość zapisu całej podróży, analogicznie do aplikacji webowej locco.

  • Nowy POST /api/v1/utilities/per-diems/calculate - bezstanowy kalkulator. Wyślij przebieg podróży (jeden odcinek = przebieg podróży z 2 punktami trasy) i otrzymaj obliczone wiersze z zastosowaną redystrybucją na poziomie przebiegu podróży. Brak kontekstu wpisu, nic nie jest zapisywane.
  • Nowy POST /api/v1/travel-entries/{id}/per-diems/from-itinerary - odbuduj diety wpisu na podstawie przebiegu podróży w jednym wywołaniu. Serwer ponownie uruchamia własny silnik dla punktów trasy (przesłane kwoty nigdy nie są traktowane jako zaufane), stosuje posiłki dla poszczególnych odcinków kluczowane przez (countryCode, departureTime) (pary zwracane przez kalkulator), a następnie atomowo zastępuje diety wpisu. Pozwala to na odtworzenie redystrybucji na poziomie przebiegu podróży oraz podwyższeń stawek krajowych, czego nie da się osiągnąć, przesyłając wiersze pojedynczo.
  • Usunięto POST /api/v1/travel-entries/{id}/per-diems/compute oraz POST /api/v1/per-diems/calculate-simple. Oba zostały zastąpione przez utilities/per-diems/calculate - pojedynczy odcinek to po prostu przebieg podróży z 2 punktami trasy. Należy przenieść integracje na nowy kalkulator.

2026-05-22 – obsługa rynku słoweńskiego

Wstępna obsługa słoweńskich organizacji (tenants). Zaktualizuj swojego klienta na podstawie odświeżonej migawki OpenAPI pod adresem /api-reference/:

  • Słoweńskie gminy - nowy GET /api/v1/reference/slovenian-municipalities zwraca listę gmin SURS dla rozwijanych list miejsc na słoweńskich formularzach podróży. Możliwość wywołania z poziomu dowolnej organizacji.
  • Rezydencja podatkowa pracownika - rekordy pracowników otrzymują pole residency (Resident / NonResident) na potrzeby raportowania słoweńskich diet i podatku od wynagrodzeń. Domyślna wartość to Resident.

2026-04-28 – uruchomienie wersji v1.0

Pierwsze stabilne wydanie partnerskiego API locco. Każdy obszar jest połączony, otypowany i objęty migawką OpenAPI pod adresem /api-reference/:

  • Wpisy - pełny CRUD oraz przejścia przepływu pracy (wyślij, zatwierdź, odrzuć, anuluj, zwróć do poprawy), z atomowym tworzeniem, które akceptuje zagnieżdżone wydatki, diety, alokacje miejsc powstawania kosztów oraz załączniki w jednym wywołaniu.
  • Załączniki - bezpośrednie przesyłanie do wpisu za pomocą POST /api/v1/travel-entries/{id}/attachments. Format multipart, do 5 plików na żądanie.
  • Webhooki - rejestracja subskrypcji z jednorazowym ujawnieniem klucza podpisywania (signing secret), historia dostarczeń dla każdej subskrypcji oraz endpoint ponawiania prób dla nieudanych lub wyczerpanych dostarczeń.
  • Wypłaty - lista wpisów gotowych do wypłaty, tworzenie paczek, lista historii, usuwanie paczki.
  • Pracownicy, Działy, Pojazdy, Miejsca powstawania kosztów - CRUD + lista, z wartościami pól niestandardowych dla pracowników.
  • Dane referencyjne - chorwackie gminy, kursy wymiany walut HNB, tabela stawek diet, listy dozwolonych krajów i walut włączone dla firmy.
  • Firma - odczyt profilu.

Wartości enum dla Status, TravelType, PayoutMethod i pozostałych są udokumentowane w schemacie OpenAPI jako listy dozwolonych wartości enum: [...], dzięki czemu wygenerowani klienci opierają się na kanonicznych ciągach znaków ("PendingApproval", "BankTransfer" itp.), a nie na niejasnych wartościach.