# Crewli API Contract Base path: `/api/v1/` Auth: Bearer token (Sanctum) ## Auth - `POST /auth/login` - `POST /auth/logout` - `GET /auth/me` ## Organisations - `GET /organisations` — list (super admin) - `POST /organisations` — create - `GET /organisations/{org}` — show - `PUT /organisations/{org}` — update - `GET /organisations/{org}/members` — members - `POST /organisations/{org}/invite` — invite user ## Events - `GET /organisations/{org}/events` — list (top-level only by default) - `GET /organisations/{org}/events?include_children=true` — include sub-events nested in response - `GET /organisations/{org}/events?type=festival` — filter by event_type (festival|series|event) - `POST /organisations/{org}/events` — create (supports `parent_event_id` for sub-events) - `GET /organisations/{org}/events/{event}` — detail (includes children and parent if loaded) - `PUT /organisations/{org}/events/{event}` — update (does NOT accept `status` — use transition endpoint) - `POST /organisations/{org}/events/{event}/transition` — change event status via state machine (see below) - `GET /organisations/{org}/events/{event}/children` — list sub-events of a festival/series ### Event Status Transitions `POST /organisations/{org}/events/{event}/transition` Body: `{ "status": "published" }` Enforces a state machine: only valid forward (and select backward) transitions are allowed. Returns 422 with `errors`, `current_status`, `requested_status`, and `allowed_transitions` when the transition is invalid or prerequisites are missing. **Prerequisites checked:** - `→ published`: name, start_date, end_date required - `→ registration_open`: at least one time slot and one section required **Festival cascade:** Transitioning a festival parent to `showday`, `teardown`, or `closed` automatically cascades to all children in an earlier status. **EventResource** includes `allowed_transitions` (array of valid next statuses) so the frontend knows which buttons to show. ## Crowd Types - `GET /organisations/{org}/crowd-types` - `POST /organisations/{org}/crowd-types` - `PUT /organisations/{org}/crowd-types/{type}` - `DELETE /organisations/{org}/crowd-types/{type}` ## Companies - `GET /organisations/{org}/companies` - `POST /organisations/{org}/companies` - `PUT /organisations/{org}/companies/{company}` - `DELETE /organisations/{org}/companies/{company}` ## Section Categories - `GET /organisations/{org}/section-categories` — distinct categories used across the organisation's events (for autocomplete). Returns `{ "data": ["Bar", "Podium", ...] }` ## Festival Sections - `GET /events/{event}/sections` - `POST /events/{event}/sections` - `PUT /events/{event}/sections/{section}` - `DELETE /events/{event}/sections/{section}` - `POST /events/{event}/sections/reorder` > **Festival context:** `{event}` can be a festival parent or a sub-event. > On a festival parent, sections are for operational planning (build-up, teardown). > For sub-events, `GET` automatically includes `cross_event` sections from the parent festival. > Shifts on cross_event sections must use the **parent festival's event_id** in API calls, > since the section's `event_id` points to the parent. ## Time Slots - `GET /events/{event}/time-slots` - `POST /events/{event}/time-slots` - `PUT /events/{event}/time-slots/{timeSlot}` - `DELETE /events/{event}/time-slots/{timeSlot}` > **Festival context:** `{event}` can be a festival parent or a sub-event. > Festival-level time slots (operational: build-up, teardown, transitions) are separate > from sub-event time slots (program-specific). > > `GET /events/{event}/time-slots` returns only the specified event's own time slots by > default. For sub-events, pass `?include_parent=true` to also include the parent festival's > time slots — each time slot is marked with a `source` field (`sub_event` or `festival`) > and includes `event_name` for display grouping. This parameter has no effect on festivals > or flat events. > > Shifts on sub-event sections may reference parent festival time slots (e.g. for build-up > shifts). The `time_slot_id` validation accepts time slots from the sub-event itself or > its parent festival. ## Shifts - `GET /events/{event}/sections/{section}/shifts` - `POST /events/{event}/sections/{section}/shifts` - `PUT /events/{event}/sections/{section}/shifts/{shift}` - `DELETE /events/{event}/sections/{section}/shifts/{shift}` - `POST /events/{event}/sections/{section}/shifts/{shift}/assign` - `POST /events/{event}/sections/{section}/shifts/{shift}/claim` > **Festival context:** When managing shifts on a `cross_event` section, the `{event}` > in the URL must be the parent festival's ID (matching `section.event_id`), not the > sub-event's ID. ## Persons - `GET /events/{event}/persons` - `POST /events/{event}/persons` - `GET /events/{event}/persons/{person}` - `PUT /events/{event}/persons/{person}` - `POST /events/{event}/persons/{person}/approve` - `DELETE /events/{event}/persons/{person}` ## Crowd Lists - `GET /events/{event}/crowd-lists` - `POST /events/{event}/crowd-lists` - `PUT /events/{event}/crowd-lists/{list}` - `DELETE /events/{event}/crowd-lists/{list}` - `POST /events/{event}/crowd-lists/{list}/persons` - `DELETE /events/{event}/crowd-lists/{list}/persons/{person}` ## Locations - `GET /events/{event}/locations` - `POST /events/{event}/locations` - `PUT /events/{event}/locations/{location}` - `DELETE /events/{event}/locations/{location}` ## Person Tags (Organisation Settings) - `GET /organisations/{org}/person-tags` — list active tags (ordered) - `POST /organisations/{org}/person-tags` — create tag - `PUT /organisations/{org}/person-tags/{tag}` — update tag - `DELETE /organisations/{org}/person-tags/{tag}` — deactivate tag (soft: sets `is_active = false`) - `GET /organisations/{org}/person-tag-categories` — distinct categories for autocomplete ## User Tag Assignments - `GET /organisations/{org}/users/{user}/tags` — list all tags for user in organisation - `POST /organisations/{org}/users/{user}/tags` — assign a tag - `DELETE /organisations/{org}/users/{user}/tags/{tagAssignment}` — remove assignment - `PUT /organisations/{org}/users/{user}/tags/sync` — sync tags by source > **Sync endpoint:** Replaces tags of the specified `source` only. > Body: `{ "tag_ids": ["ulid1", "ulid2"], "source": "self_reported" }` > Removes `self_reported` tags not in the list, adds new ones, leaves `organiser_assigned` untouched (and vice versa). ### Person list tag filtering - `GET /events/{event}/persons?tag={person_tag_id}` — filter persons by single tag - `GET /events/{event}/persons?tags=ulid1,ulid2` — filter persons by multiple tags (AND logic: must have all) _(Extend this contract per module as endpoints are implemented.)_