API dla partnerów organizacji (klucze API)
HTTP-API, którym zewnętrzny partner właściciela klubu czyta dane jego organizacji: kasa, system lojalnościowy, strona z rezerwacją online, własna witryna sieci. Klucz wydaje właściciel w panelu (sekcja Klucze API), on też decyduje, co klucz może i do kiedy działa.
To nie protokół stanowiska do gry (ten działa po WebSocket) ani API aplikacji mobilnej klienta — to osobne kontrakty. Nie ma tu sesji klienta ani logowania człowieka: żądanie przychodzi od programu partnera i jest rozpoznawane po kluczu.
Postman. Do importu gotowe są dwa pliki:
| Plik | Co to |
|---|---|
| spike_api_postman_collection.json | Kolekcja: 51 żądań w 8 folderach — wszystkie akcje plus sprawdzenia odmów, z gotowymi testami |
| spike_api_postman_environment.json | Środowisko: base_url i api_key (typ *secret*), pozostałe zmienne wypełniają się same |
Zaimportuj oba (*Import* → przeciągnij pliki), wybierz środowisko w prawym górnym rogu i wpisz w nim api_key. Klucz trzymaj w środowisku, a nie w kolekcji: kolekcje się przekazuje i eksportuje, środowisko zostaje u ciebie.
Dalej całość można uruchomić Runnerem — listy pamiętają id pierwszego elementu, więc gs.get, product.get, session.get, order.get i chat.messages działają bez ręcznego wpisywania. Foldery 1–7 oczekują odpowiedzi 200, czyli liczą na klucz z pełnym dostępem; klucz z ograniczonymi uprawnieniami zgodnie z zasadami odpowiada 403 forbidden_action na niedostępne akcje — co dokładnie jest dla niego dostępne, mówi meta.actions.
---
1. Skąd wziąć klucz
Klucz wydaje właściciel klubu w swoim panelu: sekcja Klucze API → „Utwórz”. Tam też wybiera, co kluczowi wolno i do kiedy działa, dlatego o klucz proś razem z listą potrzebnych ci akcji — właściciel zaznaczy właśnie je.
Co powiedzieć właścicielowi:
- jakie dane są potrzebne — po akcjach z §4 (np.
gs.status,product.list); w karcie klucza zaznacza uprawnienia o tych samych nazwach, które podpisane są przy akcji; - na jaki okres — klucz można wydać na dzień, tydzień, miesiąc, rok, do konkretnej daty albo bez terminu;
- czy potrzebny jest dziennik żądań — gdy integracja jest jeszcze dopracowywana, właściciel włącza pole wyboru i widzi twoje żądania (patrz §8).
Gotowy klucz przekazuje ci jak hasło. Na liście kluczy widać go w masce (pierwsze i ostatnie cztery znaki), w całości — tylko w karcie, skąd się go kopiuje.
Więcej od strony właściciela jest w podręczniku użytkownika SpikePC, w sekcji Klucze API.
Klucz nie jest przypisany do klubu: działa na całą organizację, a konkretny klub wskazujesz parametrem club_id.
---
2. Transport
POST https://spikepc.com/spike_api/
X-Api-Key: 7b2d4f9a1c3e5d7f9b0a2c4e6f8a0b1d
Content-Type: application/json
{ "action": "gs.list", "v": 1, "data": { "club_id": 3, "limit": 50, "offset": 0 } }
Adres podawaj z ukośnikiem na końcu (/spike_api/). Bez niego serwer odpowiada przekierowaniem na adres z ukośnikiem, a treść żądania POST przy przekierowaniu ginie — żądanie przyjdzie puste i dostanie 400 bad_request.
Ten sam handler jest dostępny też pod wewnętrznym adresem ?loc=spike_user_api_request — on nadal działa, ale w integracjach używaj /spike_api/.
| Zasada | Dlaczego tak |
|---|---|
Tylko POST, treść — jeden obiekt JSON | Parametry w query nie dochodzą do handlera: panel przepuszcza z URL wyłącznie białą listę (id, loc, page, token, hash) |
Klucz — nagłówek X-Api-Key | Sprawdzone: proxy go przepuszcza. Authorization: Bearer nie dochodzi do PHP wcale, więc nie można go użyć |
Zapas — pole api_key w treści | Dla klientów, którzy nie potrafią nagłówków. To pole nigdy nie trafia do dziennika |
| Klucz nie jest przekazywany w URL | Adresy zostają w logach proxy i serwera |
Parametry biznesowe — wewnątrz data | Pola systemowe (action, v, api_key) leżą obok, nie mieszając się z twoimi |
org_id w żądaniu nie jest przyjmowany | Organizacja bierze się z klucza. Inaczej partner jednego klubu czytałby obce dane |
club_id jest przyjmowany | Ale sprawdzany względem klubów organizacji klucza → inaczej club_foreign (403) |
v to wersja kontraktu, opcjonalna (obecnie 1).
---
3. Odpowiedź
Ta sama koperta, co u stanowiska i aplikacji mobilnej; code powtarza się w statusie HTTP:
{
"code": 200,
"error_key": "",
"error_text": "",
"action": "gs.list",
"data": { "items": [ … ], "total": 38, "limit": 50, "offset": 0 }
}
error_keyto stabilny kod maszynowy — na nim się opieraj.error_textto wyjaśnienie po angielsku, jego brzmienie może się zmieniać.datato zawsze obiekt (przy błędzie{}): do obiektu można dodać pole, nie psując parsera. Listy toitems+total+limit/offset, pojedynczy rekord toitem.actionto echo zamówionej akcji, przydatne, gdy po twojej stronie działa kolejka asynchroniczna.
Kody i klucze błędów
| HTTP | error_key | Kiedy |
|---|---|---|
| 400 | bad_request | Treść nie jest obiektem JSON albo brak pola action |
| 401 | empty_key | Klucz nie został przysłany |
| 401 | unknown_key | Klucza nie znaleziono |
| 401 | key_expired | Termin ważności klucza minął |
| 403 | forbidden_action | Klucz nie ma prawa do tej akcji |
| 403 | club_foreign | club_id należy do innej organizacji |
| 404 | unknown_action | Nie ma takiej akcji |
| 404 | not_found | Obiektu nie znaleziono (klub, stanowisko) |
| 422 | bad_params | Brakuje wymaganego parametru |
| 429 | rate_limited | Przekroczona częstotliwość (patrz §7), w nagłówku Retry-After |
| 500 | org_unavailable | Baza organizacji jest niedostępna |
| 500 | query_failed | Zapytanie się nie udało. Nie myl tego z pustą listą: przy awarii przychodzi błąd, a nie items: [] |
---
4. Akcje
Co zrobić, mówi pole action. Punkt wejścia jest jeden, więc dodanie metody nie zmienia adresu i nie wymaga zmian po twojej stronie. Uprawnienie każdej akcji to nazwa funkcji w panelu: jest podpisana w karcie klucza obok pozycji uprawnień i właśnie ją właściciel zaznacza, żeby otworzyć partnerowi akcję.
Akcje w tej sekcji tylko czytają. Te, które zmieniają dane, są w §5.
Serwisowe (bez uprawnienia)
action | Przeznaczenie |
|---|---|
ping | Klucz przyjęty: organizacja, nazwa klucza, tryb dostępu, termin, czas serwera |
meta.actions | Co jest dostępne temu kluczowi — lista akcji i ich uprawnień |
Klub i sala
action | Uprawnienie | Parametry | Przeznaczenie |
|---|---|---|---|
club.list | spike_clubs_list | — | Kluby: nazwa, adres, telefony, współrzędne |
club.map | spike_club_maps_list | club_id* | Plan sali (obiekt albo null, jeśli mapy nie narysowano) |
gs_group.list | spike_gs_groups_list | club_id | Grupy stanowisk — na nich opierają się taryfy i abonamenty |
gs.list | spike_gs_list | club_id, group_id, limit, offset | Stanowiska: numer, typ, IP, MAC, grupa, tryb, częstotliwość ekranu, wersja oprogramowania klienta (app_version — zgłasza ją samo stanowisko przy połączeniu; puste = nie zgłaszało) |
gs.get | spike_gs_list | id albo mac | Jedno stanowisko |
gs.status | spike_club_maps_list | club_id | Stan sali na żywo (patrz niżej) |
snap.list | spike_gs_snaps_list | club_id* | Złote obrazy klubu: który jest aktywny, kiedy zrobiony |
Katalog
action | Uprawnienie | Parametry | Przeznaczenie |
|---|---|---|---|
game.list | spike_games_list | club_id, genre_id, status, search, locale, limit, offset | Gry klubu: sposób uruchomienia, ocena, gatunki |
game_genre.list | spike_game_genres_list | locale | Gatunki organizacji (jedna lista na całą sieć) |
banner.list | spike_banners_list | club_id | Banery: obszar na ekranie stanowiska, okres, okno czasowe |
category.list | spike_market_category_list | locale | Grupy towarów |
product.list | spike_market_products_list | club_id*, cat_id, status, search, locale, limit, offset | Towary z ceną klubu i stanem magazynu |
product.get | spike_market_products_list | club_id*, id albo code albo barcode | Jeden towar (wygodnie ze skanera) |
stock.list | spike_stock_list | club_id, type, product_id, date_from, date_to, limit, offset | Ruch magazynu: przyjęcie, wydanie, sprzedaż, zwrot, korekta |
search w towarach szuka po nazwie, kodzie artykułu i kodzie kreskowym. locale podmienia name i description tłumaczeniem tej lokalizacji; nie ma tłumaczenia — zostaje wartość bazowa, więc odpowiedź nigdy nie przychodzi pusta.
club_id przy towarach jest wymagany: towar ma własną cenę w każdym klubie, a bez klubu trzeba by zwracać „cenę w ogóle” — partner sprzedawałby po obcej. Jeśli w tym klubie ceny nie ma, przychodzi price: null i available: false — tutaj towar nie jest sprzedawany.
Godziny dostępności towaru przychodzą w polu hours — po jednym wierszu na dzień tygodnia: [{day, from_sec, to_sec}], gdzie day to 0=pon … 6=niedz, a from_sec/to_sec — sekundy od północy. Pusta tablica oznacza sprzedaż całodobową. to_sec to ostatnia wliczona sekunda (okno „18:00–22:00” przychodzi jako 64800…79259), a to_sec mniejszy od from_sec to okno przez północ: zaznaczony dzień wyznacza początek, koniec wypada następnej doby. Obok przychodzi gotowa odpowiedź hours_open (true/false — czy towar jest w sprzedaży właśnie teraz): samodzielne wyliczenie wymagałoby naszej konwencji granic i naszej strefy czasowej. Na pole available harmonogram nie wpływa — nadal dotyczy ceny w klubie i aktywności towaru.
Taryfy i abonamenty
action | Uprawnienie | Parametry | Przeznaczenie | |
|---|---|---|---|---|
tariff.list | spike_tariffs_list | club_id | Taryfy: cena za godzinę, kolor, czy działają rabaty, status (0 = taryfa wycofana z obiegu: nie przypisuje się jej do nowych godzin harmonogramu, ale godziny, w których już stoi, nadal liczą się po jej cenie) | |
tariff.schedule | spike_tariffs_schedule | club_id* | Siatka tygodniowa: dzień+czas → grupa stanowisk → taryfa | |
ticket.list | spike_tickets_list | club_id | Abonamenty: czas, cena, auto_apply, status (0 = wyłączony przez klub, nie jest w sprzedaży) | |
ticket.schedule | spike_tickets_schedule | club_id*, kind (sale \ | valid) | Kiedy abonament jest sprzedawany albo kiedy można go wykorzystywać |
discount.list | spike_discounts_list | status | Lista rabatów okolicznościowych | |
promocode.list | spike_promocodes_list | status, search, club_id, limit, offset | Kody promocyjne: zdarzenie, kwota, okno, pozostałe aktywacje | |
payment_method.list | spike_payment_methods_list | type, status, club_id | Kasy organizacji z wyliczanym saldem |
Granice przedziałów harmonogramu są włączne, przy czym time_end to ostatnia włączona sekunda bloku (godzina 13 → 50399 = 13:59:59). W odpowiedzi obok sekund idą gotowe from/to, żebyś nie powtarzał tej konwencji u siebie i nie tracił ostatniej godziny.
Dwie siatki abonamentu to różne rzeczy: kind=sale odpowiada „kiedy można go kupić”, kind=valid — „kiedy kupione można wykorzystać”. Pusta siatka ważności znaczy „działa zawsze”.
Przy kasie bonusowej balance przychodzi jako null, a nie zero: bonusy to nie realne pieniądze w kasie.
Klienci i pieniądze
action | Uprawnienie | Parametry | Przeznaczenie | ||
|---|---|---|---|---|---|
client.list | spike_clients_list | search, is_banned, limit, offset | Klienci organizacji | ||
client.get | spike_clients_list | id albo email albo phone | Karta klienta plus jego abonamenty | ||
client.packs | spike_clients_list | id \ | email \ | phone | Tylko abonamenty |
transaction.list | spike_transactions_list | client_id, club_id, type, date_from, date_to, limit, offset | Rejestr operacji | ||
shift.list | spike_cash_shifts_list | club_id, status, date_from, date_to, limit, offset | Zmiany kasowe z podsumowaniem i różnicą | ||
postpay.list | spike_cashdesk_list | club_id, status, date_from, date_to, limit, offset | Płatności po grze |
search w klientach szuka po mailu, telefonie, loginie, imieniu i nazwisku. Telefon w client.get jest szukany we wszystkich formatach, w jakich mógł zostać zapisany (380…, +380…, 0…, 38…) — import i rejestracja zapisywały go inaczej.
Serwisowe wpisy gościnne stanowisk (gościnna płatność po grze) nie trafiają do listy klientów. Notatki operatora i awatar nie są wydawane.
Pozostały czas abonamentu liczy się po zegarze ściennym: aktywowany abonament wypala się od momentu aktywacji, więc remaining_seconds nie pokaże czasu, który już się wypalił, gdy klienta nie było.
Sesje, zamówienia, rezerwacje, czat
action | Uprawnienie | Parametry | Przeznaczenie |
|---|---|---|---|
session.list | spike_gs_sessions_list | club_id, gs_id, client_id, status, date_from, date_to, limit, offset | Historia sesji |
session.get | spike_gs_sessions_list | id* | Sesja plus dziennik zdarzeń (start, uruchomienia gier, zamówienia, koniec) |
order.list | spike_orders_list | club_id, status, gs_id, client_id, date_from, date_to, limit, offset | Zamówienia sklepu z pozycjami |
order.get | spike_orders_list | id* | Jedno zamówienie; pozycja combo nosi wybór klienta w options |
reserve.list | spike_gs_reserve_list | club_id, gs_id, client_id, date_from, date_to, limit, offset | Rezerwacje stanowisk |
chat.list | spike_chats_list | club_id, date_from, date_to, limit, offset | Rozmowy (jedna rozmowa = jedna sesja) |
chat.messages | spike_chats_list | session_id*, limit, offset | Wiadomości rozmowy |
Aktywna sesja wciąż zbiera swój wynik, dlatego w odpowiedzi jest active: true — nie bierz sumy pośredniej za ostateczną.
Zamówienie nie obciąża salda klienta — płaci się je na miejscu personelowi, więc total_amount to kwota do zapłaty, a nie zrealizowana płatność.
Okres przy rezerwacjach liczy się po przecięciu, a nie po początku: rezerwacja, która zaczęła się wczoraj i trwa teraz, trafia do wyboru za dziś.
Przy rozmowach total to liczba rozmów, a nie wiadomości.
Organizacja
action | Uprawnienie | Parametry | Przeznaczenie |
|---|---|---|---|
org.info | spike_org_config_list | — | Strefa czasowa, zasady magazynu, limity abonamentów i płatności po grze, kluby przyjmujące zamówienia |
locale.list | spike_locale_list | status | Języki katalogu i który jest domyślny |
staff.list | spike_staff_list | — | Pracownicy i ich kluby |
event.list | spike_events_list | search, date_from, date_to, limit, offset | Dziennik zmian w panelu |
analytics.daily | spike_analytics_list | club_id, date_from, date_to | Dzienne wyliczenia: obłożenie i przychód |
org.info nie wydaje danych dostępowych: ani do bazy, ani SMTP, ani tokenów Telegrama, ani gs_token stanowisk, ani salda organizacji. Uprawnienie „podgląd ustawień” dostaje pracownik wewnątrz panelu, a tutaj odpowiedź idzie do strony trzeciej.
event.list nie wydaje zrzutów „przed/po”: trafiają do nich całe pola, w tym hasła Windows klubu. Zostaje kto, kiedy, w jakiej sekcji i nad czym.
analytics.daily wylicza nocne zadanie w strefie czasowej organizacji, dlatego bieżący dzień zwykle jest pusty — to nie utrata danych: za dziś bierz żywe gs.status, transaction.list, order.list. Okres domyślny to ostatnie 30 dni.
Strony i filtry
limit— 50 domyślnie, maksymalnie 200;offsetliczy się od zera. Oba wracają w odpowiedzi.date_from/date_to—YYYY-MM-DDalboYYYY-MM-DD HH:MM:SS. Data bez czasu wdate_toznaczy całą dobę, inaczej2026-10-01odciąłby cały dzień, zostawiając północ.- Wartości filtrów wyliczeniowych (
status,type,kind) są sprawdzane z białą listą: obca wartość daje422 bad_params, a nie cicho zignorowany filtr.
Przykład: stan sali na żywo
→ { "action": "gs.status", "data": { "club_id": 3 } }
← { "code": 200, …, "data": { "items": [
{ "gs_id": 587, "club_id": 3, "number": "2",
"online": true, "mode": "active",
"busy": true, "client_id": 1402, "started_at": "2026-10-01 14:05:11",
"time_left": { "seconds": 4260, "text": "1:11", "unlimited": false } }
], "total": 38, "server_time": "2026-10-01 16:10:59" } }
| Pole | Znaczenie |
|---|---|
online | Stanowisko ma otwarte połączenie z serwisem. To o łączności, nie o trybie |
mode | active / service (obsługa) / admin (administrowanie) / disabled (wycofane z wynajmu) |
busy | Przy stanowisku gra klient. Stanowisko może być busy także przy utraconej łączności — sesję zamknie uzgadnianie |
time_left | Szacunek pozostałego czasu: darmowe minuty klubu + stosowalne abonamenty + pieniądze po obowiązującej stawce. unlimited — stawki nie ma albo jest zerowa. null — przy stanowisku nikogo nie ma |
time_left i online liczy ten sam kod, co mapa klubu, dlatego partner i operator widzą tę samą liczbę.
---
5. Zapis
Te akcje zmieniają dane klubu: przyjmują pieniądze, sprzedają abonament, przesuwają zamówienie, tworzą rezerwację, przyznają rabat i kredyt, wysyłają polecenie do stanowiska. Ich uprawnienia są osobne — właściciel zaznacza je w karcie klucza niezależnie od podglądu, więc „obejrzeć" i „zmienić" nigdy nie idą w jednym polu wyboru.
Zasady są te same, co u operatora w panelu: bez otwartej zmiany kasowej pieniądze nie są przyjmowane, abonament ponad limit organizacji nie jest sprzedawany, rezerwacja w zamkniętych godzinach nie powstaje. To nie „podobne sprawdzenia" — obie strony wywołują ten sam kod, więc partner nie może tego, czego nie wolno operatorowi.
action | Uprawnienie | Parametry | Co robi |
|---|---|---|---|
client.topup | spike_clients_topup | client_id*, amount*, payment_method_id*, comment, club_id | Doładowuje saldo klienta przez kasę |
client.sell_ticket | spike_clients_sell_ticket | client_id*, ticket_id* | Sprzedaje abonament, obciążając saldo klienta |
transaction.refund | spike_transactions_refund | tx_id* | Zwraca doładowanie |
order.set_status | spike_orders_list | order_id*, status* | Przesuwa zamówienie: new→processing→done, albo cancelled |
reserve.create | spike_gs_reserve_add | club_id*, gs_id*, date_start*, date_end*, client_id, description | Tworzy rezerwację stanowiska |
reserve.cancel | spike_gs_reserve_del | reserve_id* | Anuluje rezerwację |
discount.grant | spike_club_maps_gs_discount | gs_id*, discount_id* | Przyznaje rabat okolicznościowy (0 zdejmuje) |
credit.set | spike_club_maps_gs_credit | gs_id*, amount* | Ustawia kredyt na sesję (0 zdejmuje) |
postpay.enable | spike_cashdesk_postpay_enable | gs_id*, limit, guest, comment | Włącza płatność po grze na stanowisku |
postpay.limit | spike_cashdesk_postpay_enable | postpay_id*, limit | Zmienia limit płatności po grze |
postpay.stop | spike_cashdesk_postpay_enable | postpay_id* | Kończy płatność po grze (stanowisko dostaje session_end) |
postpay.cancel | spike_cashdesk_postpay_enable | postpay_id* | Zdejmuje płatność po grze, która czeka na klienta |
postpay.pay | spike_cashdesk_postpay_enable | postpay_id*, payment_method_id*, comment | Przyjmuje zapłatę za płatność po grze do kasy |
gs.command | spike_club_maps_gs_command | gs_id*, action*, data | Polecenie dla stanowiska: message, poweroff, poweron, session_end |
Pieniądze
Kasa jest wymagana (payment_method_id), listę podaje payment_method.list. Przy kasie bonusowej wymagany jest comment — bonusu bez wyjaśnienia nie przyjmujemy, inaczej nie ma czym odpowiedzieć na pytanie „za co naliczono". Jeśli kasa jest wspólna dla organizacji, a klubów jest kilka, potrzebny jest club_id: bez niego operacja zginęłaby dla raportów klubu.
Potrzebna jest otwarta zmiana kasowa w tym klubie, inaczej 409 no_shift. Zmianę otwiera kasjer w panelu i przez API się tego nie obejdzie: pieniądze muszą należeć do czyjejś zmiany.
Zwrot (transaction.refund) jest możliwy tylko dla doładowania i tylko raz; saldo może zejść na minus — pieniądze oddano z kasy, więc schodzą z salda w całości.
Sprzedaż abonamentu obciąża saldo klienta z uwzględnieniem jego stałego rabatu (jeśli abonament rabaty dopuszcza). Abonament trafia do zapasu: zegar startuje przy aktywacji na stanowisku, a nie przy sprzedaży, inaczej kupiony z wyprzedzeniem wypaliłby się przed przyjściem klienta.
Sala
Rabat: jeśli przy stanowisku jest klient, procent idzie zrzutem do jego sesji (lista może być później zmieniona, a człowiek dogrywa na tym, co obiecano); jeśli stanowisko jest wolne, zgłoszenie czeka na tego, kto usiądzie, 30 minut.
Kredyt to dozwolona głębokość minusa do końca sesji, a nie wpłata: klient dogrywa na kredyt i spłaca go zwykłym doładowaniem. Potrzebna jest aktywna sesja klienta.
Polecenia: dozwolone są tylko message, poweroff, poweron i session_end. Tryby stanowiska, złote obrazy i reszta zarządzania salą zostają przy operatorze — to sprawa klubu, nie systemu zewnętrznego. W odpowiedzi jest delivery: done/delivered — stanowisko przyjęło, offline — nie jest połączone, timeout — nie odpowiedziało w czasie.
Klucze odmowy przy zapisie
error_key | Kiedy |
|---|---|
bad_amount | Kwota nie większa od zera (albo kredyt ujemny) |
invalid_payment_method | Kasy nie ma albo jest wyłączona |
comment_required | Kasa bonusowa bez komentarza |
club_required | Kasa jest wspólna, a club_id nie przekazano |
no_shift | W klubie nie ma otwartej zmiany kasowej |
client_not_found, ticket_not_found, order_not_found, reserve_not_found, tx_not_found | Obiekt nie istnieje |
no_balance | Na saldzie klienta brakuje pieniędzy na abonament |
ticket_disabled | Abonament jest wyłączony przez klub i nie jest w sprzedaży |
pack_single_only, pack_limit_reached | Limity organizacji na abonamenty |
not_a_topup, already_refunded | Zwrot: to nie doładowanie albo już je zwrócono |
bad_status, order_finalized | Zamówienie: nieznany status albo jest już zamknięte |
bad_time, past_time, too_far, too_short, too_long | Rezerwacja: przedział nie pasuje |
outside_window, gs_busy, too_many_reserves | Rezerwacja: poza godzinami klubu, czas zajęty, zbyt wiele rezerwacji |
no_client | Przy stanowisku nie ma grającego klienta (kredyt) |
discount_not_found, credit_unavailable | Rabatu nie ma albo kredyt jest w organizacji niedostępny |
---
6. Uprawnienia klucza
Tryb dostępu klucza wybiera właściciel:
- Pełny dostęp — klucz może wszystko, co właściciel organizacji;
- Grupa dostępu — uprawnienia wybranej grupy, jak u pracownika;
- Własny zestaw — tylko zaznaczone funkcje.
Uprawnienie każdej akcji to nazwa funkcji w panelu i jest ona podpisana w karcie klucza obok pozycji uprawnień. Właściciel widzi więc dokładnie to, co jest sprawdzane przy żądaniu: jeśli partner dostaje forbidden_action na gs.list, w karcie klucza trzeba zaznaczyć pozycję z uprawnieniem spike_gs_list.
Termin klucza sprawdza serwer: po jego upływie każde żądanie odpowiada key_expired (401), nawet jeśli partner o tym nie wie.
---
7. Częstotliwość żądań
Limit to 120 żądań na minutę na klucz (wartość zmienia administrator systemu). Po przekroczeniu przychodzi 429 rate_limited i nagłówek Retry-After: 60.
Odpytywanie stanu sali raz na 2–5 sekund mieści się w limicie z zapasem. Jeśli trzeba częściej — poproś właściciela klubu o podniesienie limitu, a nie obchodź go kilkoma kluczami: limit liczy się na klucz właśnie po to, by jeden partner nie przeszkadzał innym.
---
8. Dziennik żądań
W karcie klucza właściciel może włączyć dziennik żądań: wtedy każde wywołanie tym kluczem jest przechowywane 7 dni — akcja, kod odpowiedzi, czas trwania, IP i treść żądania (klucz jest z niej wycinany). To narzędzie do rozwiązywania „u partnera nie działa”: na stałe dziennik nie jest potrzebny.
Jeśli twoje żądanie dostaje odmowę, a powód jest niejasny, poproś właściciela o włączenie dziennika i powtórz żądanie — w dzienniku będzie widać akcję, kod i to, co dokładnie przysłałeś.
---
9. Czego w tym API nie ma
Edycji danych słownikowych. Taryfy, abonamenty, towary, kluby, stanowiska, pracownicy, uprawnienia i ustawienia organizacji nie zmieniają się przez API — to robi się w panelu. Partner ma pracować z klientami i salą, a nie przepisywać ustawienia klubu, a każda taka zmiana wymagałaby własnych sprawdzeń formularza.
Listy kluczy. Kluczem nie można odczytać tabeli kluczy: partner zobaczyłby klucze innych partnerów tej samej organizacji.
Struktury uprawnień panelu nie wydajemy: zewnętrznemu systemowi nic nie mówi, a w odpowiedzi pojawiałyby się nazwy naszych wewnętrznych funkcji.
---
10. Webhooki: zdarzenia same przychodzą do Ciebie
Aby nie odpytywać API w poszukiwaniu nowego zamówienia, podaj właścicielowi klubu swój adres — zdarzenia będą trafiać na niego same. Ustawia się to w panelu organizacji (karta „Klienci i zamówienia”): adres, wyłącznik, klucz podpisu i lista zdarzeń.
Żądanie: POST <Twój adres>, treść to JSON:
{ "event": "order.new",
"org_id": 11,
"sent_at": "2026-10-08T12:46:08+03:00",
"data": { "order_id": 42, "club_id": 1, "gs_id": 11, "client_id": 35839,
"total": 320.00, "items": 3 } }
Nagłówki: X-Spike-Event (nazwa zdarzenia), X-Spike-Org (organizacja) oraz X-Spike-Signature — podpis treści.
Zdarzenia
| Zdarzenie | Kiedy | Co jest w data |
|---|---|---|
order.new | klient złożył zamówienie na stanowisku | order_id, club_id, gs_id, client_id, total, items |
order.status | zmienił się status zamówienia | order_id, club_id, gs_id, old_status, status, total, user_login |
session.start | klient zalogował się na stanowisku | session_id, club_id, gs_id, client_id, auth_method |
session.end | sesja została zamknięta | to samo plus date_start, date_end, duration_seconds, time_cost, products_cost, total_cost, reason |
client.topup | doładowanie salda klienta | client_id, login, amount, balance, club_id, payment_method, type, comment, source |
Organizacja może zapisać się na część zdarzeń — wtedy przyjdą tylko one.
Podpis: sprawdzaj go
X-Spike-Signature: sha256=<hex> to HMAC-SHA256 z całej treści żądania na kluczu, który właściciel klubu ustawił w ustawieniach. Policz podpis samodzielnie i porównaj z nagłówkiem: bez tej weryfikacji na Twój adres będzie mógł pisać każdy, podając się za nas. Jeśli klucz nie jest ustawiony, nagłówka nie będzie; i tak warto poprosić właściciela, żeby go ustawił.
# przykład weryfikacji (bash)
calc=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
[ "sha256=$calc" = "$SIG" ] || exit 1
Dostarczanie
Wysyła kolejka, a nie żądanie operatora: panel i serwis socketowy nigdy nie czekają na Twój serwer. Stąd zasady, na które możesz liczyć:
- limity czasu — 5 sekund na połączenie i 10 na całą wymianę;
- trzy próby z przerwami (od razu, po 2 i po 5 sekundach), dopóki odpowiedź nie jest
2xx; - odpowiedzi
4xx(poza408i429) nie powtarzamy — uznajemy żądanie za odrzucone; - przekierowania nie są wykonywane:
Locationjest ignorowany, POST nie pójdzie dalej; - kolejność zdarzeń nie jest gwarantowana, a dostarczenie co najmniej raz nie jest zapewnione — jeśli Twój serwer nie działał przy wszystkich trzech próbach, zdarzenie przepadnie. Rzeczy krytyczne uzgadniaj czytając API: są w nim zamówienia, sesje i operacje.
Odpowiadaj 200 jak najszybciej i przetwarzaj zdarzenie u siebie asynchronicznie: dopóki trzymasz połączenie, zadanie kolejki jest zajęte Tobą.