Files
crewli/dev-docs/API.md

6.7 KiB

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.)