JSON API
Kým ho nezapnete, je vypnuté:
EVENTS_ROUTES=on- Prefix events.routes.prefix (api), názvy rout events.*, middleware events.routes.middleware (['api', 'throttle:events-api']); zapisovacie routy pridajú events.routes.auth_middleware (['auth']).
- Nenastavený prepínač zostáva vypnutý — aj v publikovanej konfigurácii, ktorej pole routes kľúč nemá, pretože zlučovanie je plytké.
- Ak chcete upraviť súbor rout, publikujte --tag=events-routes: keď routes/events.php vo vašej aplikácii existuje, načíta sa namiesto súboru balíka a prepínač ho naďalej riadi.
- Zápisy vždy vyžadujú prihláseného používateľa. Po vašom auth_middleware spustí každá zapisovacia routa vlastnú kontrolu balíka: chýbajúci prihlásený používateľ alebo používateľ guardu, ktorý nie je Eloquent model, je 401 — aj pri auth_middleware = []. Beží pred validáciou, takže hosť sa nikdy nedozvie tvar požiadavky. Čítacie routy a routy odkazu z pozvánky hostí naďalej pustia.
- Čítania rozpoznajú váš guard. Každá routa spúšťa aj UseConfiguredGuard: keď auth_middleware uvádza guardy (auth:sanctum alebo Authenticate::class s guardmi), prvý z nich, ktorý požiadavku overí, sa stane jej guardom, ako to robí middleware auth z Laravelu — nikoho však neodmietne. Používateľ s tokenom potom číta ako on sám — jeho súkromné podujatia, viewer.rsvp, filter[mine], odkaz na pripojenie — a požiadavka bez tokenu je hosť. Obyčajné auth používa predvolený guard, ktorý čítania používajú už teraz.
// config/events.php — a token client reads as itself, writes need a token
'routes' => [
'enabled' => env('EVENTS_ROUTES') ?: 'off',
'prefix' => 'api',
'middleware' => ['api', 'throttle:events-api'],
'auth_middleware' => ['auth:sanctum'],
'max_per_page' => 100,
'rate_limits' => ['api' => 120, 'rsvp' => 20, 'check_in' => 300, 'feeds' => 60],
],Konvencie
- {event} je uuid — obmedzené tak, aby sa naň /events/feed.ics nikdy nedostal — a podujatie, ktoré volajúci vidieť nesmie, je 404 presne ako neexistujúce, na každom endpointe. {rsvp} a {tier} sú obmedzené na podujatie; {tier} je kľúč typu vstupenky — aj čisto číselný, ak ho niektorý typ podujatia má —, inak je číslo jeho id. {venue} je číselné.
- Na vaše modely sa API odvoláva cez alias, nikdy cez názov triedy: {type: user, id: 42} v telách a user:42 v cestách a filtroch (events.organizers, events.attendees) — ten istý alias, ktorý vykresľujú odpovede, takže klient pošle späť to, čo prečítal. V tele je každé zlyhanie — neznámy alias, chybný kľúč, chýbajúci riadok — rovnaké 422 unknown_alias.
- Časy v požiadavkách sú miestny čas v pásme podujatia alebo okamih ISO-8601 s posunom; odpovede používajú ISO-8601 s vlastným posunom podujatia.
- Textové polia prijímajú reťazec (aktuálny jazyk) alebo mapu jazykov. Ceny sú desatinné reťazce s voliteľnou menou — viac desatinných miest, než má mena (15.999 EUR, 15.5 JPY), je 422 invalid_tier; odpovede vykresľujú MoneyResource z money-for-laravel — {minor, decimal, currency, exponent, formatted}, pričom minor je reťazec a formatted je len na zobrazenie. Bezplatný typ vstupenky má price: null.
- Zoznamy sú length-aware paginátory z Laravelu (data, links, current_page, per_page, total, …) s per_page obmedzeným na events.routes.max_per_page.
Routy
Všetkých 49 rout, zoskupených, s cestami relatívne k prefixu. Riadky s poznámkou „Aj hostia“ odpovedajú aj neprihláseným; každá iná routa vyžaduje prihláseného používateľa:
| Metóda · cesta | Názov | Oprávnenie | Poznámky |
|---|---|---|---|
GET /events | events.index | viewAny | Aj hostia. Filtre objavovania nižšie → stránkovaný EventResource. |
GET /events/feed.ics | events.feed | — | Aj hostia. ?category=&label=&organizer=alias:key&venue= → text/calendar (inline); throttle events-feeds. |
GET /events/{event} | events.show | view | Aj hostia. {data: EventResource} s hosts a viewer.can. |
GET /events/{event}/calendar.ics | events.calendar | view | Aj hostia. text/calendar (príloha); odkaz na pripojenie len pre diváka, ktorý ho smie vidieť. |
GET /events/{event}/tiers | events.tiers.index | view | Aj hostia. Skryté len pre správcov. |
GET /events/{event}/attendees | events.attendees.index | viewAttendees | Aj hostia. ?status=&checked_in=&tier=&per_page= (predvolene 25) — filtre len pre správcov. |
GET /events/{event}/hosts | events.hosts.index | view | Aj hostia. {data: [referencia spoluorganizátora]}. |
GET /event-venues | events.venues.index | — | Aj hostia. ?mine=1&near=lat,lng&radius=&per_page= (predvolene 25). |
GET /event-venues/{venue} | events.venues.show | — | Aj hostia. |
POST /event-invitations/accept · decline | events.invitations.accept · decline | token | Aj hostia. {token, response?} (predvolene going, interested) → RsvpResource (pohľad vlastníka) / {status: declined}; throttle events-rsvp. |
POST /events | events.store | create | 201 {data: EventResource}. |
PATCH /events/{event} · DELETE /events/{event} | events.update · destroy | update · delete | Zmenia sa len poslané polia; ?notify=0 zapíše potichu · 204. |
POST /events/{event}/restore | events.restore | restore | Zmazané podujatia, len správcovia (ostatní 404). |
POST /events/{event}/publish · unpublish · complete | events.publish · … | publish · … | {data: EventResource} |
POST /events/{event}/postpone · cancel | events.postpone · cancel | postpone · cancel | {reason?} (≤ 500). |
POST /events/{event}/reschedule | events.reschedule | reschedule | {starts_at, ends_at?, reason?} |
POST /events/{event}/move-online | events.move-online | moveOnline | {url, keep_venue?} |
PUT · DELETE /events/{event}/poster | events.poster.update · destroy | update | Multipart poster alebo token draftu · odstráni ho. |
POST /events/{event}/tiers · tiers/reorder | events.tiers.store · reorder | manageTiers | 201 AdmissionTierResource · {order: [id typu, …]} → 204. |
PATCH · DELETE /events/{event}/tiers/{tier} | events.tiers.update · destroy | manageTiers | Typ vstupenky podľa kľúča (aj čisto číselného), inak je číslo jeho id; len poslané polia · 204. |
GET /events/{event}/rsvp · rsvp/qr.svg | events.rsvp.show · qr | — | Vaše RSVP (pohľad vlastníka) alebo null · váš kód ako image/svg+xml, Cache-Control: no-store, private. |
PUT · DELETE /events/{event}/rsvp | events.rsvp.update · destroy | rsvp | {response, guests?, tier?, note?} · odpovie not_going; throttle events-rsvp. |
POST /events/{event}/attendees | events.attendees.store | manageAttendees | {attendee: {type, id}, tier?, guests?, over_capacity?} → 201 |
PATCH · DELETE /events/{event}/attendees/{rsvp} | events.attendees.update · destroy | manageAttendees | {tier?, guests?}: aspoň jedno, inak 422 — overuje sa až po policy, takže kto nie je správca, dostane 403 pri akomkoľvek tele; uplatní sa oboje alebo nič · 204. |
POST · DELETE /events/{event}/attendees/{rsvp}/check-in | events.attendees.check-in · undo-check-in | checkIn | Ručný check-in {guests?} (bez kódu) · zrušenie. |
POST /events/{event}/check-ins | events.check-ins.store | checkIn | Skenovanie pri vstupe {code, guests?} → CheckInResultResource, 201 prvý raz, 200 opakovane; throttle events-check-in. |
GET /events/{event}/waitlist · POST …/waitlist/promote | events.waitlist.index · promote | viewWaitlist · manageAttendees | V poradí posúvania · {limit?} (1–1000). |
POST · DELETE /events/{event}/hosts… | events.hosts.store · destroy | manageHosts | {person: {type, id}} (Organizer) → 201 · {host} = alias:key → 204. |
GET · POST · DELETE /events/{event}/invitations… | events.invitations.index · store · destroy | invite | Tokeny sa nikdy nevykreslia · {person: {type, id}, message?} (≤ 1000) → 201 · {invitee} = alias:key → 204. |
POST · PATCH · DELETE /event-venues… | events.venues.store · update · destroy | VenuePolicy | Telo miesta konania nižšie → 201 · len poslané polia · 204. |
POST /event-venues/{venue}/geocode | events.venues.geocode | VenuePolicy geocode | Vždy geokóduje znova (force). |
Routa na potvrdenie platby neexistuje: potvrďte ju z webhooku poskytovateľa v PHP cez Events::rsvps()->confirmPayment($reference).
Vytvorenie podujatia
{
"title": { "en": "Jazz Night", "sk": "Jazzový večer" },
"venue_id": 7,
"starts_at": "2026-11-14 20:00",
"duration": "PT3H",
"capacity": 120,
"max_guests": 1,
"category": "jazz",
"labels": ["Live", "Late night"],
"tiers": [
{ "name": "Free entry", "key": "free" },
{ "name": "Supporter", "price": "15.00", "currency": "EUR", "capacity": 30 }
],
"publish": true
}| Pole | Pravidlá |
|---|---|
title | Povinné; reťazec alebo mapa jazykov, každý ≤ 200 znakov. |
description | ≤ 20 000. |
organizer | {type, id} z events.organizers — predvolene volajúci; musí volajúceho povoliť. |
venue_id · venue | Existujúce miesto konania (akýkoľvek organizátor môže usporiadať podujatie na akomkoľvek mieste) — alebo nové miesto priamo v tele, vlastnené volajúcim: name (povinné), street, city, region, postal_code, country, coordinates, timezone. |
online_url · attendance_mode · visibility · guest_list · timezone | ≤ 2048 · hodnoty enumov · názov IANA. |
starts_at · ends_at · duration · doors_open_at · all_day_from · all_day_through | starts_at povinné, ak nie je all_day_from; celodenné dátumy Y-m-d. |
capacity · max_guests | 0–10,000,000 · 0–1000 |
waitlist · waitlist_auto_promote · show_counts · publish | Boolean hodnoty; publish: true ho ohlási v tej istej transakcii. |
rsvp_opens_at · rsvp_closes_at · reminders | Reťazce; ≤ 5 pripomienok. |
category · labels | Kategória stromu · ≤ 10 reťazcov (v katalógu organizátora pri events.tags.scope = organizer). |
tiers | ≤ 50 položiek {name, key?, price?, currency?, capacity?, hidden?}; price v tvare ^\d{1,12}(\.\d{1,4})?$. |
poster_draft | Token draftu z media-library. |
PATCH /events/{event} zmení len poslané polia a null vymaže pole, ktoré môže byť prázdne; category (null vymaže) a labels (presne tieto) sa uplatnia v tej istej transakcii. Nový začiatok ohláseného podujatia sa odmietne — použite POST /reschedule. ?notify=0, alebo notify: false v tele, zapíše potichu.
Typy vstupeniek a miesta konania
Telá typov vstupeniek prijímajú name (pri vytvorení povinné, ≤ 200), description (≤ 2000), key (≤ 64; bez neho sa odvodí z názvu), price + currency, capacity, sale_starts_at, sale_ends_at, min_party (1–1000), max_party, hidden a position. Telá miest konania prijímajú name (pri vytvorení povinné), street, city, region, postal_code, country (2 písmená), formatted_address, coordinates (lat,lng, [lat, lng] alebo {latitude, longitude}), timezone, capacity a geocode a pri vytvorení owner ({type, id}, ktorý musí volajúceho povoliť) alebo shared (len administrátori a abilities.shared_venues). Bez owner a shared patrí nové miesto volajúcemu. Pri PATCH sa každé pole adresy mení samostatne a vlastníctvo sa nikdy nemení.
Vaše RSVP
PUT /api/events/{event}/rsvp s telom:
{ "response": "going", "tier": "free", "guests": 1, "note": "Wheelchair access" }vráti vaše vlastné RSVP v pohľade vlastníka, s kódom pre check-in a URL QR kódu:
{
"data": {
"id": 42,
"status": "going",
"guests": 1,
"party_size": 2,
"tier": { "id": 1, "key": "free", "name": "Free entry" },
"note": "Wheelchair access",
"waitlist_position": null,
"held_until": null,
"amount": null,
"checked_in_at": null,
"checked_in_guests": null,
"responded_at": "2026-11-02T18:12:40+01:00",
"payment_url": null,
"check_in_code": "EV1-…",
"qr_url": "https://example.com/api/events/0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21/rsvp/qr.svg"
}
}Zoznam hostí
GET /events/{event}/attendees vypíše správcom akýkoľvek stav (?status=, predvolene going) so záznamom o check-ine — nikdy s kódmi — a filtrom podľa checked_in a tier. Ktokoľvek iný, kto zoznam smie vidieť, uvidí len účastníkov so stavom going ako {attendee, guests}: status=interested je preňho 403 a status, checked_in a tier sa ignorujú — vždy celý zoznam, nikdy zúžený na to, kto je na mieste alebo kto má skrytý typ vstupenky.
Objavovanie: GET /events
Postavené na scopes modelu, takže PHP aj HTTP filtrujú rovnako; query-builder rozdelí každú hodnotu podľa čiarok. Základom je browse($viewer) — viditeľné, verejne uvedené, ohlásené:
| Parameter | Scope |
|---|---|
filter[when]=upcoming|ongoing|past | upcoming() … |
filter[from], filter[to] | Prekrývajúce sa s rozsahom, miestny čas v ?tz= (predvolene app.timezone). |
filter[date]=2026-11-14 | onLocalDate() — skutočný dátum (2027-02-30 je 422). |
filter[near]=48.14,17.11,25 | near() — polomer voliteľný, ≤ geo.max_radius_km. |
filter[category]=jazz | inCategory() vrátane podkategórií. |
filter[labels]=live,outdoor | Všetky štítky. |
filter[organizer]=user:42 | organizedBy() — riadok sa nikdy nevyhľadáva; neexistujúci nezodpovedá ničomu. |
filter[venue]=7,8 · filter[mode]=online,mixed | atVenue() · withAttendanceMode() |
filter[free]=true · filter[available]=true | free() · withSeatsAvailable() |
filter[search]=… | search() — vyhľadávanie podreťazca v názve a popise so zneškodnenými znakmi (čiarky zostanú). |
filter[status]=postponed,rescheduled | inPhase() |
filter[mine]=organizing|attending|interested | Vaše podujatia, každá viditeľnosť (organizing vrátane konceptov); hostia nedostanú nič. |
sort=starts_at (default), -starts_at, created_at, -created_at, popularity, distance | distance vyžaduje filter[near] (inak 400); popularity počíta podujatie s vypnutým show_counts ako nula účastníkov — okrem filter[mine]=organizing. |
per_page | Predvolene 15, ≤ events.routes.max_per_page. |
Neplatná hodnota filtra je 422 invalid_query. Stránka načíta miesta konania a ich vlastníkov, organizátorov, štítky, plagáty, typy vstupeniek, vlastné RSVP diváka, cesty kategórií a to, ktoré podujatia divák spravuje, v pevnom počte dotazov — počet nerastie s veľkosťou stránky.
Resources
Bezplatné podujatie tak, ako ho vidí hosť v zozname:
{
"id": "0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21",
"slug": "jazz-night",
"title": "Jazz Night",
"description": "A live quartet, then an open jam session.",
"phase": "scheduled",
"schema_status": "EventScheduled",
"attendance_mode": "offline",
"starts_at": "2026-11-14T20:00:00+01:00",
"ends_at": "2026-11-14T23:00:00+01:00",
"doors_open_at": null,
"previous_starts_at": null,
"all_day": false,
"timezone": "Europe/Bratislava",
"starts_on": "2026-11-14",
"ends_on": "2026-11-14",
"venue": {
"id": 7,
"name": "Blue Note",
"slug": "blue-note",
"address": {
"street": "Hlavná 1", "city": "Bratislava", "region": null, "postal_code": "811 01",
"country": "SK", "formatted": "Hlavná 1, 811 01 Bratislava, SK"
},
"coordinates": { "latitude": 48.1439, "longitude": 17.1097 },
"timezone": "Europe/Bratislava",
"capacity": null,
"owner": null,
"distance_km": null,
"deleted": false
},
"online_url": null,
"organizer": { "id": 1, "type": "user" },
"category": { "id": 3, "slug": "jazz", "name": "Jazz", "color": null, "icon": null, "path": ["Music"] },
"labels": [{ "id": 4, "slug": "live", "name": "Live", "color": null, "icon": null }],
"poster": null,
"tiers": [
{
"id": 1, "key": "free", "name": "Free entry", "description": null, "price": null, "free": true,
"capacity": null, "seats_left": null, "on_sale": true, "sale_starts_at": null, "sale_ends_at": null,
"min_party": 1, "max_party": null, "position": 1
}
],
"capacity": 120,
"seats_left": 85,
"counts": { "going": 35, "waitlisted": 0 },
"viewer": { "rsvp": null },
"links": {
"self": "https://example.com/api/events/0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21",
"ics": "https://example.com/api/events/0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21/calendar.ics",
"url": "https://example.com/en/events/jazz-night"
}
}| Resource | Tvar |
|---|---|
EventResource | Pozrite príklad vyššie. status a visibility len pre správcov; online_url pre správcov a účastníkov so stavom going; seats_left a counts pre správcov, alebo pre všetkých pri show_counts (counts.interested len pre správcov); hosts a viewer.can len v detaile. |
VenueResource | id, name, slug, address {street, city, region, postal_code, country, formatted}, coordinates {latitude, longitude} | null, timezone, capacity, owner, distance_km (3 decimals, when selected), deleted — never meta |
AdmissionTierResource | id, key, name, description, price (MoneyResource alebo null), free, capacity, seats_left, on_sale, sale_starts_at, sale_ends_at, min_party, max_party, position a hidden pre správcov. |
RsvpResource | id, status, guests, party_size, tier {id, key, name}, note, waitlist_position, held_until, amount, checked_in_at, checked_in_guests, responded_at; pohľad vlastníka pridá payment_url (kým je rezervované), check_in_code a qr_url; pohľad správcov pridá attendee, check_in_method a checked_in_by — nikdy kód. |
InvitationResource | id, status, invitee, invited_by, message, expires_at (null, kým je odložené), responded_at, sent_at — nikdy token. |
CheckInResultResource | first_check_in, guests, checked_in_at, method, attendee, rsvp (RsvpResource) |
Referenčné resources
Každá osoba či tím, ktorý odpoveď spomína, sa vykreslí cez vlastný resource — predvolene {id, type}, kde type je alias modelu z events.organizers (prednostne) alebo events.attendees (user), ten istý, ktorý prijímajú telá a cesty alias:key; trieda, ktorá nie je v žiadnej mape, sa vykreslí krátkym názvom triedy. Nikdy názov PHP triedy ani morph typ, aj bez morph mapy. Chýbajúci model sa vykreslí ako null. Verzia 1.0 vykresľovala pri každej triede krátky názov (User):
| Konfiguračný kľúč | Predvolené | Vykresľuje |
|---|---|---|
events.resources.organizer | EventOrganizerResource | organizer |
events.resources.host | EventHostResource | spoluorganizátori |
events.resources.attendee | EventAttendeeResource | účastníci |
events.resources.invitee | EventInviteeResource | pozvaní |
events.resources.actor | EventActorResource | invited_by, checked_in_by |
events.resources.venue_owner | VenueOwnerResource | vlastník miesta konania |
Ak chcete pridať meno či avatar, nastavte kľúč na vlastný JsonResource — dostane model. Prázdna hodnota znamená predvolený; čokoľvek, čo nie je inštanciovateľná podtrieda JsonResource, vyhodí výnimku s názvom kľúča. Zoznamy súvisiace modely načítajú vopred, takže resource, ktorý číta len ich atribúty, nepridá žiadne dotazy:
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
final class PersonResource extends JsonResource
{
public function toArray(Request $request): array
{
return ['id' => $this->resource->getKey(), 'name' => $this->resource->name, 'avatar' => $this->resource->avatar_url];
}
}
// config/events.php
'resources' => [
'organizer' => App\Http\Resources\PersonResource::class,
'host' => EventHostResource::class,
'attendee' => App\Http\Resources\PersonResource::class,
'invitee' => EventInviteeResource::class,
'actor' => EventActorResource::class,
'venue_owner' => VenueOwnerResource::class,
],Chyby
{message, code} so stavom balíka — plus reason pri odmietnutí check-inu, denials pri zamietnutom prechode a errors pri validačnom 422. Správy sú preložené a nikdy neuvádzajú triedu a váš exception handler sa nikdy nemení:
{ "message": "This code is for another event.", "code": "check_in_refused", "reason": "wrong_event" }| Stav | code | Kedy |
|---|---|---|
| 401 | (váš handler) | Zápis bez prihláseného Eloquent používateľa. |
| 403 | forbidden · invitation_required | Policy zamietla · súkromné podujatie bez pozvánky. |
| 404 | event_not_found, venue_not_found, tier_not_found, rsvp_not_found, invalid_code, invalid_invitation | Neexistuje alebo je skryté. |
| 409 | event_trashed, rsvp_closed, event_full, tier_sold_out, tier_not_on_sale, hold_expired, invalid_rsvp_transition, check_in_refused | Konflikt stavu (check_in_refused pridá reason). |
| 422 | validation_failed, invalid_event, invalid_schedule, invalid_venue, venue_in_use, invalid_tier, tier_key_taken, tier_in_use, tier_required, invalid_guest_count, invalid_organizer, unknown_alias, unknown_transition, transition_denied, invalid_query, tag_not_found, tag_limit_exceeded, invalid_tag, invalid_poster | Chybný vstup (validation_failed pridá errors, transition_denied pridá denials). |
| 400 | invalid_query | sort=distance bez filter[near]. |
| 429 | transition_denied (+ Retry-After) alebo throttle | Prekročený limit požiadaviek. |
Štítok iného rozsahu odpovie tag_not_found, rovnako ako chýbajúci. Plagát, ktorý media-library odmietne, alebo neznámy či vypršaný draft je invalid_poster.
Limity požiadaviek
Definujú sa, len keď sú routy zapnuté, za minútu:
| Limiter | Predvolené | Kľúč | Routy |
|---|---|---|---|
events-api | routes.rate_limits.api = 120 | používateľ, inak IP | Každá routa (v routes.middleware). |
events-rsvp | rate_limits.rsvp = 20 | používateľ (alebo IP) a podujatie | PUT / DELETE /rsvp, prijatie / odmietnutie pozvánky. |
events-check-in | rate_limits.check_in = 300 | používateľ | POST /check-ins — skenery pri vstupe pracujú v nárazoch. |
events-feeds | rate_limits.feeds = 60 | IP | feed.ics, calendar.ics |
Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.