API synchronizacji
Jak serwer lokalny lokalizacji wymienia dane z chmurą - pobieranie (pull) z kursorami i zakresem operatora, nagrobki usunięć, wypychanie (push) z ostrzeżeniami, pliki i operacje wyłącznie online.
Zaktualizowano 2 listopada 2026
Na tej stronie
- Zanim zaczniesz
- Cykl synchronizacji
- Pobieranie (pull)
- Kursory
- Kształt rekordów
- Zakres operatora
- Jednorazowe pełne pobranie po aktualizacji (listopad 2026)
- Usunięcia i nagrobki
- Wypychanie (push)
- Rekordy przyjęte z ostrzeżeniem
- Sprzedaż przetworzona w chmurze
- Promocje i kody ponad limit
- Zwroty z kasy
- Pliki
- Operacje wyłącznie online
- Limity żądań
- Częste problemy
- Powiązane
API synchronizacji (/api/sync/v1) łączy serwer lokalny lokalizacji, czyli kasę, z chmurą. Kasa pobiera z chmury
konfigurację, wysyła do niej to, co powstało na miejscu, i wykonuje operacje, które wymagają połączenia
(np. wpłata na zamówienie). Ten przewodnik opisuje model wymiany. Pola i przykłady każdej operacji są
w dokumentacji referencyjnej sync.yaml (gdzie ją znaleźć).
Zanim zaczniesz
- Token synchronizacji lokalizacji z panelu: Lokalizacje i zasoby › Lokalizacje, karta lokalizacji, zakładka Synchronizacja.
- Trwały identyfikator instalacji serwera lokalnego do nagłówka
X-Sync-Installation. Zasady obu nagłówków: Uwierzytelnianie.
Cykl synchronizacji
Opis tekstowy diagramu
- Kasa → Chmura: GET ping (token, instalacja
- Kasa → Chmura: POST pull z kursorami
- Chmura → Kasa: paczka zmian, nowe kursory, has_more
- Kasa → Chmura: POST pull, dopóki has_more
- Kasa → Chmura: GET files/uuid (bajty nowych plików
- Kasa → Chmura: POST push (dane z kasy
- Chmura → Kasa: wynik dla każdego zasobu
Kasa prowadzi oba kierunki: sama pyta o zmiany i sama wysyła swoje rekordy.
Każda strona jest źródłem prawdy dla innych danych. Chmura rządzi konfiguracją: produktami, cennikami, użytkownikami, stanowiskami, regulaminami. Kasa rządzi tym, co powstało na miejscu: rachunkami, sprzedażą, płatnościami, zmianami kasowymi, ruchami magazynowymi z kasy.
GET ping sprawdza token i powiązanie instalacji. Zwraca podstawowe dane lokalizacji i schema_version chmury.
Pobieranie (pull)
POST pull zwraca przyrostowe zmiany wszystkich zasobów - łącznie najwyżej batch_limit rekordów
(1-1000, domyślnie 500). Zasoby idą w kolejności zależności: rodzic przed dzieckiem. Pełna lista i kolejność
są w opisie operacji pull.
- Wyślij
localization_uuidlokalizacji tokenu,schema_versionkasy icursorsz poprzedniej odpowiedzi. Przy pierwszej synchronizacjicursorsjest puste. - Zapisz rekordy z
data.resources- każdy zapis pouuid, tak żeby powtórzony rekord niczego nie dublował. - Zapisz wszystkie kursory z
data.cursors, także te dla zasobów bez zmian. - Powtarzaj
pull, dopókidata.has_more = true.
Kursory
Kursor to para chwila w UTC i id rekordu, zapisana jako chwila#id, np. 2026-10-25T00:50:00Z#123.
Sam czas nie wystarcza: rekordy z tej samej sekundy na granicy paczki by przepadły. Starszy zapis kursora
(sam czas, czas z przesunięciem +02:00) chmura przyjmuje jako (czas, 0).
Kursor nie mija rekordów młodszych niż 3 sekundy. Transakcja zatwierdzona chwilę po odczycie może nieść
wcześniejszy znacznik czasu i inaczej zginęłaby na zawsze. Dlatego taki rekord przychodzi w paczce, a potem
jeszcze raz. Gdy cała paczka jest świeża, has_more ma wartość false - kursor stoi i nie ma sensu pytać
od razu o tę samą paczkę.
Kształt rekordów
- Numer
idjest usuwany. Klucze obce przychodzą jako uuid, np.operator_id→operator_uuid. - Daty są w czasie ściennym strefy aplikacji (
Europe/Warsaw), bez przesunięcia: kolumna daty jako2026-11-05, data z godziną i znacznikicreated_at,updated_at,deleted_atjako2026-11-05 10:00:00. Wyjątek to zasoby z własnym ładunkiem (marketing_pos_rules,coupon_pool_entries,marketing_contents,ticket_entitlements) - mają daty ISO 8601 z przesunięciem. Kursor zawsze bierz zcursorsodpowiedzi, nigdy z dat rekordów. - Zasoby lokalizacji (kasetki, terminale, magazyny, cenniki) przychodzą tylko dla lokalizacji tokenu.
usersobejmuje tylko aktywnych pracowników lokalizacji. - Wybrane zasoby mają własny zakres, np.
warehouse_itemsbez ilości (stan na kasie to suma ruchów),invoicestylko z metadanymi,regulationsz gotowym HTML-em iversion_hash. - Rekord, którego chmura nie zdoła przygotować do wysłania, jest pomijany, a reszta paczki schodzi normalnie. Wróci przy następnej zmianie tego rekordu.
Zakres operatora
Kasa dostaje dane wyłącznie operatora swojej lokalizacji:
| Zasób | Co przychodzi |
|---|---|
operators |
tylko operator lokalizacji tokenu |
localizations |
lokalizacje tego operatora, zawsze także własna |
files |
pliki tego operatora i pliki wspólne (bez operatora) |
Rekord innego operatora nie znika z odpowiedzi, tylko przychodzi jako { uuid, _op: delete, updated_at, deleted_at }.
Dzięki temu kasa usuwa kopię pobraną przed zawężeniem zakresu. Odsyłacz do takiego rekordu w innych zasobach,
np. brands.owner_operator_uuid albo logo_image_uuid, przychodzi jako null - kasa nie czeka na rodzica, którego
nigdy nie dostanie. Pozostałe zasoby katalogu (marki, produkty, kategorie) nadal przychodzą wszystkie.
Po przeniesieniu lokalizacji do innego operatora rekordy nowego operatora nie zejdą same przy pobraniu przyrostowym. Kasę trzeba wtedy zsynchronizować od nowa (pełne pobranie bez kursorów).
Jednorazowe pełne pobranie po aktualizacji (listopad 2026)
Chmura w wersji z 2026-11-02 zmieniła dwie rzeczy w pobraniu: format dat (wcześniej UTC z Z, teraz czas ścienny)
i zakres operatora. Rekordy pobrane wcześniej mają więc daty przesunięte o strefę, a rekordy innych operatorów
usuną się dopiero przy ponownym wysłaniu. Dlatego kasa po aktualizacji raz kasuje kursory pobrania i pobiera
cały zakres od nowa. Kursory wysyłki zostają - dane czekające na wysłanie nie giną.
Kolejność wdrożenia: najpierw chmura, potem kasy. Kasa zaktualizowana przed chmurą pobrałaby cały zakres jeszcze w starym formacie.
Usunięcia i nagrobki
Rekord usunięty w chmurze przychodzi jako { "uuid": "...", "_op": "delete" }. Źródła są dwa:
| Źródło | Kiedy | Kursor |
|---|---|---|
| miękkie usunięcie rekordu | zwykłe zasoby z deleted_at |
kursor zasobu |
| nagrobek (wpis w dzienniku usunięć) | wiersze kasowane twardo, np. powiązania grup uprawnień, pozycje magazynu, produkty lokalizacji, faktury usunięte trwale, pracownik przeniesiony do innej lokalizacji | osobny kursor <zasób>:deletions |
Nagrobek może dotyczyć jednej lokalizacji. Gdy cennik, magazyn albo pracownik przejdzie do innej lokalizacji, stara
lokalizacja dostaje delete, a nowa - zwykły rekord. Kasa przy delete cennika i magazynu nie kasuje
rekordu, tylko go wyłącza, bo odwołują się do niego rachunki i ruchy. Plik z magazynu prywatnego zawsze
przychodzi jako delete.
Wypychanie (push)
POST push przyjmuje rekordy zasobów, dla których źródłem prawdy jest kasa. Chmura zapisuje każdy rekord
osobno, po uuid: rekord z kasy nadpisuje kopię w chmurze. Ponowne wysłanie tego samego rekordu jest
bezpieczne.
- Wysyłaj rodzica przed dzieckiem, w kolejności z opisu operacji (np.
billsprzedbill_items,salesprzedsale_items). - Klucze obce wysyłaj jako
*_uuid. Lokalizację ustala chmura z tokenu. warehouse_movementsto wyjątek: chmura przyjmuje je jako zmianę stanu, a nie nadpisanie. Powtórzony ruch o tym samymuuidnie zmienia stanu drugi raz.- Jedno żądanie mieści najwyżej 500 rekordów łącznie, inaczej chmura zwraca
400 BATCH_LIMIT_EXCEEDED.
Błędny rekord nie przerywa paczki. Odpowiedź ma dla każdego zasobu liczniki received, upserted, deleted,
skipped, listę errors z uuid i powodem oraz - gdy jest co zgłosić - listę warnings (niżej):
| Powód | Zasoby | Co zrobić |
|---|---|---|
UNRESOLVED_RELATION |
wszystkie | chmura nie zna jeszcze rekordu, do którego się odwołujesz - wyślij ponownie później |
PENDING_DATA |
bill_transfers |
przejęcie rachunku czeka na komplet danych - wyślij ponownie |
CODE_COLLISION |
ticket_entitlements |
kod biletu jest już zajęty w chmurze - nadaj nowy kod i wyślij ten sam uuid ponownie |
MISSING_UUID, UNKNOWN_RESOURCE |
wszystkie | błąd po stronie kasy - popraw rekord |
UNKNOWN_TYPE |
warehouse_movements, sale_returns |
nieznany rodzaj ruchu albo rodzaj, dokument lub sposób wypłaty zwrotu - popraw rekord |
MISSING_TAX_RATE |
sale_items |
pozycja bez stawki VAT - chmura nie podstawia zera, bo 0% to też stawka |
INVALID_RECORD |
kuchnia, bramki, akceptacje regulaminów, liczniki i wydruki kampanii | brak wymaganych pól albo zła data |
INVALID_ACCESS_CODE, MISSING_SALE, DUPLICATE_UNIT |
ticket_entitlements |
kod spoza formatu, bilet bez sprzedaży i jej pozycji, drugi bilet dla tej samej sztuki |
INVALID_STATUS |
ticket_entitlement_redemptions |
nieznany stan realizacji |
DUPLICATE_SALE |
marketing_receipt_prints |
druga wydrukowana kampania do tej samej sprzedaży |
UNKNOWN_REQUEST |
kitchen_requests |
odpowiedź na żądanie kuchni nieznane albo innej lokalizacji |
TOTAL_MISMATCH |
sale_returns |
kwoty zwrotu nie wynikają z pozycji korekty - operator dostaje alarm |
RETURN_EXCEEDS_SALE |
sale_returns |
łączne zwroty przekraczają wartość sprzedaży - operator dostaje alarm |
FOREIGN_RECORD, FOREIGN_WAREHOUSE |
wszystkie, warehouse_movements |
rekord innej lokalizacji albo z panelu - chmura go pomija |
SETTLED_RECEIVABLE |
payments |
należność rozliczona w chmurze - nie da się jej usunąć |
SYNC_ERROR |
wszystkie | wyjątek poza obsługą rekordu; chmura powtarza wtedy paczkę zasobu rekord po rekordzie, a ten rekord dalej zawodzi - wyślij ponownie |
ERROR |
wszystkie | inny błąd przetwarzania; w trybie debugowania chmury zamiast ERROR przychodzi treść błędu |
Ponowienie bez zmian ma sens tylko przy UNRESOLVED_RELATION, PENDING_DATA i SYNC_ERROR (oraz przy
CODE_COLLISION po nadaniu nowego kodu). Pełna lista powodów jest w schemacie PushResourceResult w dokumentacji
referencyjnej.
Rekordy przyjęte z ostrzeżeniem
Rekord z listy warnings jest zapisany i liczy się w upserted. Kasa go nie ponawia - ostrzeżenie mówi tylko,
że chmura czegoś z niego nie wzięła albo założyła alarm. Każdy wpis ma uuid i reason:
| Powód | Zasoby | Co znaczy |
|---|---|---|
CANCELLATION_MISMATCH, CANCELLATION_TOTAL_MISMATCH |
sale_returns |
wypłata zwrotu z rezygnacji nie pasuje do dokumentu rezygnacji; operator dostaje alarm |
SALE_PROCESSED_LOCKED |
sales, sale_items, payments |
sprzedaż jest już przetworzona w chmurze, więc część zmian z kasy pominięto; lista pól jest w fields, opis po polsku w message |
Sprzedaż przetworzona w chmurze
Gdy sprzedaż ma w chmurze stan processed albo completed, chmura wykonała już jej skutki, np. wystawiła fakturę
i wykonała czynności sprzedaży. Od tej chwili o polach, od których te skutki zależą, decyduje chmura. Push pomija zmiany:
- w
sales- statusu, kwot, rodzaju dokumentu, danych stron, klienta, marki, mnożnika punktów, korekty i anulowania; - w
sale_items- towaru, stawki, ilości i kwot; - w
payments- formy, kierunku i kwoty płatności, także_op: delete.
Pozostałe pola rekordu się zapisują, np. numer paragonu i dane e-paragonu. Pominięte pola wracają w warnings
z powodem SALE_PROCESSED_LOCKED i listą fields. Nowe pozycje i płatności przetworzonej sprzedaży chmura
przyjmuje normalnie. Kasa loguje ostrzeżenia i pokazuje je w podsumowaniu paczki, ale ich nie ponawia.
Promocje i kody ponad limit
Kasa bez połączenia liczy limit użyć promocji z ostatniego pobrania, a rezerwacja kodu wygasa po 12 godzinach.
Rozliczenie w coupon_redemptions może więc przekroczyć limit promocji, kodu albo limit kodu na klienta. Chmura
takie rozliczenie przyjmuje - rabat już udzielono - i liczniki rosną ponad limit. Operator dostaje ostrzeżenie
„Promocja lub kod użyte ponad limit” (PROMOTION_USAGE_OVER_LIMIT): najwyżej jedno otwarte na promocję, kod
i parę kod-klient. Odpowiedź nie ma przy tym błędu ani ostrzeżenia, a ponowny push tego samego wpisu niczego
nie dubluje.
Zwroty z kasy
Wpis sale_returns musi mieć kwoty wynikające z pozycji korekty, liczone tak samo jak przy zwrocie w panelu:
total_gross_minor to wartość wszystkich pozycji korekty razem z kaucjami, total_net_minor - pozycje bez
opakowań zwrotnych, a total_deposit_minor - same opakowania zwrotne, wszystko ze znakiem dodatnim. Inne kwoty
dają TOTAL_MISMATCH. Gdy łączne zwroty sprzedaży (bez anulowanych korekt) przekroczyłyby jej wartość brutto
z kaucją, wpis dostaje RETURN_EXCEEDS_SALE. Zwrot na kasie jest już wtedy wypłacony, więc chmura zakłada
alarm dla operatora - ponowienie tego samego wpisu nic nie zmieni.
Pliki
Metadane plików (obrazy produktów, filmy ekranów, wydruki) przychodzą zasobem files w zwykłym pull.
Zawartość pobiera się osobno: GET files/{uuid}.
- Kasa dostaje wyłącznie pliki z magazynu publicznego. Plik prywatny (dokumenty z danymi klientów) daje
403 FILE_FORBIDDEN, a wpullprzychodzi jakodelete. - Duży plik można pobierać z wznowieniem: nagłówek
Range: bytes=OD-daje206z częścią pliku, zakres poza plikiem -416. Odpowiedź zawsze maAccept-Ranges: bytes. - PDF faktury do druku idzie strumieniem
GET invoices/{uuid}/pdfi nie trafia do lokalnej kopii plików.
Kasa raportuje stan swojej kopii plików przez POST files/report, a rekordy, których nie udaje się wysłać ani
zapisać, przez POST failures/report (co 5 minut). Oba raporty widać w panelu w zakładce Synchronizacja
lokalizacji. Gdy najstarszy odrzucony rekord czeka dłużej niż 60 minut, operator dostaje alarm.
Operacje wyłącznie online
Część operacji działa tylko przy połączeniu z chmurą - kasa nie ma ich danych u siebie:
- rozpoznanie klienta po telefonie albo kodzie QR (
customers/resolve), zgoda na e-paragon; - kody rabatowe (
coupons/check,reserve,release) - kody nie schodzą do kasy; - bilety do wykorzystania (
ticket-entitlements/*); - wpłaty, realizacje i listy zamówień (
orders/*), wypłaty zwrotów z rezygnacji (order-cancellations/*); - przejęcie rachunku z chmury (
bills/*,bill-transfers/*), blokada identyfikatora; - stan faktur, dokumenty do druku i należności sprzedaży (
sales/*), zadania wydruku paragonów (fiscalization-jobs); - kontrahenci i użytkownicy lokalizacji.
Większość z nich działa w imieniu pracownika i wymaga nagłówka X-User-Uuid. Wpłaty i wypłaty najpierw
rezerwują kwotę (payment-claims, payouts/.../claim), żeby dwa stanowiska nie przyjęły tych samych pieniędzy.
Limity żądań
Całe API ma limit SYNC_RATE_LIMIT_PER_MINUTE (domyślnie 600 żądań na minutę z jednego adresu IP). Część
operacji online ma dodatkowo limit własny:
| Limit na minutę | Operacje |
|---|---|
| 240 | sales/{uuid}/* (faktura, dokumenty, należności, bilety), fiscalization-jobs, fiscalization-jobs/{uuid}/report |
| 120 | customers/resolve, invoices/{uuid}/pdf, ticket-entitlements/redeem, ticket-entitlements/{code}, listy i szczegóły zamówień, wypłaty zwrotów (lista i podgląd) |
| 60 | customers/{uuid}/e-receipt-consent, orders/counters |
Każdy limit własny ma osobny licznik na adres IP. Żądania sales/* nie zużywają więc limitu orders/counters.
Przekroczenie daje 429 HTTP_ERROR z nagłówkiem Retry-After.
Częste problemy
Powiązane
- Przegląd API i uwierzytelnianie Trzy interfejsy REST StayPark Suite - kto ich używa, jak się uwierzytelnić, jak wygląda koperta odpowiedzi, stronicowanie i limity żądań.
- Kody błędów API Zbiorcza lista kodów error.code trzech interfejsów API z kodem HTTP, znaczeniem i operacjami, które je zwracają.
- Kolejność wdrożenia nowego klienta Lista kontrolna wdrożenia od pustej instalacji do pierwszej sprzedaży - integracje, słowniki, operator i marki, lokalizacje i urządzenia, zasoby, grafik i usługi, użytkownicy i próba generalna.