API aplikacji mobilnej klienta
Marka w nagłówku, logowanie kodem i hasłem, konto, zgody i e-paragony, tryb gościa, sekcje marki, Idempotency-Key, punkty, nagrody, zamówienia, sklep, bilety i kod klienta w API aplikacji klienta.
Zaktualizowano 2 listopada 2026
Na tej stronie
API aplikacji mobilnej (/api/mobile-app/v1, wersja 1.4) to portal klienta marki bez przeglądarki. Aplikacja
dostaje te same dane i te same decyzje co portal: te same ceny, rabaty, odmowy i komunikaty. Ten przewodnik
opisuje logowanie, konto, tryb gościa, ochronę przed podwójnym wykonaniem, punkty i nagrody, zamówienia, sklep,
bilety i kod klienta. Treści marketingowe i cele przycisków opisuje osobny artykuł:
Treści i cele przycisków. Pola i przykłady operacji są w mobile-app.yaml,
w panelu pod /help/api/mobile-app (gdzie ją znaleźć).
Marka w każdym żądaniu
Każde żądanie poza health i version niesie markę w nagłówku X-Brand-Slug (albo X-Brand-UUID).
Nieznana marka daje 400 BRAND_NOT_FOUND, marka nieaktywna 403 BRAND_INACTIVE, a marka z wyłączonym
portalem 403 PORTAL_DISABLED.
Na starcie pobierz GET brand/config - wygląd marki, kontakt, sposoby logowania i stan sekcji (features).
Sekcje marka włącza tymi samymi przełącznikami co w portalu: rewards, orders, profiles, contractors,
shop (domyślnie wyłączony; wymaga też sprzedaży w kanale aplikacji); loyalty oznacza aktywny program
lojalnościowy. Operacja z wyłączonej sekcji daje 403 FEATURE_DISABLED z polem error.feature. Ukryj wtedy
tę część aplikacji. Dokumenty marki pobierzesz jako HTML z GET brand/documents/{type}: terms (regulamin),
privacy (polityka prywatności), e_receipts (Regulamin e-paragonów).
GET version (bez marki i tokenu) podaje api_version - wersję kontraktu z pliku OpenAPI - i features, czyli
sekcje, które obsługuje serwer. O sekcjach marki decyduje brand/config, nie version. Pola min_app_version,
latest_app_version i update_required są dziś stałe. GET health zwraca stan usługi.
Logowanie
- Wyślij
POST auth/request-codezidentifier(e-mail albo telefon). Odpowiedź jest taka sama dla znanego i nieznanego adresu:channel,destination_masked,code_length,expires_atiresend_available_at. Kanał wyłączony w marce daje400 LOGIN_CHANNEL_DISABLED, a inna odmowa (np. kolejny kod dopiero za kilkadziesiąt sekund, za dużo kodów w ciągu godziny) -400 AUTH_ERRORz powodem wmessage. - Wyślij
POST auth/verify-codezidentifier,codei opcjonalniedevice_name,device_id. Zły albo wygasły kod daje400 CODE_VERIFICATION_FAILED(powód i liczba pozostałych prób wmessage), identyfikator niepasujący do włączonego kanału -400 INVALID_IDENTIFIER. - Gdy
status=authenticated, zapisztokeniuser. - Gdy
status=registration_required, pokaż formularz nowego konta i wyślijPOST auth/registerzverification_token,first_name,last_name, drugą daną kontaktową (phone_numberprzy kodzie z e-maila - wymagany, gdyregistration_requires_phone;emailprzy kodzie SMS-em) i opcjonalniedevice_name,device_id,marketing_consents({ "email": true, "sms": false }). Odpowiedź 201 zestatus=registeredniesie token. Wygasły token rejestracji daje400 INVALID_VERIFICATION_TOKEN, konto albo telefon już zajęte -400 REGISTRATION_FAILED, rejestracja wyłączona w marce -403 FORBIDDEN.
- Sprawdź w
brand/config, czy marka dopuszcza logowanie hasłem. - Wyślij
POST auth/loginzidentifier,passwordi opcjonalniedevice_name,device_id. - Zapisz
tokeniuserz odpowiedzi (ta odpowiedź nie ma polastatus).
Marka bez logowania hasłem daje 400 PASSWORD_LOGIN_DISABLED, złe hasło albo blokada po zbyt wielu próbach -
400 LOGIN_FAILED (blokada prób przychodzi jako 400, nie 429).
Zgody marketingowe przy rejestracji zapisują się tylko z wartością true i tylko wtedy, gdy marka pokazuje zgody
marketingowe.
Token Sanctum jest ważny 90 dni (expires_at) i związany z marką, dla której go wydano. POST auth/refresh
kasuje bieżący token i wydaje nowy tej samej marki; identyfikator urządzenia i klucz kodu offline przechodzą na
nowy token. POST auth/logout kasuje token i unieważnia klucz kodu offline urządzenia. GET auth/me zwraca
zalogowanego klienta. Nagłówek innej marki daje 403 BRAND_MISMATCH, a stary token bez przypisanej marki -
401 REAUTH_REQUIRED (zaloguj ponownie).
Każda operacja zwraca wyłącznie dane właściciela tokenu w marce tokenu. Cudzy zasób albo zasób innej marki
daje 404 NOT_FOUND, a nie 403 - API nie zdradza, że taki zasób istnieje.
Urządzenia
device_id z logowania opisuje urządzenie na liście GET devices i jest na niej pokazywany jako nazwa
urządzenia (bez niego - losowy identyfikator). device_name jest dziś przyjmowane, ale nie jest zapisywane.
device_id to inny identyfikator niż X-Device-Id używany przy pomiarze treści gościa - nie łącz ich.
GET devices- zalogowane urządzenia konta we wszystkich markach (bez wygasłych), od ostatnio używanego;is_currentoznacza bieżące,brand_slug- markę tokenu.DELETE devices/{id}- odłącza urządzenie (token i klucz kodu offline). Bieżącego nie odłączysz (409 CANNOT_REVOKE_CURRENT) - użyjPOST auth/logout. Token cudzy albo spoza aplikacji klienta daje404.POST devices/revoke-all- odłącza pozostałe urządzenia bieżącej marki; urządzenia w aplikacjach innych marek zostają.
Konto, hasło i zgody
GET accountzwraca dane konta,PATCH accountzmienia imię, nazwisko, e-mail i telefon (pominięte pole zostaje). Telefon innego konta daje409 PHONE_EXISTS, zajęty e-mail -422 VALIDATION_ERROR, a próba usunięcia naraz e-maila i telefonu -400 IDENTIFIER_REQUIRED.POST account/passwordustawia pierwsze hasło (password,password_confirmation, co najmniej 8 znaków); konto z hasłem dostaje409 PASSWORD_ALREADY_SET.PUT account/passwordzmienia hasło (current_password,password,password_confirmation); konto bez hasła -409 PASSWORD_NOT_SET, złe aktualne hasło -400 INVALID_PASSWORD, po 5 błędnych próbach na konto (albo 20 na adres IP) w 15 minut -429 TOO_MANY_REQUESTS. Marka bez haseł daje w obu403 FORBIDDEN.- Ustawienie albo zmiana hasła wylogowuje pozostałe urządzenia klienta we wszystkich markach i unieważnia
ich klucze kodu offline (
other_devices_logged_outw odpowiedzi). Bieżący token i jego klucz zostają. Nieudana próba niczego nie wylogowuje. DELETE accountdezaktywuje konto po potwierdzeniu hasłem (password) albo kodem (confirmation_code). Kod zamów przezPOST auth/request-code- przychodzi na e-mail konta, a bez e-maila na telefon. Złe hasło daje400 INVALID_PASSWORD, zły kod -400 INVALID_CONFIRMATION_CODE. Dezaktywacja kasuje tokeny i klucze kodu offline.GET account/consentszwraca zgody marketingowe (email,sms,phone,push),PUT account/consentsje zmienia (pominięty kanał bez zmian). Marka bez zgód marketingowych daje wGETenabled: falsei pustą listę, a wPUT-403 FORBIDDEN.
E-paragony i sprzeciw wobec profilowania
E-paragony. GET account/e-receipts zwraca stan przełącznika „Chcę otrzymywać e-paragony” w marce tokenu.
Włączony przełącznik znaczy, że na kasie w lokalizacji klient dostaje domyślnie e-paragon na e-mail konta
(address_masked); przy zakupach na odległość e-paragon przychodzi zawsze.
- Pobierz
GET account/e-receipts. Gdyavailable=false, marka nie wskazała Regulaminu e-paragonów - ukryj przełącznik. - Gdy
blocker_codenie jest pusty, pokaż powód (EMAIL_REQUIRED- konto bez e-maila,E_RECEIPT_REGULATION_MISSING). - Gdy
regulation.accepted=false, pokaż regulamin (regulation.document_path=brand/documents/e_receipts) i pole akceptacji. - Wyślij
PUT account/e-receiptszenabled: trueiaccept_regulation_version=regulation.version_hash. Wyłączenie (enabled: false) działa od razu.
Brak akceptacji bieżącej wersji regulaminu daje 422 E_RECEIPT_TERMS_NOT_ACCEPTED, konto bez e-maila -
422 EMAIL_REQUIRED. Akceptacja trafia do rejestru akceptacji z kanałem „W aplikacji mobilnej”.
Sprzeciw wobec profilowania. GET account/profiling-objection zwraca stan przełącznika „nie profiluj”
z gotowymi tekstami label i description (te same co w portalu). PUT account/profiling-objection
z objected: true zgłasza sprzeciw, a z false go wycofuje - od razu. Klient ze sprzeciwem nie trafia do grup
odbiorców wybieranych na podstawie zachowania ani do automatów reagujących na zachowanie. Zgody na wiadomości
działają dalej. Ta operacja działa zawsze, niezależnie od ustawień portalu marki.
Profile osób i dane do faktury
account/profiles(sekcjaprofiles) - osoby klienta, np. dzieci: lista ztype_optionsigender_options, dodanie, karta, zmiana (PATCH; pominięte pole bez zmian, pusty napis = brak wartości), usunięcie.account/contractors(sekcjacontractors) - nabywcy do faktury, domyślny pierwszy.buyer_type=company(domyślnie, gdy podano NIP) alboperson. NIP, REGON i KRS system sprowadza do cyfr. NIP jest unikalny wśród nabywców klienta. Pierwszy nabywca zostaje domyślnym,POST account/contractors/{uuid}/defaultzmienia domyślnego, a po usunięciu domyślnego jego miejsce zajmuje następny.
Tryb gościa
Bez tokenu działają: brand/config, brand/documents/{type}, treści (content*, news*) i zdarzenia treści.
Aplikacja może więc pokazać ekran startowy przed logowaniem. Gdy wysyłasz Authorization, obowiązują wszystkie
reguły tokenu - zły token daje błąd, a nie widok gościa. Przebieg trybu gościa i powrót do celu po logowaniu:
Treści i cele przycisków.
Idempotency-Key
Operacje zmieniające stan przyjmują nagłówek Idempotency-Key (8-64 znaki A-Z a-z 0-9 _ - : ., np. UUID
wygenerowany przy otwarciu okna potwierdzenia).
| Nagłówek | Operacje |
|---|---|
| wymagany | POST rewards/{uuid}/redeem, POST shop/checkout, POST shop/orders/{uuid}/pay, POST ticket-entitlements/claim |
| opcjonalny | POST account/profiles, POST account/contractors, POST customer-code/offline-key, POST shop/cart/items, POST shop/orders/{uuid}/return-request |
- To samo żądanie z tym samym kluczem w ciągu 24 godzin dostaje pierwszą odpowiedź jeszcze raz, z nagłówkiem
Idempotent-Replayed: true. Nic nie dzieje się drugi raz. - Ten sam klucz z inną ścieżką albo treścią daje
409 IDEMPOTENCY_KEY_REUSED. Równoległe żądanie z tym samym kluczem daje409 IDEMPOTENCY_IN_PROGRESS(po 10 sekundach oczekiwania). Brak wymaganego nagłówka daje400 IDEMPOTENCY_KEY_REQUIRED, zły format -400 INVALID_IDEMPOTENCY_KEY. - Serwer pamięta odpowiedzi 2xx i 4xx, także odmowę. Po zmianie danych przez klienta wyślij nowy klucz. Odpowiedzi 5xx i 429 nie są pamiętane - ponów z tym samym kluczem.
- Odbiór nagrody i złożenie zamówienia w sklepie są dodatkowo zabezpieczone w bazie: ten sam klucz to zawsze to samo zamówienie, także po upływie 24 godzin.
Limity żądań
Trasy zalogowanego klienta liczą żądania osobno dla konta i dla grupy operacji. Limit grupy i limit operacji obowiązują razem, więc wiąże niższy.
| Operacje | Limit trasy na minutę | Limit w operacji |
|---|---|---|
auth/request-code, auth/login |
30 | limity kodów i haseł jak w portalu (jako 400) |
auth/verify-code |
60 | błędne próby kodu jak w portalu (jako 400) |
auth/register |
10 | - |
auth/logout, auth/refresh, auth/me, account, account/password, account/consents, devices/*, loyalty* |
120 | PUT account/password: 5 błędnych prób na konto i 20 na IP w 15 minut |
account/e-receipts, account/profiling-objection |
odczyt 60, zmiana 20 | - |
customer-code |
60 | - |
customer-code/offline-key |
odczyt 30, wydanie i unieważnienie po 10 | - |
account/profiles/*, account/contractors/* |
60 | - |
rewards/*, orders/*, shop/*, ticket-entitlements/* |
120 | - |
POST rewards/{uuid}/redeem |
20 | 10 prób odbioru na konto |
| dokumenty PDF zamówień i zakupów | 30 | - |
koszyk (shop/cart/items*, DELETE shop/cart) |
60 | - |
| kody rabatowe w koszyku | 20 | 10 prób kodu w 10 minut na konto i na IP, wspólnie z portalem |
POST shop/checkout, POST shop/orders/{uuid}/pay |
10 | starty płatności (SHOP_TOO_MANY_ATTEMPTS) |
POST shop/orders/{uuid}/return-request |
5 | - |
POST ticket-entitlements/claim |
10 | 5 prób na minutę na konto i na IP, wspólnie z portalem |
PATCH ticket-entitlements/{uuid}/visit-date |
20 | - |
online-payments/{uuid} |
60 | refresh=1 pyta bramkę najwyżej raz na minutę |
| treści i aktualności / zdarzenia treści | 120 / 240 | - |
Bez limitu trasy są health, version i brand/*. Cztery trasy logowania (auth/request-code,
auth/verify-code, auth/register, auth/login) liczą żądania na adres IP, każda własnym licznikiem - żądania
auth/login nie zużywają limitu auth/register. Treści i zdarzenia gościa też liczą się na adres IP. Limit
trasy daje 429 HTTP_ERROR, limit prób w operacji - 429 TOO_MANY_REQUESTS z error.retry_after; oba
z nagłówkiem Retry-After. Nieoczekiwany błąd serwera daje 500 SERVER_ERROR w zwykłej kopercie.
Punkty i nagrody
GET loyalty- saldo, poziom i postęp do następnego, punkty wygasające i opis programu. Bez aktywnego programu:enabled: false.GET loyalty/transactions(filtrytype,direction,date_from,date_to) iGET loyalty/transactions/{uuid}- historia punktów (sekcjaloyalty).GET rewards(sekcjarewards) - nagrody dostępne dla poziomu klienta: bez ograniczenia poziomu albo z poziomem nie wyższym niż poziom klienta.can_redeemirefusal_reasonto podpowiedź; wiążąco sprawdza dopiero odbiór. Filtry:search,category,affordable,featured,sort. Kategorie:GET rewards/categories.POST rewards/{uuid}/redeemzIdempotency-Key- punkty schodzą od razu. Nagroda z promocją wydaje jednorazowy kodNAG…(QRASIK1:<kod>), ważnycode_valid_daysdni. Nagroda wymagająca zatwierdzenia dostaje statusawaiting_approval, a kod przychodzi po zatwierdzeniu. 201 to nowy odbiór, 200 - ten sam klucz ponownie. Odmowa daje422 REDEMPTION_REFUSEDz powodem wmessage. Klient nie anuluje odbioru.GET rewards/redemptions(status:active,awaiting,history,all) - moje nagrody i kody. Kod po terminie ma statusexpiredi nie pokazujecoupon_code. Kod zarezerwowany na otwartym rachunku kasy (12 godzin) albo w zamówieniu maavailable: falseireservation- nie pokazuj go jako dostępnego.
Zamówienia i płatności online
GET orders (sekcja orders, filtr status) i GET orders/{uuid} pokazują zamówienia klienta w marce -
tylko do odczytu. Karta zamówienia niesie pozycje z terminem, miejscem, opcjami i stanem realizacji, wpłaty
i zwroty, płatność online w toku, stan rezygnacji (cancellation.state; samej rezygnacji w aplikacji nie ma)
i dokumenty z download_path.
| Dokument | Operacja |
|---|---|
| potwierdzenie zamówienia (nie dla anulowanego) | GET orders/{uuid}/documents/confirmation |
| faktura (tylko gotowa) | GET orders/{uuid}/documents/invoices/{invoiceUuid} |
| regulamin pozycji | ścieżka z documents.regulations[].download_path |
Dokumenty przychodzą jako PDF z nagłówkiem Cache-Control: no-store. Pola e-paragonu w documents
(receipt_delivery, receipt_address_masked, receipt_delivery_message, receipt_delivery_blocker) mówią,
jak klient dostanie paragon za wpłatę. receipt_delivery_blocker oznacza brak e-maila - wpłaty online
system wtedy nie przyjmie.
GET online-payments/{uuid} zwraca stan płatności online: state = processing, paid, late, cancelled
albo failed, z odsyłaczem do zamówienia (order_uuid) albo zakupu w sklepie (shop_order_uuid). Parametr
refresh=1 pyta bramkę płatności, najwyżej raz na minutę.
Sklep
Sklep działa, gdy marka go włączyła i sprzedaje w kanale aplikacji (features.shop). Koszyk jest jeden na
klienta w marce i wspólny z portalem; ceny przelicza się przy każdym pobraniu koszyka.
- Katalog
GET shop/localizations,shop/categories,shop/products, karta produktu. - Koszyk
POST shop/cart/items, kody rabatowePOST shop/cart/coupons. - Złożenie
GET shop/checkout(regulaminy, dokument), potemPOST shop/checkoutzIdempotency-Key. - PayU Otwórz
payment.redirect_url; powrót napayment.continue_url. - Stan i bilety
GET online-payments/{uuid}, potemGET ticket-entitlements.
- Kilka lokalizacji. Marka może sprzedawać w kilku lokalizacjach, każda z własnym katalogiem i cenami.
Zapamiętaj wybór klienta i wysyłaj
localization_uuidw katalogu, na karcie produktu i przy dodaniu do koszyka. Lokalizacja spoza listy daje422 LOCALIZATION_NOT_AVAILABLE. - Jeden operator w koszyku. Koszyk może łączyć produkty kilku lokalizacji, ale jednego operatora. Produkt
innego operatora daje
422 SHOP_OTHER_OPERATOR- klient kończy zakup albo czyści koszyk (DELETE shop/cart). Zamówienie to jedna płatność i jeden dokument w lokalizacji fiskalizującej (fiscal_localization), a bilet działa w lokalizacji pozycji, z której go kupiono. - Koszyk.
PATCH shop/cart/items/{uuid}zmienia ilość (0 usuwa pozycję),DELETE shop/cart/items/{uuid}usuwa pozycję,DELETE shop/cart/coupons/{code}zdejmuje kod. W koszyku działa kod rabatowy albo kod nagrody. - Regulaminy.
POST shop/checkoutwymaga akceptacji bieżących wersji wszystkich regulaminów zGET shop/checkout(version_hash). Brak akceptacji daje422 SHOP_TERMS_NOT_ACCEPTEDz listąrequired_regulations. - Powrót z PayU to adres https ekranu stanu płatności portalu. Aplikacja rozpoznaje go w przeglądarce w aplikacji - linków uniwersalnych nie ma.
- Odmowy mają kod w
error.codei nazwę pola werror.field; kwoty i powody są te same co w portalu. Lista: Kody błędów.
Po zakupie GET shop/orders (filtr status) pokazuje zakupy, a karta zakupu - pozycje, dokument (e-paragon
albo faktura z download_path), bilety, płatność i prośbę o zwrot. Gdy płatność się nie udała, ponów ją przez
POST shop/orders/{uuid}/pay z Idempotency-Key - trwająca próba jest wznawiana z tym samym redirect_url.
Fakturę pobierzesz jako PDF z GET shop/orders/{uuid}/documents/invoices/{invoiceUuid}. Prośbę o zwrot
niewykorzystanych biletów bez dnia wizyty (do 14 dni od zapłaty) złożysz przez
POST shop/orders/{uuid}/return-request z ticket_uuids - zwrot wykonuje obsługa.
Bilety
GET ticket-entitlements (status: active, history, all) zwraca bilety klienta z każdego kanału
sprzedaży: ze sklepu, z kasy, z panelu. Działa także przy wyłączonym sklepie. Karta biletu niesie kod QR do
pokazania przy wejściu (scan_payload, postać ASIW1:<kod>), instrukcję, wejścia i lokalizacje, w których
bilet działa (redeemable_localizations; pusto - każda lokalizacja operatora).
- Dodaj bilet kodem:
POST ticket-entitlements/claimzcode(W…, z myślnikami albo bez, alboASIW1:…) iIdempotency-Key. Tak trafia na konto bilet na okaziciela tej marki, np. z wydruku kasy. Kod nieznany i kod innej marki dają ten sam422 TICKET_NOT_FOUND. Limit prób jest wspólny z portalem. - Zmiana dnia wizyty:
PATCH ticket-entitlements/{uuid}/visit-datezvisit_date, tylko dla aktywnego biletu z ważnością na dzień wizyty, bez wejść, przed dniem wizyty i w zakresievisit_date_change.min-max. Inna cena na nowy dzień daje422 VISIT_DATE_PRICE_DIFFERS- zmianę wykona obsługa. Klient dostaje e-mail.
Kod klienta
Kasa i bramka rozpoznają klienta po kodzie QR ASI1:<customer_uuid>:<kod>; kod ma 8 cyfr i zmienia się co
30 sekund. Online aplikacja pobiera go z GET customer-code. Bez internetu liczy go sama z klucza urządzenia:
- Po zalogowaniu wyślij
POST customer-code/offline-key. Sekret (secret, 64 znaki) jest tylko w tej odpowiedzi - zapisz go w Keychain albo Android Keystore. - Licz kod z sekretu i bieżącego czasu (HMAC-SHA256 jak w RFC 4226, krok 30 s). Algorytm krok po kroku jest w dokumentacji referencyjnej, w sekcji „Kod klienta offline”.
- Sprawdzaj
GET customer-code/offline-key- mówi tylko, czy klucz jest aktywny, bez sekretu.
Ponowne POST unieważnia poprzedni klucz urządzenia. Konto ma najwyżej 10 aktywnych kluczy - wydanie kolejnego
unieważnia najstarszy. Klucz wydaje się tylko tokenowi urządzenia, inaczej 403 FORBIDDEN. Klucz przestaje
działać po DELETE customer-code/offline-key, wylogowaniu, odłączeniu urządzenia, usunięciu konta, wygaśnięciu
tokenu i zmianie hasła na innym urządzeniu. Zmiana hasła w portalu klienta wylogowuje wszystkie urządzenia
aplikacji we wszystkich markach. Urządzenie, które samo zmieniło hasło w aplikacji, zachowuje token i klucz.
Kody zeskanowane przed unieważnieniem nadal przechodzą. Zegar telefonu powinien być synchronizowany
(tolerancja 30 sekund).
Powiązane
- Treści i cele przycisków w aplikacji mobilnej Sloty treści marketingowych, obiekt link (type, params, url), pomiar pokazań paczką content/events, X-Device-Id i tryb gościa - ustalenia z zespołem aplikacji.
- 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ą.