OAuth i prijava putem Sportyja

Partnerski API

Što pristupni token zapravo može čitati i pisati, a što namjerno ne može.

https://api.sporty.plus/api/partner/v1

Ovo je jedino mjesto na kojem OAuth pristupni token radi. Običan JSON — ne JSON:API — s namjerno malenom površinom: svako polje je ono koje je netko odlučio izložiti.

curl https://api.sporty.plus/api/partner/v1/courts \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Krajnje točke

MetodaPutanjaOpseg
GET/meme.read
GET/me/reservationsme.reservations.read
POST/me/reservationsme.reservations.write
DELETE/me/reservations/{id}me.reservations.write
GET/me/resultsme.results.read
GET/me/membershipsme.memberships.read
GET/clubs/meclub.read
GET/courtsclub.courts.read
GET/availabilityclub.courts.read
GET/reservationsclub.reservations.read
GET/reservations/{id}club.reservations.read
GET/reservation-categoriesclub.reservations.read
POST/reservationsclub.reservations.write
PATCH/reservations/{id}club.reservations.write
DELETE/reservations/{id}club.reservations.write
GET PUT DELETE/webhookclub.reservations.read
GET/membersclub.members.read
POST/membersclub.members.write

GET /me

Član za kojeg je token izdan: id, first_name, last_name, name, picture i locale. Adresa e-pošte nije dio toga; zatražite opseg email i pročitajte je s /oauth2/userinfo.

Ova krajnja točka i /me/reservations traže token izdan za člana (authorization code), a riječ je o vlastitim podacima člana, ne kluba, pa nijedan klub vašoj aplikaciji ne mora ništa odobriti. Token dobiven putem client credentials dobiva 401. Ako je član vašu aplikaciju autorizirao samo za jedan klub, vidite onaj dio njegovih podataka koji se odnosi na taj klub.

GET /me/reservations

Rezervacije u kojima član igra ili trenira, samo nadolazeće, osim ako ne zatražite drugačije. from i to (ISO 8601) određuju vremenski raspon; per_page ide do 100.

{
  "data": [
    {
      "id": 918301,
      "club": { "id": 57, "name": "TK Test" },
      "court_id": 412,
      "court_name": "Teren 3",
      "from": "2026-10-07T16:00:00+00:00",
      "to": "2026-10-07T17:00:00+00:00",
      "approved": true
    }
  ]
}

Ostali igrači nisu uključeni: s kim član igra, odluka je ostalih igrača hoće li to podijeliti.

POST /me/reservations

Rezervira teren kao član. Ovdje vrijede sva pravila koja klub na njega primjenjuje u aplikaciji Sporty:

  • radno vrijeme i cijena njegova članstva;
  • koliko unaprijed smije rezervirati i koliko blizu početka;
  • koliko rezervacija smije imati;
  • mora li najprije platiti.
{
  "court_id": 412,
  "from": "2026-10-07T18:00:00+02:00",
  "to": "2026-10-07T19:30:00+02:00",
  "external_id": "agent-7f3a"
}
  • from i to moraju se poklapati s terminima terena, onako kako ih navodi GET /availability. Rezervacija mora trajati cijeli broj termina duljine reservation_duration, i svaki od njih mora biti otvoren za ovog člana.
  • external_id je neobavezan: vaš id zahtjeva, do 191 znaka. Ponovno slanje odgovara 200 s već napravljenom rezervacijom, pa ponovni pokušaj nikad ne rezervira dvaput.

Odgovor je 201:

{
  "data": {
    "id": 918402,
    "club": { "id": 57, "name": "TK Test" },
    "court_id": 412,
    "court_name": "Teren 3",
    "from": "2026-10-07T16:00:00+00:00",
    "to": "2026-10-07T17:30:00+00:00",
    "approved": false,
    "price": 20,
    "needs_payment": true,
    "pay_by": "2026-10-01T10:20:00+00:00"
  }
}

Plaćanje se nikad ne naplaćuje putem API-ja. Kad je needs_payment true, član mora platiti u aplikaciji Sporty prije pay_by, inače klub termin vraća. Recite mu to. Klub koji ne traži plaćanje unaprijed odgovara s needs_payment: false i pay_by: null. pay_by se navodi samo u odgovoru kojim je rezervacija napravljena, a pri ponavljanju je null.

Član dobiva obavijest kao i za svaku rezervaciju koju napravi sam, a klub je u svojem rasporedu vidi kao njegovu.

Kad član ne može rezervirati, odgovor je 409 s kodom te porukom samog pravila, prevedenom za člana i namijenjenom da mu se prikaže:

errorZnačenje
slot_takenNetko drugi ima dio tog vremena.
outside_opening_hoursTeren nije otvoren za ovog člana tijekom cijelog tog vremena.
court_closedTeren je tog dana zatvoren.
in_pastTo je vrijeme prošlo.
too_soonPreblizu početka za njegovo članstvo.
too_far_aheadDalje unaprijed nego što njegovo članstvo dopušta.
leaves_a_gapOstala bi praznina od pola sata koju klub ne dopušta.
duration_not_offeredKlub u to vrijeme ne prodaje takvo trajanje.
limit_reachedVeć ima onoliko rezervacija koliko mu članstvo dopušta.
not_a_memberKlub ga još nije prihvatio.
not_bookableBilo koje drugo pravilo; poruka kaže koje.

invalid_times (422) znači da vremena nisu cijeli termini ili prelaze ponoć. 403 access_denied znači da je član vašu aplikaciju autorizirao za neki drugi klub.

DELETE /me/reservations/{id}

Otkazuje jednu od članovih rezervacija kao član, pa vrijedi njegov rok za otkazivanje. Sve što je platio vraća se u njegov klupski novčanik, a ostali igrači dobivaju obavijest. Odgovor je 204. Odgovor je 409 not_cancelable, s razlogom, kad je prekasno ili rezervaciju ne može otkazati on (rezervirao ju je netko drugi). Odgovor je 404 za rezervaciju u kojoj nije.

GET /me/results

Rezultati članovih mečeva, od najnovijih. from i to (ISO 8601) sužavaju popis; per_page ide do 100.

{
  "data": [
    {
      "id": 22875,
      "club": { "id": 57, "name": "TK Test" },
      "played_at": "2026-09-27T18:00:00+00:00",
      "sport": "tennis",
      "kind": "league",
      "sets": [
        { "games": [2, 6], "tie_break": null },
        { "games": [6, 3], "tie_break": null },
        { "games": [7, 6], "tie_break": [10, 8] }
      ],
      "won": true,
      "official": true,
      "verified": true
    }
  ]
}
  • Rezultati su iz perspektive člana. Svaki par je [članov, protivnički], pa nikad ne morate znati na kojoj je strani meča bio.
  • Setovi: navode se samo odigrani setovi.
  • won: null kad nije bilo pobjednika.
  • kind: league, tournament ili match.
  • verified: protivnici su potvrdili rezultat.
  • Protivnici nisu uključeni: s kim je član igrao, odluka je protivnika hoće li to podijeliti.

GET /me/memberships

Klubovi kojima član pripada i s kojim planom: svako članstvo koje nije završilo.

{
  "data": [
    {
      "club": { "id": 57, "name": "TK Test" },
      "membership": { "id": 12, "name": "Seniori", "basic": false },
      "start": "2026-01-01T00:00:00+00:00",
      "end": "2026-12-31T00:00:00+00:00",
      "renews": false
    }
  ]
}

end je null za članstvo bez datuma završetka. renews kaže obnavlja li se samo. basic označava plan koji svaki član kluba ima prije nego što kupi neki drugi.

GET /clubs/me

Za koji klub djeluje vaš token. Isplati se pozvati prvo, osobito ako imate odobrenja za više klubova.

GET /courts

per_page do 100. Svaki teren ima id, name, active, indoor, lights, working_from, working_to, reservation_duration (minute po terminu) i sports, ono što se na njemu igra:

"sports": [ { "id": 2, "slug": "padel", "name": "Padel" } ]

Većina terena ima jedan sport; nekoliko ih ima dva, a teren kojem ga nitko nije postavio nema nijedan.

GET /availability

Koji su termini slobodni. date (YYYY-MM-DD, lokalni dan kluba) je obavezan; days obuhvaća do 7 dana od tog datuma; court_id sužava upit na jedan teren.

{
  "data": [
    {
      "court_id": 412,
      "date": "2026-10-07",
      "timezone": "Europe/Zagreb",
      "closed": null,
      "slots": [
        { "from": "2026-10-07T06:00:00+00:00", "to": "2026-10-07T07:00:00+00:00", "available": false },
        { "from": "2026-10-07T07:00:00+00:00", "to": "2026-10-07T08:00:00+00:00", "available": true }
      ]
    }
  ]
}
  • Jedan unos po terenu po danu. Termini prate klupski raspored za taj dan, svaki traje reservation_duration. Vremena su u UTC-u; timezone je vremenska zona kluba.
  • Zauzeto ili slobodno. Termin je zauzet ("available": false) kad se s njim preklapa bilo koja rezervacija, makar i djelomično.
  • Zatvoreni tereni. closed je inactive ili court_break kad se teren tog dana uopće ne može rezervirati, a njegov je slots tada prazan.
  • Čiji raspored. Ovo je standardni raspored kluba, onaj koji vidi posjetitelj koji nije prijavljen. Članstvo pojedinog člana može otvoriti ili zatvoriti neke sate i odrediti cijenu. Ništa od toga ovdje se ne prikazuje.

GET /reservations

from i to (ISO 8601, UTC) sužavaju vremenski raspon; per_page do 100. Rezervacije se vraćaju bez osoba koje u njima sudjeluju. Za njih je potreban club.members.read i krajnja točka za članove, pa klub te dvije odluke donosi zasebno.

updated_since umjesto toga vraća svaku rezervaciju promijenjenu u tom trenutku ili nakon njega, od najstarije promjene, uključujući otkazane. Namijenjen je za održavanje sinkronizirane kopije.

Svaka rezervacija ima:

Polje
id, court_id, court_name
from, toUTC
category_idKategorija rezervacije kluba, ako postoji
approvedJe li rezervacija potvrđena
cancelledtrue samo uz updated_since ili na GET /reservations/{id}
sourcepartner za rezervaciju koju je poslala vaša aplikacija, sporty za svaku drugu
external_id, nameVaš id i naziv za rezervaciju koju ste poslali; null za svaku drugu
updated_atKad je rezervacija posljednji put promijenjena

Upisivanje rezervacija i webhookovi

POST, PATCH i DELETE /reservations te /webhook namijenjeni su sustavu koji vlastiti raspored usklađuje s klupskim. Opisani su, s primjerima, u Sinkronizaciji rezervacija.

GET /members

search pretražuje ime ili adresu e-pošte. per_page do 100.

POST /members

{
  "email": "new@example.com",
  "firstName": "New",
  "lastName": "Member",
  "language": "hr",
  "countryCode": "HR"
}

language i countryCode su neobavezni, a zadane su im vrijednosti one kluba.

Idempotentno prema adresi e-pošte. Član kojeg već poznajemo pridružuje se klubu i vraća s 200; novi se stvara i vraća s 201. Ponovno slanje iste osobe je sigurno, što je važno kad dva sustava sinkroniziraju osobe.

Što ovdje ne postoji

Naplata. Članska rezervacija napravljena putem API-ja plaća se u aplikaciji Sporty, nikad putem API-ja.

Pogreške

StatusTijeloZnačenje
401invalid_tokenNedostaje, istekao je, opozvan je ili nije naš.
403insufficient_scopeToken je valjan, opseg nije odobren. WWW-Authenticate navodi što je potrebno.
403access_deniedNijedan klub nije odobrio vašu aplikaciju, ili to nije učinio klub koji ste naveli.
400club_requiredDjelujete za više klubova; navedite jedan s X-Club.
422Laravel pogreške validacijeNeispravno tijelo zahtjeva.
429—120 zahtjeva u minuti, po klijentu.

Verzioniranje

v1 je u putanji i neće vam promijeniti oblik ispod ruku. Mogu se dodavati nova polja i nove krajnje točke; postojeća polja neće se uklanjati niti mijenjati značenje. Promjena koja narušava kompatibilnost znači v2, i javit ćemo vam prije nego što stigne.