Trainees can book a training session or a conversation with their coach from the app. Slots are generated on request from the coach’s availability windows and existing calendar events. Nothing is pre-materialized. Mount: /v1/trainee/calendar, router traineeCalendarRouter in src/modules/trainee-calendar/trainee-calendar.routes.ts. Lane: trainee. Each route uses authenticateTrainee only, without the app access guard. Preview tokens can call the two GET routes. See Authentication.

Rules that apply to every endpoint

  • Feature switch. When the studio’s settings.calendarEnabled is explicitly false, every endpoint returns 403 calendar is disabled for this studio. The same flag is exposed to the app as studio.calendarEnabled on sign-in and me.
  • Which coach. Slots are for the trainee’s assigned coach (Client.coachId). A trainee with no assigned coach gets the studio’s oldest active HEAD_COACH, or failing that the oldest active coach.
  • Time zone. Availability windows are wall-clock minutes of the day. They are converted to UTC instants in the studio’s time zone (Studio.timezone, default Asia/Jerusalem). The trainee’s own zone is not used here.
  • Bookable types. TRAINING and CONVERSATION (BOOKABLE_SLOT_TYPES).

Endpoints

GET /v1/trainee/calendar/slots

Lists the free slots in a time range. Auth: trainee token.
string
required
ISO date or date-time. Start of the range.
string
required
ISO date or date-time. Must be after from.
string
TRAINING or CONVERSATION. Omit for both.
How slots are generated (generateSlots):
  1. The coach’s active AvailabilityWindow rows are loaded. A window is either RECURRING on a weekday, or bound to one date. Each has startMinute, endMinute, durationMin and a slotType.
  2. For each day in the range, each matching window is cut into back-to-back slots of durationMin. The walk covers at most 31 days from from (MAX_SLOT_RANGE_DAYS), whatever to says.
  3. A slot is kept when it starts inside the range, starts in the future, and does not overlap any calendar event of that coach whose status is not CANCELLED.
Slots are sorted by start time. Response: 200.
When the studio has no active coach at all, items is empty. Errors:

POST /v1/trainee/calendar/book

Books one slot. Auth: trainee token. Refused for preview tokens.
string
required
The coach from the slot.
string
required
TRAINING or CONVERSATION.
string
required
The slot’s startsAt, exactly as returned.
string
required
The slot’s endsAt, exactly as returned. Must be after startsAt.
The server never trusts the times sent. It regenerates the slots for that coach, type and window and requires one whose start and end match to the millisecond. It then checks once more for an overlapping event before inserting. A trainee with an assigned coach can only book that coach. The booking is a CalendarEvent with title set to the trainee’s name, type set to the slot type and status CONFIRMED. The module sends no notification and writes no Google Calendar event. The availability check and the insert are separate queries, not one transaction. Two trainees booking the same slot at the same instant are not guarded by a database constraint. Response: 201. The created event.
Errors:

GET /v1/trainee/calendar/meetings

Lists every calendar event linked to the trainee, past and future, including cancelled ones. Auth: trainee token. No parameters. Ordered by startsAt, newest first. This endpoint does not check the calendar feature switch. Response: 200.
Events a coach created for the trainee from the web calendar appear here too, with whatever type the coach chose. studio.timezone is included so the app can show times in the studio’s zone. Errors: none beyond the lane errors.

POST /v1/trainee/calendar/meetings/:id/cancel

Cancels one of the trainee’s upcoming meetings. Auth: trainee token. Refused for preview tokens.
string
required
The calendar event id.
Sets the event’s status to CANCELLED. The slot becomes bookable again, because cancelled events are ignored when slots are generated. Cancelling an already cancelled meeting returns it unchanged. Like the meetings list, this endpoint does not check the calendar feature switch. Response: 200. The event row, same shape as a booking, with status CANCELLED. Errors: