{"openapi":"3.0.0","info":{"title":"Medley API","version":"2.7.98","description":"Medley REST API documentation"},"servers":[{"url":"https://api.production.yourmedley.com","description":"Production server"},{"url":"https://api.staging.yourmedley.com","description":"Staging server"},{"url":"http://localhost:3000","description":"Development server"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"schemas":{"LoginRequest":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email","description":"User's email address"},"password":{"type":"string","format":"password","description":"User's password"},"remember_me":{"type":"boolean","description":"Whether to extend session duration"}}},"AuthTokens":{"type":"object","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"},"expiresIn":{"type":"number"}}},"UserProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"role":{"type":"string"}}},"ActiveSessionParticipant":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"initials":{"type":"string"},"is_connected":{"type":"boolean","description":"Presence stub — full realtime presence ships with MED-804 (always false in this story)."}}},"ActiveSessionSlideDeck":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"converted_pdf_s3_key":{"type":"string","nullable":true},"processing_state":{"type":"string","enum":["pending","processing","ready","failed"]},"original_filename":{"type":"string"}}},"ActiveSessionState":{"type":"object","description":"Composite payload for the Active Session screen (one fetch per page load).","properties":{"session_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer","nullable":true},"status":{"type":"string","enum":["scheduled","in_progress","completed","cancelled","rescheduled"]},"duration_minutes":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"actual_start":{"type":"string","format":"date-time","nullable":true},"actual_end":{"type":"string","format":"date-time","nullable":true},"group_id":{"type":"string","format":"uuid","nullable":true},"group_index":{"type":"integer","nullable":true},"group_name":{"type":"string","nullable":true},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"organization_name":{"type":"string","nullable":true},"meeting_link":{"type":"string","nullable":true,"description":"MED-1112 — resolved meeting link (Join video call), null for in-person"},"modality_id":{"type":"string","format":"uuid","nullable":true},"coaching_sub_type":{"type":"string","enum":["coach_led","peer_led"],"nullable":true},"ai_features_enabled":{"type":"boolean","description":"AC7 gating signal — frontend renders the AI disclosure modal when true."},"sections":{"type":"array","description":"MED-976 (E1.3h) — authored sections from the e1.3d/e/f model\n(session_agenda_sections + visuals + leader_prompts). Replaces\nthe legacy parsed_outline from MED-784. Each item carries\nsection_name, duration_minutes, sort_order, participant_prompt,\nleader_prompts[] (intro_text + discussion_prompt HTML), and\nvisuals[] (slide_page associations).\n","items":{"type":"object"}},"slide_deck":{"$ref":"#/components/schemas/ActiveSessionSlideDeck"},"leader_notes":{"type":"array","items":{"type":"object"}},"current_section_index":{"type":"integer","nullable":true},"current_section_entered_at":{"type":"string","format":"date-time","nullable":true},"section_time_seconds":{"type":"object","additionalProperties":{"type":"integer"},"description":"Cumulative seconds per section, keyed by section index as string (AC5)."},"coach_session_notes_html":{"type":"string"},"coach_session_notes_updated_at":{"type":"string","format":"date-time","nullable":true},"coach_present":{"type":"boolean","description":"MED-952 — true when any assigned coach has been seen within the\npresence freshness window. Drives the participant \"coach stepped\naway\" notice (false ⇒ shown). On the coach's own view this is\neffectively always true while polling.\n"},"participants":{"type":"array","items":{"$ref":"#/components/schemas/ActiveSessionParticipant"}}}},"Coach":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"bio":{"type":"string"},"expertise_areas":{"type":"array","items":{"type":"string"}},"coach_type":{"type":"string","enum":["medley","partner"],"description":"Whether coach is a Medley coach or partner coach"},"partner_id":{"type":"string","format":"uuid","nullable":true,"description":"Organization ID for partner coaches"},"partner_name":{"type":"string","nullable":true,"description":"Organization name (computed from partner_id)"},"status":{"type":"string","enum":["active","inactive"],"description":"Derived status (Active when has approved modalities AND not manually inactive)"},"location":{"type":"string"},"home_base_timezone":{"type":"string"},"coaching_specializations":{"type":"string"},"hourly_rate_cents":{"type":"integer"},"modality_proficiencies":{"type":"array","items":{"type":"string","enum":["group_coaching","one_on_one","workshop"]},"description":"Modalities the coach is proficient in"},"modality_approvals":{"type":"array","items":{"type":"string","enum":["group_coaching","one_on_one","workshop"]},"description":"Modalities the coach is Medley Approved for (admin-only, not visible to coaches)"},"zoom_link":{"type":"string","nullable":true,"description":"Coach's Zoom personal meeting link"},"zoom_passcode":{"type":"string","nullable":true,"description":"Passcode of the Zoom link. Shown in invites of cohorts that turn on show_zoom_dial_in (MED-1411)"},"teams_link":{"type":"string","nullable":true,"description":"Coach's Microsoft Teams meeting link"},"google_meet_link":{"type":"string","nullable":true,"description":"Coach's Google Meet link"},"created_at":{"type":"string","format":"date-time"}}},"PostSessionParticipant":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"attendance_id":{"type":"string","format":"uuid","nullable":true},"current_status":{"type":"string","enum":["pending","present","absent","excused","late","attended","partial"]},"current_absent_reason":{"type":"string","nullable":true,"enum":["business_conflict","personal_conflict","no_notice","technical_issues","other"]},"current_notes":{"type":"string","nullable":true},"current_coach_notes":{"type":"string","nullable":true,"description":"MED-979 — coach-authored per-participant free-text note from the post-session screen."},"rsvp_status":{"type":"string"}}},"PostSessionEngagementRating":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"participant_id":{"type":"string","format":"uuid"},"coach_id":{"type":"string","format":"uuid"},"rating":{"type":"integer","minimum":1,"maximum":5},"participant_name":{"type":"string"},"coach_name":{"type":"string"}}},"PostSessionCommitment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"participant_user_id":{"type":"string","format":"uuid"},"coach_user_id":{"type":"string","format":"uuid","nullable":true,"description":"NULL for participant-authored rows (MED-1216)."},"authored_by_user_id":{"type":"string","format":"uuid","description":"Actual author. Equals participant_user_id for participant-authored rows (coach cannot overwrite these), or the coach's id for coach fill-ins (MED-805/MED-1216)."},"commitment_text":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"PostSessionBundle":{"type":"object","description":"Composite payload for the post-session screen. One fetch per page\nload. AC4 Session Notes carry-forward lives on\n`session.coach_session_notes_html` / `_plain` / `_updated_at`\n(populated by MED-792).\n","properties":{"session":{"type":"object","description":"SessionWithDetails — includes group_name, cohort_name, coach_session_notes_*, status, scheduled/actual timestamps."},"participants":{"type":"array","items":{"$ref":"#/components/schemas/PostSessionParticipant"}},"engagement_ratings":{"type":"array","items":{"$ref":"#/components/schemas/PostSessionEngagementRating"}},"feedback":{"type":"object","nullable":true,"description":"Coach's own feedback row (responses JSONB + submitted_at). Null if not yet started."},"commitments":{"type":"array","items":{"$ref":"#/components/schemas/PostSessionCommitment"}},"ai_features_enabled":{"type":"boolean","description":"MED-971 AC1 — true iff the cohort's group_coaching modality has\n`ai_features_enabled=true`. Drives whether the Send Recap Email\nmodal seeds from the AI envelope (true) or the default template\n(false), with the template as a fail_open fallback when AI is on\nbut the draft is unavailable.\n"},"next_session_date":{"type":"string","format":"date-time","nullable":true,"description":"MED-971 AC2 — ISO 8601 scheduled_start of the next group session\nfor the cohort (session_number + 1). Null when no future session\nis scheduled; the `{{next_session_date}}` merge tag resolves to\nempty string in that case.\n"},"recap_email_default_template":{"type":"string","nullable":true,"description":"MED-971 AC1/AC7 — admin-managed default body HTML for the Send\nRecap Email modal, loaded from `system_configs.recap_email_default_body`.\nNull when no row exists; body stays empty in that case.\n"}}},"CoachLeaderNotesScope":{"type":"string","enum":["all_groups","group"],"description":"Whether a leader-notes row applies to all the coach's groups (shared) or a specific group overlay."},"CoachSessionLeaderNote":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"coach_user_id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"block_key":{"type":"string","description":"Stable identifier of the agenda step the note belongs to (v1 form `section_idx:block_idx`)."},"scope":{"$ref":"#/components/schemas/CoachLeaderNotesScope"},"group_id":{"type":"string","format":"uuid","nullable":true,"description":"Required when scope=group; must be null when scope=all_groups."},"content_html":{"type":"string","description":"Sanitized HTML from the RichTextEditor."},"content_plain":{"type":"string","description":"Plain-text rendering used for AC13 prep-sheet PDF."},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CoachSessionPrep":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"coach_user_id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"marked_reviewed_at":{"type":"string","format":"date-time","nullable":true},"rsvp_status":{"type":"string","enum":["pending","accepted","declined","tentative"]},"rsvp_responded_at":{"type":"string","format":"date-time","nullable":true},"rsvp_decline_reason":{"type":"string","nullable":true},"first_prep_completed_at":{"type":"string","format":"date-time","nullable":true},"checklist_state":{"type":"object","description":"Per-item completion state for the \"Before you start\" checklist.","additionalProperties":{"type":"boolean"}}}},"CoachPrepGroupContext":{"type":"object","properties":{"group_id":{"type":"string","format":"uuid"},"group_index":{"type":"integer","nullable":true},"group_name":{"type":"string"},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"status_label":{"type":"string","enum":["scheduled","ready_to_start"]},"is_live":{"type":"boolean","description":"MED-952 — true when this group's session is in_progress; prep UI shows \"Rejoin Session\"."},"participant_count":{"type":"integer"},"rsvp_accepted_count":{"type":"integer"},"prework_submitted_count":{"type":"integer"},"prework_total_count":{"type":"integer"},"has_missing_rsvps":{"type":"boolean"},"has_missing_prework":{"type":"boolean"}}},"CoachPrepPayload":{"type":"object","properties":{"session":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"duration_minutes":{"type":"integer","nullable":true},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"organization_name":{"type":"string","nullable":true},"is_live":{"type":"boolean"},"prep_unlocked":{"type":"boolean"}}},"agenda":{"description":"MED-976 (E1.3h) — discriminated union. When `mode='authored'`\nthe response carries the authored sections from the e1.3d/e/f\nmodel (live_session_delivery flag ON). When `mode='parsed_outline'`\nit carries the legacy parsed_outline (flag OFF). UI branches on\n`mode` to pick the renderer. When mode='authored' and\n`authored.is_incomplete=true` the FE shows a \"not yet ready\"\nAlert instead of the agenda.\n","oneOf":[{"type":"object","properties":{"mode":{"type":"string","enum":["parsed_outline"]},"parsed_outline":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/SessionContentSection"}},"unavailable_reason":{"type":"string","enum":["no_content_uploaded","still_processing","failed"]}}},{"type":"object","properties":{"mode":{"type":"string","enum":["authored"]},"authored":{"type":"object","properties":{"is_incomplete":{"type":"boolean"},"incomplete_reasons":{"type":"object","properties":{"sections_missing_required_fields":{"type":"boolean"}}},"has_reflection_prompts":{"type":"boolean"},"sections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"section_name":{"type":"string","nullable":true},"duration_minutes":{"type":"integer","nullable":true},"sort_order":{"type":"integer"},"participant_prompt":{"type":"string","nullable":true},"leader_prompts":{"type":"array","items":{"type":"object","properties":{"intro_text":{"type":"string","nullable":true},"discussion_prompt":{"type":"string","nullable":true},"sort_order":{"type":"integer"}}}},"visuals":{"type":"array","items":{"type":"object","properties":{"slide_page":{"type":"integer"},"sort_order":{"type":"integer"}}}}}}}}}}}]},"prep":{"$ref":"#/components/schemas/CoachSessionPrep"},"leader_notes":{"type":"array","items":{"$ref":"#/components/schemas/CoachSessionLeaderNote"}},"groups":{"type":"array","items":{"$ref":"#/components/schemas/CoachPrepGroupContext"}},"ai_features_enabled":{"type":"boolean"},"is_first_time_coach":{"type":"boolean"}}},"CohortEvent":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"event_type":{"type":"string","enum":["coach_immersion","participant_orientation","coach_huddle","cohort_wrap_up"]},"event_name":{"type":"string"},"event_number":{"type":"integer"},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"timezone":{"type":"string"},"video_link":{"type":"string","nullable":true},"status":{"type":"string","enum":["not_scheduled","ready","scheduled","awaiting_attendance","completed"]},"invites_sent_at":{"type":"string","format":"date-time","nullable":true},"attendance_marked":{"type":"boolean"}}},"CohortEventRsvpSummary":{"type":"object","properties":{"total":{"type":"integer"},"accepted":{"type":"integer"},"declined":{"type":"integer"},"pending":{"type":"integer"},"tentative":{"type":"integer"}}},"MatchingSession":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"external_session_id":{"type":"string","nullable":true,"description":"matching-server's id; populated after leg (C). Not unique under the 1:N model."},"opened_by_admin_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["submitting","ready","in_progress","awaiting_finalize","completed","failed","expired"]},"error_message":{"type":"string","nullable":true},"time_slot_id_to_identifier":{"type":"object","nullable":true,"additionalProperties":{"type":"string"}},"data_as_of_at":{"type":"string","format":"date-time","nullable":true},"received_at":{"type":"string","format":"date-time","nullable":true},"rep_name":{"type":"string","nullable":true},"user_in_valid":{"type":"boolean","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CreateMatchingSessionResponse":{"type":"object","required":["session_id","matching_url"],"properties":{"session_id":{"type":"string","format":"uuid","description":"SGOS's matching_sessions.id for the new row owned by the calling admin"},"matching_url":{"type":"string","format":"uri","description":"matching-server URL with a single-use navigation token (leg D entrypoint)"}}},"ResumeMatchingSessionResponse":{"type":"object","required":["matching_url","status"],"properties":{"matching_url":{"type":"string","format":"uri","description":"matching-server URL with a freshly minted single-use navigation token"},"status":{"type":"string","enum":["in_progress"],"description":"The row's status after a successful resume. Always `in_progress` per the §7 state\nmachine. Returned so the SPA can update its sessions list immediately without a\nfollow-up GET.\n"}}},"MatchingSessionCallbackBody":{"type":"object","required":["session_id","cohort_id","timestamp","rep_name","user_in_valid","matching"],"properties":{"session_id":{"type":"string","format":"uuid","description":"SGOS's matching_sessions.id (must match the URL :id)"},"cohort_id":{"type":"string","format":"uuid","description":"Echoed from leg (C) for log correlation only -- not authoritative"},"timestamp":{"type":"string","format":"date-time","description":"As-of moment for the matched data; stored as data_as_of_at"},"rep_name":{"type":"string","description":"matching-server's internal solver run id"},"user_in_valid":{"type":"boolean","description":"True iff the admin force-saved despite a constraint violation"},"matching":{"type":"array","description":"Ordered list of matched groups. The 0-based array index is the\nstable `group_index` used in SGOS's upsert key (cohort_id, group_index).\n","items":{"type":"object","required":["group"],"properties":{"group":{"type":"array","description":"Participant user ids (UUIDs).","items":{"type":"string","format":"uuid"}},"slot":{"type":"string","description":"Short slot identifier (e.g. \"DS13-TS1\"). Reverse-looked-up\nvia the matching_sessions row's time_slot_id_to_identifier\nJSONB. Optional -- groups without a slot do NOT get\nsessions auto-generated.\n"},"coaching":{"type":"array","description":"Coach user ids. Optional / nullable. Single-coach product\nin v1 -- only `coaching[0]` is honored, the rest are ignored.\n","items":{"type":"string","format":"uuid"}},"commentary":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}}}}}},"MatchingSessionCallbackResponse":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["accepted"]}}},"Cohort":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"partner_id":{"type":"string","format":"uuid"},"program_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"short_name":{"type":"string"},"code":{"type":"string"},"description":{"type":"string"},"status":{"type":"string","enum":["setup","ready","live","paused","completed","cancelled"]},"cohort_type":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"preferred_days":{"type":"array","items":{"type":"string"}},"preferred_times":{"type":"array","items":{"type":"string"}},"participant_capacity":{"type":"integer"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"CohortModality":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"modality_type":{"type":"string"},"session_count":{"type":"integer"},"session_frequency":{"type":"string"},"configuration":{"type":"object"}}},"CohortJobTitleAssignment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"job_title_id":{"type":"string","format":"uuid"},"job_title_name":{"type":"string","description":"Name of the job title"},"job_title_description":{"type":"string","nullable":true,"description":"Description of the job title"},"user_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of the assigned user (null if unassigned)"},"user_name":{"type":"string","nullable":true,"description":"Full name of the assigned user"},"user_email":{"type":"string","nullable":true,"description":"Email of the assigned user"}}},"CohortEmailSettings":{"type":"object","properties":{"master_enabled":{"type":"boolean","description":"Master kill switch for all cohort emails"},"personas":{"type":"object","properties":{"participants":{"$ref":"#/components/schemas/PersonaEmailSettings"},"coaches":{"$ref":"#/components/schemas/PersonaEmailSettings"},"partners":{"$ref":"#/components/schemas/PersonaEmailSettings"}}}}},"PersonaEmailSettings":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Persona-level toggle"},"emails":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Individual email type toggles"}}},"DataLibraryParticipant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"},"status":{"type":"string","enum":["pending","active","withdrawn"]},"cohort_name":{"type":"string"},"group_name":{"type":"string","nullable":true},"coach_name":{"type":"string","nullable":true},"attendance_percentage":{"type":"integer","nullable":true},"has_open_flag":{"type":"boolean"},"flag_severity":{"type":"string","nullable":true,"enum":["high","medium","low"]}}},"DataLibraryCohortGroup":{"type":"object","properties":{"partner_id":{"type":"string"},"partner_name":{"type":"string"},"program_name":{"type":"string"},"cohorts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string"},"participant_count":{"type":"integer"}}}}}},"ExportRequest":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"entity_type":{"type":"string","enum":["participants","coaches","partners","cohorts","groups","sessions","resources","users","survey_responses","attendance"]},"context_type":{"type":"string","enum":["global","cohort","partner"]},"context_id":{"type":"string","format":"uuid","nullable":true},"filter_context":{"type":"object","nullable":true,"description":"Filters/search applied when export was requested"},"selected_columns":{"type":"array","items":{"type":"string"}},"include_all_columns":{"type":"boolean"},"match_current_view":{"type":"boolean"},"format":{"type":"string","enum":["csv","xlsx"]},"requested_by":{"type":"string","format":"uuid"},"requested_at":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["pending","processing","completed","failed"]},"retry_count":{"type":"integer"},"started_at":{"type":"string","format":"date-time","nullable":true},"completed_at":{"type":"string","format":"date-time","nullable":true},"record_count":{"type":"integer","nullable":true},"file_size":{"type":"integer","nullable":true},"expires_at":{"type":"string","format":"date-time","nullable":true},"error_message":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"ExportRequestWithDetails":{"allOf":[{"$ref":"#/components/schemas/ExportRequest"},{"type":"object","properties":{"requested_by_name":{"type":"string"},"requested_by_email":{"type":"string"},"context_name":{"type":"string","nullable":true}}}]},"CreateExportRequest":{"type":"object","required":["entity_type"],"properties":{"entity_type":{"type":"string","enum":["participants","coaches","partners","cohorts","groups","sessions","resources","users","survey_responses","attendance"],"description":"Type of data to export"},"context_type":{"type":"string","enum":["global","cohort","partner"],"description":"Scope of the export"},"context_id":{"type":"string","format":"uuid","description":"ID of cohort or partner if scoped"},"filter_context":{"type":"object","description":"Current filter/search state to apply to export"},"selected_columns":{"type":"array","items":{"type":"string"},"description":"Column keys to include in export"},"include_all_columns":{"type":"boolean","description":"If true, include all available columns"},"match_current_view":{"type":"boolean","description":"If true, match currently visible table columns"},"format":{"type":"string","enum":["csv","xlsx"],"default":"csv"},"populate_dnm_groupings":{"type":"boolean","description":"If true, calculate and populate DNM groupings in the export"}}},"ExportDownloadResponse":{"type":"object","properties":{"download_url":{"type":"string","description":"Signed CloudFront URL for download"},"filename":{"type":"string","description":"Suggested filename for download"},"file_size":{"type":"integer","description":"File size in bytes"},"expires_at":{"type":"string","format":"date-time","description":"When the download link expires"}}},"ExportColumnDefinition":{"type":"object","properties":{"key":{"type":"string","description":"Column identifier"},"label":{"type":"string","description":"Display label"},"category":{"type":"string","enum":["basic","contact","work","cohort","intake","profile","status","capacity","dates","config","counts","access"]},"field":{"type":"string","description":"Data field path"},"formatter":{"type":"string","enum":["date","datetime","boolean","currency","number","array"]}}},"AvailableColumnsResponse":{"type":"object","properties":{"entity_type":{"type":"string"},"columns":{"type":"array","items":{"$ref":"#/components/schemas/ExportColumnDefinition"}},"categories":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"}}}}}},"FeedbackSubmission":{"type":"object","required":["feedback_type","title","description","page_url","browser_os","app_version"],"properties":{"feedback_type":{"type":"string","enum":["bug","feature_request"],"description":"Type of feedback"},"title":{"type":"string","maxLength":255,"description":"Brief title for the feedback"},"description":{"type":"string","maxLength":5000,"description":"Detailed description"},"is_blocking":{"type":"string","enum":["true","false"],"description":"Whether this is a blocking bug (sent as string in FormData)"},"page_url":{"type":"string","description":"URL of the page where the issue was encountered"},"browser_os":{"type":"string","description":"Browser and OS information"},"app_version":{"type":"string","description":"Application version"},"build_number":{"type":"string","description":"Build number (optional)"},"environment":{"type":"string","description":"Environment (defaults to NODE_ENV)"},"attachments":{"type":"array","items":{"type":"string","format":"binary"},"description":"File attachments (max 5, 10MB each)"}}},"FeedbackResponse":{"type":"object","properties":{"success":{"type":"boolean"},"attachmentCount":{"type":"integer"}}},"FeedbackSurveyConfig":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"survey_type":{"type":"string","enum":["post_session","midpoint","endpoint"]},"paperform_id":{"type":"string","description":"Paperform form ID"},"email_subject":{"type":"string","description":"Email subject template with merge tags"},"email_template":{"type":"string","description":"Email body template with merge tags"},"survey_deadline":{"type":"string","format":"date","nullable":true},"schedule_status":{"type":"string","enum":["configured","scheduled","sent","cancelled"]},"scheduled_for":{"type":"string","format":"date-time","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"configured_by":{"type":"string","format":"uuid","nullable":true},"configured_at":{"type":"string","format":"date-time"}}},"SurveyScheduleTimes":{"type":"object","properties":{"midpoint":{"type":"object","nullable":true,"properties":{"session_number":{"type":"integer"},"session_end":{"type":"string","format":"date-time"},"scheduled_for":{"type":"string","format":"date-time"}}},"endpoint":{"type":"object","nullable":true,"properties":{"session_number":{"type":"integer"},"session_end":{"type":"string","format":"date-time"},"scheduled_for":{"type":"string","format":"date-time"}}},"total_sessions":{"type":"integer"}}},"Framework":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier"},"name":{"type":"string","description":"Canonical spelling of the framework or acronym"},"description":{"type":"string","nullable":true,"description":"Optional context to help the AI recognize and spell it correctly"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"FrameworkInput":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Canonical spelling of the framework or acronym (required)"},"description":{"type":"string","nullable":true,"description":"Optional context for the AI"}}},"Group":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"max_size":{"type":"integer"},"coach_id":{"type":"string","format":"uuid"},"is_active":{"type":"boolean"},"member_count":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"GroupMember":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"title":{"type":"string"},"joined_at":{"type":"string","format":"date-time"}}},"GroupWithMembers":{"allOf":[{"$ref":"#/components/schemas/Group"},{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/GroupMember"}}}}]},"GroupImportPreviewRequest":{"type":"object","required":["csvData","emailColumnIndex","groupColumnIndex"],"properties":{"csvData":{"type":"array","items":{"type":"array","items":{"type":"string"}}},"emailColumnIndex":{"type":"integer"},"groupColumnIndex":{"type":"integer"}}},"GroupImportPreviewResponse":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"groupNumber":{"type":"integer"},"name":{"type":"string"},"participants":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"title":{"type":"string"},"matched":{"type":"boolean"},"alreadyAssigned":{"type":"boolean"}}}}}}},"unassigned":{"type":"array","items":{"type":"object"}},"summary":{"type":"object","properties":{"totalGroups":{"type":"integer"},"totalMatched":{"type":"integer"},"totalUnmatched":{"type":"integer"},"totalAlreadyAssigned":{"type":"integer"}}}}},"GroupImportExecuteRequest":{"type":"object","required":["groups"],"properties":{"groups":{"type":"array","items":{"type":"object","required":["name","memberEmails"],"properties":{"name":{"type":"string"},"memberEmails":{"type":"array","items":{"type":"string"}}}}}}},"GroupImportResult":{"type":"object","properties":{"groupsCreated":{"type":"integer"},"participantsAssigned":{"type":"integer"},"groups":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"memberCount":{"type":"integer"}}}},"unassignedEmails":{"type":"array","items":{"type":"string"}}}},"JobTitle":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier"},"name":{"type":"string","description":"Job title name","example":"Project Lead"},"description":{"type":"string","nullable":true,"description":"Description of the role","example":"Overall project coordination and oversight for the cohort"},"display_order":{"type":"integer","description":"Display order for UI","example":1},"is_active":{"type":"boolean","description":"Whether the job title is active"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"PaperformDeletionDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"form_type":{"type":"string"},"participant_name":{"type":"string","nullable":true},"submitted_at":{"type":"string","format":"date-time"},"deleted_from_paperform_at":{"type":"string","format":"date-time"}}},"PaperformDeletionsBannerState":{"type":"object","properties":{"shouldShow":{"type":"boolean"},"totalCount":{"type":"integer"},"countsByFormType":{"type":"array","items":{"type":"object","properties":{"form_type":{"type":"string"},"count":{"type":"integer"}}}},"deletions":{"type":"array","items":{"$ref":"#/components/schemas/PaperformDeletionDetail"}},"newestDeletionAt":{"type":"string","format":"date-time","nullable":true},"windowDays":{"type":"integer"}}},"ParticipantActiveSessionLeader":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid","nullable":true},"display_name":{"type":"string"},"first_name":{"type":"string"},"role_label":{"type":"string","enum":["COACH","PEER LEADER"],"nullable":true}}},"ParticipantActiveSessionState":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer","nullable":true},"status":{"type":"string","enum":["scheduled","in_progress","completed","cancelled","rescheduled"]},"duration_minutes":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"meeting_link":{"type":"string","nullable":true,"description":"MED-1112 — resolved meeting link (Join video call), null for in-person"},"group_id":{"type":"string","format":"uuid","nullable":true},"group_name":{"type":"string","nullable":true},"modality_id":{"type":"string","format":"uuid","nullable":true},"coaching_sub_type":{"type":"string","enum":["coach_led","peer_led"],"nullable":true},"ai_features_enabled":{"type":"boolean"},"leader":{"$ref":"#/components/schemas/ParticipantActiveSessionLeader"},"sections":{"type":"array","description":"MED-976 (E1.3h) — authored sections (slim, participant variant\n— NO leader_prompts; AC-PL3 enforced at the type layer).\nEach item carries section_name, duration_minutes, sort_order,\nparticipant_prompt, and visuals[] (slide_page associations).\nReplaces the legacy parsed_outline from MED-784/MED-805.\n","items":{"type":"object"}},"slide_deck":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"converted_pdf_s3_key":{"type":"string","nullable":true},"processing_state":{"type":"string","enum":["pending","processing","ready","failed"]},"original_filename":{"type":"string"}}},"current_section_index":{"type":"integer","nullable":true},"commitment_section_active":{"type":"boolean","description":"Decision #2 — true when the active section's title matches\n`/commitment/i`. Drives the AC #12 commitment area visibility.\n"},"coach_present":{"type":"boolean","description":"MED-952 AC8/AC9 — false when no assigned coach is currently present\non the active session screen. Drives the \"coach has temporarily\nstepped away\" notice; flips back to true within one poll when a\ncoach rejoins.\n"}}},"ParticipantNotesResponse":{"type":"object","properties":{"notes_html":{"type":"string","description":"Tiptap-serialized HTML. Empty when no notes saved."},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"ParticipantNotesUpsertRequest":{"type":"object","required":["notes_html"],"properties":{"notes_html":{"type":"string","description":"Tiptap-serialized HTML. Server sanitizes before persisting. Empty string => soft-delete."}}},"ParticipantCommitmentResponse":{"type":"object","properties":{"commitment_text":{"type":"string"},"updated_at":{"type":"string","format":"date-time","nullable":true},"authored_by_coach":{"type":"boolean","description":"MED-1216 / AC8 — true when the coach entered this and the participant has not yet saved their own; drives the 'noted by your coach' review state. Always false after a self-save."}}},"ParticipantCommitmentUpsertRequest":{"type":"object","required":["commitment_text"],"properties":{"commitment_text":{"type":"string","description":"Plain text. Must be non-empty (MED-1216 / AC1) — whitespace-only is rejected. Self-authored: saving transfers ownership to the participant and locks the coach out."}}},"ParticipantFeedbackPayload":{"type":"object","properties":{"satisfaction_rating":{"type":"integer","minimum":1,"maximum":5,"nullable":true,"description":"1 = Not satisfied, 5 = Very satisfied (higher = more satisfied, matching CoachSatisfactionRating). Nullable while draft."},"what_landed":{"type":"string"},"what_still_alive":{"type":"string"},"what_for_next":{"type":"string"},"status":{"type":"string","enum":["draft","submitted"]},"submitted_at":{"type":"string","format":"date-time","nullable":true},"updated_at":{"type":"string","format":"date-time","nullable":true}}},"ParticipantPostSessionStatePayload":{"type":"object","properties":{"notes_reviewed_at":{"type":"string","format":"date-time","nullable":true},"completed_at":{"type":"string","format":"date-time","nullable":true}}},"PostSessionSidebarSessionSummary":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"duration_minutes":{"type":"integer","nullable":true,"description":"Actual duration (end - start) when both timestamps are set; else scheduled."},"scheduled_duration_minutes":{"type":"integer","nullable":true},"session_number":{"type":"integer","nullable":true},"total_sessions":{"type":"integer","nullable":true},"leader_first_name":{"type":"string"},"leader_display_name":{"type":"string"},"feedback_status":{"type":"string","enum":["not_started","in_progress","submitted"]}}},"PostSessionSidebarNextSession":{"type":"object","nullable":true,"properties":{"session_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"group_name":{"type":"string","nullable":true},"participant_role_label":{"type":"string","enum":["PARTICIPANT"]}}},"PostSessionSidebarLeaderVisibility":{"type":"object","properties":{"leader_first_name":{"type":"string"},"commitment_tracking_enabled":{"type":"boolean"}}},"ParticipantPostSessionSummary":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"program_name":{"type":"string"},"organization_name":{"type":"string"},"session_title":{"type":"string"},"is_peer_led":{"type":"boolean","description":"MED-1199 — true when the cohort's group_coaching modality is peer_led. The FE hides the coach-oriented \"What your coach sees\" card for peer-led groups."},"session_status":{"type":"string"},"commitment_tracking_enabled":{"type":"boolean"},"is_done":{"type":"boolean"},"feedback_available":{"type":"boolean","description":"MED-1063 — false when the participant was marked absent; the page hides the editable feedback form. Submitted feedback is still returned (AC5)."},"feedback":{"$ref":"#/components/schemas/ParticipantFeedbackPayload"},"notes_html":{"type":"string"},"commitment_text":{"type":"string"},"post_session_state":{"$ref":"#/components/schemas/ParticipantPostSessionStatePayload"},"sidebar_session_summary":{"$ref":"#/components/schemas/PostSessionSidebarSessionSummary"},"sidebar_next_session":{"$ref":"#/components/schemas/PostSessionSidebarNextSession"},"sidebar_leader_visibility":{"$ref":"#/components/schemas/PostSessionSidebarLeaderVisibility"}}},"UpsertFeedbackDraftRequest":{"type":"object","properties":{"satisfaction_rating":{"type":"integer","minimum":1,"maximum":5,"nullable":true},"what_landed":{"type":"string"},"what_still_alive":{"type":"string"},"what_for_next":{"type":"string"}}},"SubmitFeedbackRequest":{"type":"object","required":["satisfaction_rating"],"properties":{"satisfaction_rating":{"type":"integer","minimum":1,"maximum":5},"what_landed":{"type":"string"},"what_still_alive":{"type":"string"},"what_for_next":{"type":"string"}}},"Participant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"},"status":{"type":"string","enum":["pending","active","withdrawn"]},"personality_type":{"type":"string"},"title":{"type":"string"},"department":{"type":"string"},"location":{"type":"string"},"custom_fields":{"type":"object"},"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"enrolled_at":{"type":"string","format":"date-time"}}},"CSVColumnMapping":{"type":"object","properties":{"csvColumn":{"type":"string"},"targetField":{"type":"string","nullable":true},"isCustomField":{"type":"boolean"},"customFieldName":{"type":"string","nullable":true}}},"Partner":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string"},"primary_contact_name":{"type":"string"},"primary_contact_email":{"type":"string"},"description":{"type":"string"},"website":{"type":"string"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"CreatePartnerRequest":{"type":"object","required":["name","code"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"code":{"type":"string","pattern":"^[A-Za-z]{2,4}$"},"primary_contact_name":{"type":"string"},"primary_contact_email":{"type":"string","format":"email"},"description":{"type":"string"},"website":{"type":"string","format":"uri"}}},"PartnerContact":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"partner_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"job_title":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"PeerLeaderPostSessionBundle":{"type":"object","properties":{"session":{"type":"object"},"participants":{"type":"array","items":{"type":"object"}},"engagement_ratings":{"type":"array","description":"Rows from participant_engagement_ratings (shared coach table, IV2).","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"participant_id":{"type":"string","format":"uuid"},"rating":{"type":"integer","minimum":1,"maximum":5}}}},"feedback":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"responses":{"type":"object","additionalProperties":{"type":"string"}},"submitted_at":{"type":"string","format":"date-time"}}},"commitments":{"type":"array","description":"The peer leader's OWN commitment only (0 or 1 row). MED-1133 (E4.5a) — commitments are private from peers, so this never includes another participant's commitment (privacy enforced at the data layer).","items":{"type":"object"}},"leader_user_id":{"type":"string","format":"uuid","description":"The peer leader's own user id — flags the \"You\" row."},"ai_features_enabled":{"type":"boolean"},"live_session_delivery_enabled":{"type":"boolean","description":"MED-1181 — per-cohort group_coaching LSD gate (always true when a bundle is returned; surfaced for FE defense-in-depth)"},"next_session_date":{"type":"string","format":"date-time","nullable":true},"recap_email_default_template":{"type":"string","nullable":true},"next_session_callout":{"type":"object","nullable":true,"description":"AC14 — set only when this leader leads the next upcoming session.","properties":{"session_id":{"type":"string","format":"uuid"},"session_number":{"type":"integer"},"session_date":{"type":"string","format":"date-time","nullable":true}}}}},"PeerLeaderRecapEmailSendResult":{"type":"object","properties":{"sent":{"type":"integer"},"failed":{"type":"integer"},"skipped_not_in_session":{"type":"integer"}}},"PeerLeaderRsvpRequest":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["confirmed","declined"],"description":"Leader's RSVP decision."}}},"PeerLeaderRsvpResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_leader_rsvp_status":{"type":"string","enum":["unconfirmed","confirmed","declined"]},"session_leader_rsvp_responded_at":{"type":"string","format":"date-time"}}}}},"PeerLeaderPrepStatus":{"type":"string","enum":["unlocked","locked","live","declined"],"description":"`unlocked` happy path · `locked` preceding session not complete (AC3) ·\n`live` session already launched (AC4) · `declined` leader declined RSVP (AC1).\n"},"PeerLeaderRsvpStatus":{"type":"string","enum":["unconfirmed","confirmed","declined"]},"PeerLeaderPrepPayload":{"type":"object","properties":{"session":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string","nullable":true},"session_number":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"duration_minutes":{"type":"integer","nullable":true},"timezone":{"type":"string","nullable":true},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string","nullable":true},"program_name":{"type":"string","nullable":true},"organization_name":{"type":"string","nullable":true},"group_id":{"type":"string","format":"uuid","nullable":true},"group_name":{"type":"string","nullable":true},"status":{"type":"string"},"ai_features_enabled":{"type":"boolean","description":"MED-816/MED-909 — cohort modality AI gate. When false all AI prep surfaces are absent."},"ai_listening_enabled":{"type":"boolean","description":"MED-909 AC4 — current AI Listening state for this session (read-only to the peer leader)."},"ai_listening_locked":{"type":"boolean","description":"MED-909 AC4 — true once within 15 min of scheduled_start; the listening value is frozen."}}},"prep_status":{"$ref":"#/components/schemas/PeerLeaderPrepStatus"},"prep_locked_reason":{"type":"string","nullable":true},"rsvp_status":{"$ref":"#/components/schemas/PeerLeaderRsvpStatus"},"agenda":{"type":"object","nullable":true,"description":"AuthoredAgendaForCoach payload from MED-976. Same shape coach prep uses."},"reflection_status":{"type":"object","nullable":true,"description":"Reserved — currently always null; the FE derives Step 1 status\nfrom the existing reflection endpoint instead.\n"},"prep_state":{"type":"object","properties":{"marked_completed_at":{"type":"string","format":"date-time","nullable":true},"step2_reviewed_at":{"type":"string","format":"date-time","nullable":true},"step3_visited_at":{"type":"string","format":"date-time","nullable":true},"ai_prompt_selections":{"type":"object","description":"MED-909 AC1-AC2 — peer leader's In Use prompt selections JSONB,\nkeyed by agenda block_key. Drives the AI suggestions banner count\nand per-step In Use badges. Empty object when none selected yet.\n","additionalProperties":true}}},"is_first_time_leader":{"type":"boolean"},"cm_name":{"type":"string","nullable":true}}},"PeerLeaderUpdateAiSelectionsRequest":{"type":"object","required":["selections"],"properties":{"selections":{"type":"object","description":"The full ai_prompt_selections document, keyed by agenda block_key.\nEach segment carries optional purpose/speaking/stall_recovery slots\nwith an `in_use` boolean. Replaces the stored document atomically.\n","additionalProperties":true}}},"PeerLeaderStepReviewedRequest":{"type":"object","required":["reviewed"],"properties":{"reviewed":{"type":"boolean","description":"True marks Step 2 reviewed; false clears the marker (toggle)."}}},"PeerLeaderSaveLeaderNoteRequest":{"type":"object","required":["block_key","content_html"],"properties":{"block_key":{"type":"string","description":"Agenda section key in the coach `${sectionIndex}:0` scheme. Must\nmatch so the note also renders in the active-session guide panel\n(MED-1046 AC6).\n","example":"0:0"},"content_html":{"type":"string","description":"Sanitized rich-text HTML. An empty string (or `<p></p>`) soft-deletes\nthe note, mirroring the coach leader-notes convention.\n"}}},"ReflectionStatus":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","COMPLETED"]},"SessionFormat":{"type":"string","enum":["Virtual","In-Person"]},"ReflectionQuestionWithResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"prompt":{"type":"string"},"placeholder_text":{"type":"string","nullable":true},"sort_order":{"type":"integer"},"response_text":{"type":"string","description":"Empty string when not yet answered"},"response_id":{"type":"string","format":"uuid","nullable":true}}},"ReflectionGroupMember":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_initial":{"type":"string"},"is_current_participant":{"type":"boolean"}}},"PreSessionIncompleteReasons":{"type":"object","description":"MED-984 / E3.1c — Per-rule breakdown of why the session content is\nconsidered incomplete. Mirrors the same bar the coach prep surface\nuses (e1.3d AC-MC1). MED-1182 (E1.3f-R1) removed the reflection-prompt\nreason — readiness is agenda-only.\n","properties":{"sections_missing_required_fields":{"type":"boolean"}}},"PreSessionReflectionPayload":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"header":{"type":"object","properties":{"session_title":{"type":"string"},"program_name":{"type":"string","nullable":true},"organization_name":{"type":"string","nullable":true},"duration_minutes":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"timing_badge":{"type":"string"}}},"ai_features_enabled":{"type":"boolean"},"status":{"$ref":"#/components/schemas/ReflectionStatus"},"question_count":{"type":"integer"},"estimated_minutes":{"type":"integer"},"questions":{"type":"array","items":{"$ref":"#/components/schemas/ReflectionQuestionWithResponse"}},"is_incomplete":{"type":"boolean","description":"MED-984 / E3.1c — When true, the page renders a gray\n\"not yet ready\" Alert and the questions/reading_tasks/\nprior_commitment fields are withheld (empty/null). Reflection\nunlocks once the CM completes the session's authored content.\n"},"incomplete_reasons":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/PreSessionIncompleteReasons"},{"type":"null"}]},"pre_session_work_present":{"type":"boolean","description":"MED-1182 (E1.3f-R1) — the \"not blank\" signal. True when the\nsession has any pre-session work (≥1 reflection prompt OR\nreading/tasks OR commitment review enabled). When false and\nis_incomplete is also false, the session is BLANK: the page\nrenders logistics-only and the dashboard withholds the prep CTA.\n"},"reading_tasks":{"type":"string","nullable":true,"description":"MED-984 / E3.1c — Sanitized HTML for the Reading / Tasks\nsection. Null when no Pre-Session Work row exists OR the\nauthored value is empty — the page omits the section.\n"},"session_details":{"type":"object","properties":{"topic_label":{"type":"string","nullable":true},"session_number":{"type":"integer","nullable":true},"total_sessions":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"duration_minutes":{"type":"integer","nullable":true},"timezone":{"type":"string"},"format":{"$ref":"#/components/schemas/SessionFormat"},"location":{"type":"string","nullable":true},"meeting_link":{"type":"string","nullable":true},"meeting_dial_in":{"type":"string","nullable":true},"leader":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid","nullable":true},"display_name":{"type":"string"},"role_label":{"type":"string","enum":["COACH","PEER LEADER"],"nullable":true}}}}},"group":{"type":"object","properties":{"group_id":{"type":"string","format":"uuid","nullable":true},"group_name":{"type":"string","nullable":true},"member_count":{"type":"integer"},"members":{"type":"array","items":{"$ref":"#/components/schemas/ReflectionGroupMember"}}}},"is_locked":{"type":"boolean"},"prior_commitment":{"nullable":true,"description":"MED-937 / E3.1b — non-null when commitment tracking is on for the cohort AND a prior-session commitment exists; otherwise null (section omitted).","oneOf":[{"$ref":"#/components/schemas/PriorCommitmentBlock"},{"type":"null"}]},"prior_commitment_pending":{"type":"boolean","description":"MED-1226 (AC5) — true when the Commitment Review section is configured but its prior-session data can't exist yet because the prior session (session_number − 1, same group) is not completed. Mutually exclusive with a non-null prior_commitment; the peer-leader prep page shows an 'available later' placeholder instead of hiding the section."}}},"PriorCommitmentBlock":{"type":"object","description":"MED-937 / E3.1b — \"Reflect on Prior Commitments\" payload block.\nPresent on the reflection payload only when the cohort has\n`commitment_tracking_enabled = true` AND the participant has a\nnon-empty commitment from the immediately preceding session in\nthe same group; otherwise the payload's `prior_commitment` is null\nand the section is omitted on the page.\n","properties":{"commitment_id":{"type":"string","format":"uuid","description":"PK of the source session_participant_commitments row"},"commitment_text":{"type":"string","description":"Prior session's commitment text (read-only on the pre-session page)"},"reflection_text":{"type":"string","description":"Participant's saved reflection on the prior commitment (empty string when none saved yet)"}}},"PeerLeaderBlock":{"type":"object","description":"MED-813 / E4.1 peer-leader banner state for the current participant.\nPresent on the reflection payload only for peer-led cohorts where the\nparticipant has a relevant role; otherwise the payload's `peer_leader`\nis null.\n","properties":{"state":{"type":"string","enum":["confirmed_leader","backup_needed","volunteer_pending","another_assigned","backup_assigned","declined","leader_declined_self"],"description":"Banner States 1–7. State 7 `leader_declined_self` (MED-1168): the\nviewer is still the assigned `session_leader_id` but their RSVP is\n`declined` and no backup replaced them — the banner offers a\none-click self re-confirm that restores the leader/start path.\n"},"leader_name":{"type":"string","nullable":true},"participant_first_name":{"type":"string"},"session_number":{"type":"integer","nullable":true},"session_time_label":{"type":"string","nullable":true},"cm_name":{"type":"string","nullable":true},"ai_assist_bullet":{"type":"boolean","description":"State 2 conditional AI bullet (IV4)"},"about_this_session":{"type":"string","nullable":true,"description":"State 1 CM content (IV3); null hides the block"}}},"Program":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"partner_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"CreateProgramRequest":{"type":"object","required":["partner_id","name"],"properties":{"partner_id":{"type":"string","format":"uuid"},"name":{"type":"string","minLength":1,"maxLength":255},"description":{"type":"string"}}},"Resource":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid","nullable":true},"session_id":{"type":"string","format":"uuid","nullable":true},"uploaded_by":{"type":"string","format":"uuid"},"category_id":{"type":"string","format":"uuid","nullable":true},"title":{"type":"string"},"description":{"type":"string","nullable":true},"resource_type":{"type":"string","enum":["file","link"]},"file_path":{"type":"string","nullable":true},"file_size":{"type":"integer","nullable":true},"mime_type":{"type":"string","nullable":true},"original_filename":{"type":"string","nullable":true},"url":{"type":"string","nullable":true},"is_evergreen":{"type":"boolean"},"access_coaches":{"type":"boolean"},"access_participants":{"type":"boolean"},"access_partners":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"ResourceWithDetails":{"allOf":[{"$ref":"#/components/schemas/Resource"},{"type":"object","properties":{"category_name":{"type":"string","nullable":true},"session_title":{"type":"string","nullable":true},"uploaded_by_name":{"type":"string"},"download_url":{"type":"string","description":"Signed CloudFront URL for file downloads"}}}]},"ResourceCategory":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"sort_order":{"type":"integer"},"is_active":{"type":"boolean"}}},"CreateFileResourceRequest":{"type":"object","required":["title","file_path","file_size","mime_type","original_filename"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"category_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string"},"file_path":{"type":"string"},"file_size":{"type":"integer"},"mime_type":{"type":"string"},"original_filename":{"type":"string"},"is_evergreen":{"type":"boolean"},"access_coaches":{"type":"boolean"},"access_participants":{"type":"boolean"},"access_partners":{"type":"boolean"}}},"CreateLinkResourceRequest":{"type":"object","required":["title","url"],"properties":{"cohort_id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"category_id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string"},"url":{"type":"string","format":"uri"},"is_evergreen":{"type":"boolean"},"access_coaches":{"type":"boolean"},"access_participants":{"type":"boolean"},"access_partners":{"type":"boolean"}}},"PresignedUploadResponse":{"type":"object","properties":{"upload_url":{"type":"string","description":"Presigned URL for uploading file to S3"},"download_url":{"type":"string","description":"Signed CloudFront URL for downloading after upload"},"key":{"type":"string","description":"S3 object key"},"expires_in":{"type":"integer","description":"URL expiration time in seconds"}}},"SessionAgendaVisual":{"type":"object","description":"One section → slide-page association (M:N).","required":["slide_page","sort_order"],"properties":{"slide_page":{"type":"integer","minimum":1,"description":"1-based page number in the uploaded slide deck PDF."},"sort_order":{"type":"integer","minimum":1,"description":"1-based display order within the section."}}},"SessionAgendaLeaderPrompt":{"type":"object","description":"One repeatable facilitator-only prompt within a section (AC-PS5).","required":["sort_order"],"properties":{"id":{"type":"string","format":"uuid","description":"Present on existing rows being kept on save; absent on new ones."},"intro_text":{"type":"string","nullable":true,"description":"Short single-line context above the discussion prompt."},"discussion_prompt":{"type":"string","nullable":true,"description":"Rich-text HTML serialization (bold, italic, underline, bullet list)."},"sort_order":{"type":"integer","minimum":1,"description":"1-based position in the section's prompt list (creation order)."}}},"SessionAgendaSection":{"type":"object","description":"One agenda section. section_name and duration_minutes are nullable to allow Save of in-progress work (AC-MC3).","required":["sort_order","visuals","leader_prompts"],"properties":{"id":{"type":"string","format":"uuid","description":"Present on existing rows being kept on save; absent on new ones."},"section_name":{"type":"string","nullable":true},"duration_minutes":{"type":"integer","nullable":true,"minimum":1,"description":"MED-1224 — section duration in whole minutes. Minimum valid value\nis 1; 0 and negatives are rejected. Nullable so an in-progress\nsection with no duration yet can still be saved (AC-MC3).\n"},"sort_order":{"type":"integer","minimum":1,"description":"1-based position in the agenda list."},"participant_prompt":{"type":"string","nullable":true,"description":"MED-980 (E1.3e / AC-PP). Rich-text instructions shown to\nparticipants in-session, stored as sanitized HTML. Optional.\nRendering to participants is E3.3a.\n"},"show_commitment_field":{"type":"boolean","description":"MED-980 (E1.3e / AC-SCF). When true, the in-session participant\nview (E3.3a) surfaces the commitment-entry field in this\nsection. UI gates the toggle on the cohort's\ncommitment_tracking_enabled; the column persists regardless.\n"},"show_last_session_commitment":{"type":"boolean","description":"MED-980 (E1.3e / AC-SLC). When true, the in-session participant\nview (E3.3a) displays the participant's prior-session\ncommitment in this section. UI gates the toggle on the\ncohort's commitment_tracking_enabled.\n"},"visuals":{"type":"array","items":{"$ref":"#/components/schemas/SessionAgendaVisual"}},"leader_prompts":{"type":"array","items":{"$ref":"#/components/schemas/SessionAgendaLeaderPrompt"}}}},"SessionAgendaReflectionPrompt":{"type":"object","description":"MED-981 (E1.3f / AC-RP). Plain-text reflection prompt authored on the\nParticipant Pre-Session Work panel. Minimum 2 non-empty enforced\nUI-side via the SessionIncompleteBanner; DB-side is nullable to\nallow Save of in-progress work.\n","required":["sort_order"],"properties":{"id":{"type":"string","format":"uuid","description":"Present on existing rows being kept on save; absent on new ones."},"prompt_text":{"type":"string","nullable":true,"description":"Plain text — NOT rich text per the spec."},"sort_order":{"type":"integer","minimum":1}}},"SessionAgendaPreSessionWork":{"type":"object","description":"MED-981 (E1.3f). Participant Pre-Session Work panel — configures\nwhat participants complete before the session. Pre-session page\nrendering is E3.1c (NOT this story).\n","required":["commitment_review_enabled","reading_tasks","reflection_prompts"],"properties":{"commitment_review_enabled":{"type":"boolean","description":"AC-CR1/CR3. When TRUE, the pre-session page (E3.1c) will surface\nthe participant's prior-session commitment review. UI gates the\ntoggle on cohort_commitment_tracking_enabled — hidden when off.\n"},"reading_tasks":{"type":"string","nullable":true,"description":"AC-RT1/RT2. Reading / Tasks rich-text (bold, italic, underline,\nbullet list). Sanitized server-side on write.\n"},"reflection_prompts":{"type":"array","items":{"$ref":"#/components/schemas/SessionAgendaReflectionPrompt"}}}},"SessionAgendaResponse":{"type":"object","description":"Full agenda bundle for a session's (cohort, modality, session_number) scope.","properties":{"cohort_id":{"type":"string","format":"uuid"},"modality":{"type":"string","enum":["group_coaching"]},"session_number":{"type":"integer"},"topic":{"type":"string","nullable":true,"description":"MED-1091 (E1.3n / AC5). Resolved session-number topic for the\neditor's \"Session Topic\" field: COALESCE(session_number_config.topic,\nthe legacy cohort_session_topics topic name). The same value the\nView-by-Session header shows — deliberately NOT sessions.title.\nNull when no topic is set (editor shows the \"Add session topic\"\nempty affordance, AC7).\n"},"description":{"type":"string","nullable":true,"description":"Multi-line plain-text session description (AC-SD2)."},"cohort_commitment_tracking_enabled":{"type":"boolean","description":"MED-980 (E1.3e / AC-SCF2, AC-SLC2) + MED-981 (E1.3f / AC-CR2).\ncommitment_tracking_enabled from the cohort's group_coaching\nmodality (MED-1069 / E1.10c, v230 — relocated from the cohort\ncolumn). The editor uses this to gate BOTH the per-section\ncommitment toggles (e1.3e) and the Enable Commitment Review toggle\non the pre-session work panel (e1.3f) — hidden entirely when false.\n"},"session_duration_minutes":{"type":"integer","nullable":true,"description":"MED-1074 (E1.3j / AC6). The configured session length for this\ncohort's modality (modalities.session_duration_minutes, set at\ncohort setup). The editor renders the agenda's running duration\ntotal alongside this and warns (advisory, non-blocking) when the\ntotal exceeds it. Null when the modality has no configured length;\nthe editor then shows the total without the comparison and\nsuppresses the warning (AC7).\n"},"marked_complete":{"type":"boolean","description":"MED-1232 (E1.3 / AC6, AC8). The persisted one-way \"Mark Complete\"\nrelease state. True once the CM has released this scope's content\nto the cohort; the editor then renders the completed-state\nindicator instead of the Mark Complete button. The CM can still\nedit + Save and it stays complete (no revert / no reopen).\n"},"completed_at":{"type":"string","format":"date-time","nullable":true,"description":"ISO timestamp of first release, or null while in progress."},"sections":{"type":"array","items":{"$ref":"#/components/schemas/SessionAgendaSection"}},"participant_pre_session_work":{"$ref":"#/components/schemas/SessionAgendaPreSessionWork"}}},"SessionAgendaUpsertRequest":{"type":"object","required":["description","sections","participant_pre_session_work"],"properties":{"description":{"type":"string","nullable":true},"sections":{"type":"array","items":{"$ref":"#/components/schemas/SessionAgendaSection"}},"participant_pre_session_work":{"$ref":"#/components/schemas/SessionAgendaPreSessionWork"}}},"SessionNumberConfig":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"modality":{"type":"string","enum":["group_coaching","one_on_one"]},"session_number":{"type":"integer","minimum":1},"topic":{"type":"string","nullable":true,"description":"Free-text topic; Group Coaching only"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"PropagationResult":{"type":"object","properties":{"config":{"$ref":"#/components/schemas/SessionNumberConfig"},"sessions_updated":{"type":"integer"},"sessions_skipped_due_to_override":{"type":"integer"},"sessions_skipped_due_to_scheduling":{"type":"integer"}}},"Session":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"group_id":{"type":"string","format":"uuid","nullable":true},"title":{"type":"string"},"description":{"type":"string","nullable":true},"session_number":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"timezone":{"type":"string"},"status":{"type":"string","enum":["scheduled","in_progress","completed","cancelled","rescheduled"]},"zoom_link":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"GroupFinalizationInfo":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"session_count":{"type":"integer"},"future_session_count":{"type":"integer"},"participant_count":{"type":"integer"},"pending_unique_count":{"type":"integer","description":"MED-838: Unique participants in this group with no `accepted` or\n`declined` RSVP across any of the group's finalized sessions.\nTentative is treated as non-response (matches per-session summary).\nDrives the resend-invites callout count on the group card.\n"},"is_finalized":{"type":"boolean"},"finalized_at":{"type":"string","format":"date-time","nullable":true},"coach_id":{"type":"string","format":"uuid","nullable":true},"coach_name":{"type":"string","nullable":true},"coach_zoom_link":{"type":"string","nullable":true},"coach_teams_link":{"type":"string","nullable":true},"coach_google_meet_link":{"type":"string","nullable":true},"saved_location":{"type":"string","nullable":true}}},"TaskPhase":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier"},"name":{"type":"string","description":"Phase name","example":"Cohort Setup & Design"},"slug":{"type":"string","description":"URL-friendly identifier","example":"cohort_setup"},"description":{"type":"string","nullable":true,"description":"Description of the phase"},"sort_order":{"type":"integer","description":"Display order","example":1},"is_active":{"type":"boolean","description":"Whether the phase is active"},"is_archived":{"type":"boolean","description":"Whether the phase is archived"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"TaskPhaseWithCount":{"allOf":[{"$ref":"#/components/schemas/TaskPhase"},{"type":"object","properties":{"template_count":{"type":"integer","description":"Number of templates in this phase"}}}]},"CreateTaskPhaseRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","description":"Phase name","example":"New Phase"},"slug":{"type":"string","description":"URL-friendly identifier (auto-generated if not provided)"},"description":{"type":"string","description":"Description of the phase"},"sort_order":{"type":"integer","description":"Display order (defaults to end)"}}},"UpdateTaskPhaseRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","nullable":true},"sort_order":{"type":"integer"},"is_active":{"type":"boolean"}}},"Task":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier"},"task_group_id":{"type":"string","format":"uuid","nullable":true,"description":"Task group this task belongs to"},"cohort_id":{"type":"string","format":"uuid","description":"Cohort this task belongs to"},"parent_task_id":{"type":"string","format":"uuid","nullable":true,"description":"Parent task ID for subtasks"},"session_id":{"type":"string","format":"uuid","nullable":true,"description":"Session this task is linked to"},"job_title_id":{"type":"string","format":"uuid","nullable":true,"description":"Job title for assignment resolution"},"title":{"type":"string","description":"Task title","example":"Schedule partner kickoff call"},"description":{"type":"string","nullable":true,"description":"Detailed task description"},"assignee_id":{"type":"string","format":"uuid","nullable":true,"description":"Assigned user ID"},"status":{"type":"string","enum":["not_started","in_progress","complete","blocked","cancelled"],"description":"Task status"},"priority":{"type":"string","enum":["low","medium","high","urgent"],"description":"Task priority"},"phase":{"type":"string","enum":["cohort_setup","cohort_prep","live_delivery","wrap_renewal"],"nullable":true,"description":"Task phase"},"due_date":{"type":"string","format":"date","nullable":true,"description":"Due date (YYYY-MM-DD)"},"due_time":{"type":"string","nullable":true,"description":"Due time (HH:MM)"},"completed_at":{"type":"string","format":"date-time","nullable":true},"completed_by":{"type":"string","format":"uuid","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"TaskWithDetails":{"allOf":[{"$ref":"#/components/schemas/Task"},{"type":"object","properties":{"assignee_name":{"type":"string","nullable":true},"assignee_email":{"type":"string","nullable":true},"job_title_name":{"type":"string","nullable":true},"session_title":{"type":"string","nullable":true},"subtask_count":{"type":"integer"},"subtasks_completed":{"type":"integer"},"comment_count":{"type":"integer"}}}]},"TaskComment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"task_id":{"type":"string","format":"uuid"},"author_id":{"type":"string","format":"uuid"},"content":{"type":"string"},"author_name":{"type":"string"},"author_email":{"type":"string"},"author_avatar_url":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"TaskActivityLog":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"task_id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid","nullable":true},"action_type":{"type":"string","enum":["created","updated","status_changed","assignee_changed","due_date_changed","comment_added","deleted"]},"field_name":{"type":"string","nullable":true},"old_value":{"type":"string","nullable":true},"new_value":{"type":"string","nullable":true},"user_name":{"type":"string","nullable":true},"user_email":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"TaskStats":{"type":"object","properties":{"total":{"type":"integer"},"not_started":{"type":"integer"},"in_progress":{"type":"integer"},"complete":{"type":"integer"},"blocked":{"type":"integer"},"overdue":{"type":"integer"}}},"CreateTaskRequest":{"type":"object","required":["title"],"properties":{"title":{"type":"string","description":"Task title"},"description":{"type":"string","description":"Detailed task description"},"assignee_id":{"type":"string","format":"uuid","description":"Assigned user ID"},"parent_task_id":{"type":"string","format":"uuid","description":"Parent task ID for subtasks"},"session_id":{"type":"string","format":"uuid","description":"Session this task is linked to"},"job_title_id":{"type":"string","format":"uuid","description":"Job title for assignment"},"status":{"type":"string","enum":["not_started","in_progress","complete","blocked","cancelled"]},"priority":{"type":"string","enum":["low","medium","high","urgent"]},"phase":{"type":"string","enum":["cohort_setup","cohort_prep","live_delivery","wrap_renewal"]},"due_date":{"type":"string","format":"date"},"due_time":{"type":"string"}}},"UpdateTaskRequest":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"assignee_id":{"type":"string","format":"uuid","nullable":true},"status":{"type":"string","enum":["not_started","in_progress","complete","blocked","cancelled"]},"priority":{"type":"string","enum":["low","medium","high","urgent"]},"phase":{"type":"string","enum":["cohort_setup","cohort_prep","live_delivery","wrap_renewal"]},"due_date":{"type":"string","format":"date","nullable":true},"due_time":{"type":"string","nullable":true}}},"BulkTaskRequest":{"type":"object","required":["task_ids","action"],"properties":{"task_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Array of task IDs to update"},"action":{"type":"string","enum":["update_status","update_assignee","update_priority","delete"],"description":"Bulk operation to perform"},"value":{"type":"string","description":"New value for the operation (status, assignee_id, or priority)"}}},"CrossCohortTaskRow":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Task unique identifier"},"title":{"type":"string","description":"Task title"},"cohort_id":{"type":"string","format":"uuid","description":"Cohort this task belongs to"},"cohort_name":{"type":"string","description":"Name of the cohort"},"cohort_code":{"type":"string","nullable":true,"description":"Cohort code"},"phase_name":{"type":"string","nullable":true,"description":"Task phase name"},"partner_name":{"type":"string","nullable":true,"description":"Partner organization name"},"status":{"type":"string","enum":["not_started","in_progress","complete","blocked","cancelled"],"description":"Task status"},"priority":{"type":"string","enum":["low","medium","high","urgent"],"description":"Task priority"},"due_date":{"type":"string","format":"date","nullable":true,"description":"Due date"},"assignee_id":{"type":"string","format":"uuid","nullable":true,"description":"Assigned user ID"},"assignee_name":{"type":"string","nullable":true,"description":"Full name of assignee"},"assignee_email":{"type":"string","nullable":true,"description":"Email of assignee"}}},"TaskAssignee":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"User ID"},"first_name":{"type":"string","description":"First name"},"last_name":{"type":"string","description":"Last name"},"email":{"type":"string","description":"Email address"}}},"TaskSubGroup":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier"},"phase_id":{"type":"string","format":"uuid","description":"Parent phase ID"},"name":{"type":"string","description":"Sub-group name","example":"Pre-Launch"},"description":{"type":"string","nullable":true,"description":"Description of the sub-group"},"cohort_status":{"type":"string","nullable":true,"enum":["draft","in_design","in_prep","live","wrap_up"],"description":"Cohort status this sub-group maps to for phase progression"},"sort_order":{"type":"integer","description":"Display order within phase","example":1},"is_active":{"type":"boolean","description":"Whether the sub-group is active"},"is_archived":{"type":"boolean","description":"Whether the sub-group is archived"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"TaskSubGroupWithDetails":{"allOf":[{"$ref":"#/components/schemas/TaskSubGroup"},{"type":"object","properties":{"phase_name":{"type":"string","description":"Name of the parent phase"},"phase_slug":{"type":"string","description":"Slug of the parent phase"},"template_count":{"type":"integer","description":"Number of templates in this sub-group"}}}]},"CreateTaskSubGroupRequest":{"type":"object","required":["phase_id","name"],"properties":{"phase_id":{"type":"string","format":"uuid","description":"Parent phase ID"},"name":{"type":"string","description":"Sub-group name","example":"Pre-Launch"},"description":{"type":"string","description":"Description of the sub-group"},"cohort_status":{"type":"string","nullable":true,"enum":["draft","in_design","in_prep","live","wrap_up"],"description":"Cohort status this sub-group maps to"},"sort_order":{"type":"integer","description":"Display order (defaults to end)"}}},"UpdateTaskSubGroupRequest":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string","nullable":true},"cohort_status":{"type":"string","nullable":true,"enum":["draft","in_design","in_prep","live","wrap_up"],"description":"Cohort status this sub-group maps to"},"sort_order":{"type":"integer"},"is_active":{"type":"boolean"}}},"TaskTemplate":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_type":{"type":"string"},"template_type":{"type":"string","enum":["base","group_coaching","one_on_one","workshop"],"description":"Determines which cohorts this template applies to"},"phase":{"type":"string","enum":["cohort_setup","cohort_prep","live_delivery","wrap_renewal"],"nullable":true},"sub_group":{"type":"string","nullable":true,"description":"Sub-grouping within a phase (e.g., \"Partner Alignment\", \"Sessions (1st Half)\")"},"milestone_name":{"type":"string"},"task_group_name":{"type":"string"},"task_title":{"type":"string","description":"Task title. Can include {session_number} placeholder for session-specific tasks"},"task_description":{"type":"string","nullable":true},"job_title_id":{"type":"string","format":"uuid","nullable":true},"parent_template_id":{"type":"string","format":"uuid","nullable":true,"description":"Reference to parent template for subtask templates"},"anchor_type":{"type":"string","enum":["cohort_start","cohort_end","orientation","coach_kickoff","session","final_session","midpoint","endpoint"]},"anchor_session_number":{"type":"integer","nullable":true},"days_offset":{"type":"integer","description":"Days offset from anchor (negative = before, positive = after)"},"default_priority":{"type":"string","enum":["low","medium","high","urgent"]},"milestone_order":{"type":"integer"},"task_group_order":{"type":"integer"},"task_order":{"type":"integer"},"is_required":{"type":"boolean"},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"TaskTemplateWithDetails":{"allOf":[{"$ref":"#/components/schemas/TaskTemplate"},{"type":"object","properties":{"job_title_name":{"type":"string","nullable":true},"parent_task_title":{"type":"string","nullable":true,"description":"Title of parent template if this is a subtask template"}}}]},"CreateTaskTemplateRequest":{"type":"object","required":["cohort_type","milestone_name","task_group_name","task_title"],"properties":{"cohort_type":{"type":"string"},"template_type":{"type":"string","enum":["base","group_coaching","one_on_one","workshop"],"default":"base"},"phase":{"type":"string","enum":["cohort_setup","cohort_prep","live_delivery","wrap_renewal"]},"sub_group":{"type":"string"},"milestone_name":{"type":"string"},"task_group_name":{"type":"string"},"task_title":{"type":"string"},"task_description":{"type":"string"},"job_title_id":{"type":"string","format":"uuid"},"parent_template_id":{"type":"string","format":"uuid","description":"Parent template ID for creating subtask templates"},"anchor_type":{"type":"string","enum":["cohort_start","cohort_end","orientation","coach_kickoff","session","final_session","midpoint","endpoint"]},"anchor_session_number":{"type":"integer"},"days_offset":{"type":"integer"},"default_priority":{"type":"string","enum":["low","medium","high","urgent"]},"milestone_order":{"type":"integer"},"task_group_order":{"type":"integer"},"task_order":{"type":"integer"},"is_required":{"type":"boolean"}}},"UpdateTaskTemplateRequest":{"type":"object","properties":{"cohort_type":{"type":"string"},"template_type":{"type":"string","enum":["base","group_coaching","one_on_one","workshop"]},"phase":{"type":"string","enum":["cohort_setup","cohort_prep","live_delivery","wrap_renewal"],"nullable":true},"sub_group":{"type":"string","nullable":true},"milestone_name":{"type":"string"},"task_group_name":{"type":"string"},"task_title":{"type":"string"},"task_description":{"type":"string","nullable":true},"job_title_id":{"type":"string","format":"uuid","nullable":true},"parent_template_id":{"type":"string","format":"uuid","nullable":true},"anchor_type":{"type":"string","enum":["cohort_start","cohort_end","orientation","coach_kickoff","session","final_session","midpoint","endpoint"]},"anchor_session_number":{"type":"integer","nullable":true},"days_offset":{"type":"integer"},"default_priority":{"type":"string","enum":["low","medium","high","urgent"]},"milestone_order":{"type":"integer"},"task_group_order":{"type":"integer"},"task_order":{"type":"integer"},"is_required":{"type":"boolean"},"is_active":{"type":"boolean"}}},"TemplateTypeOption":{"type":"object","properties":{"value":{"type":"string","enum":["base","group_coaching","one_on_one","workshop"]},"label":{"type":"string","description":"Human-readable label for the template type"}}},"TimeSlot":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"identifier":{"type":"string","description":"Auto-generated identifier (e.g., BCAP08-TS1)"},"slot_number":{"type":"integer","description":"Sequential slot number (1, 2, 3...)"},"day_of_week":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday"]},"time":{"type":"string","description":"Time in HH:MM:SS format"},"timezone":{"type":"string","description":"IANA timezone (e.g., America/New_York)"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"TimeSlotResponse":{"allOf":[{"$ref":"#/components/schemas/TimeSlot"},{"type":"object","properties":{"display_time":{"type":"string","description":"Formatted time (e.g., 10:00 AM)"},"display_day":{"type":"string","description":"Full day name (e.g., Tuesday)"},"timezone_abbreviation":{"type":"string","description":"Timezone abbreviation (e.g., ET)"}}}]},"CreateTimeSlotRequest":{"type":"object","required":["day_of_week","time","timezone"],"properties":{"day_of_week":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday"]},"time":{"type":"string","description":"Time in HH:MM or HH:MM:SS format"},"timezone":{"type":"string","description":"IANA timezone (e.g., America/New_York)"}}},"UpdateTimeSlotRequest":{"type":"object","properties":{"day_of_week":{"type":"string","enum":["monday","tuesday","wednesday","thursday","friday"]},"time":{"type":"string"},"timezone":{"type":"string"}}},"Topic":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier"},"name":{"type":"string","description":"Topic name","example":"Active Listening"},"description":{"type":"string","nullable":true,"description":"Description of the topic"},"display_order":{"type":"integer","nullable":true,"description":"Optional custom sort order"},"is_active":{"type":"boolean","description":"Whether the topic is active"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"CohortSessionTopic":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"session_number":{"type":"integer","description":"Session number (1-based)","example":1},"topic_id":{"type":"string","format":"uuid"},"topic_name":{"type":"string","description":"Canonical topic name (used by CM-facing UI and reporting)","example":"Active Listening"},"display_name":{"type":"string","nullable":true,"description":"Optional participant-facing label override (MED-832). When set, participants see\nthis in place of topic_name; CM surfaces and reporting always use topic_name.\n","example":"Welcome & Goal-Setting"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"InternalUser":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"job_title":{"type":"string"},"department":{"type":"string"},"phone":{"type":"string"},"avatar_url":{"type":"string"},"is_active":{"type":"boolean"},"last_login_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"persona":{"type":"string","enum":["internal_admin","internal_user"]},"persona_display":{"type":"string","enum":["Admin","Cohort Manager"]}}},"CreateUserInput":{"type":"object","required":["email","first_name","last_name","persona","job_title","department"],"properties":{"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"persona":{"type":"string","enum":["internal_admin","internal_user"]},"job_title":{"type":"string"},"department":{"type":"string"}}},"UpdateUserInput":{"type":"object","properties":{"job_title":{"type":"string"},"department":{"type":"string"},"persona":{"type":"string","enum":["internal_admin","internal_user"]}}},"WaitingRoomPresence":{"type":"string","enum":["present","joining","not_yet_here"]},"WaitingRoomAttendee":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"display_name":{"type":"string"},"initials":{"type":"string"},"is_current_participant":{"type":"boolean"},"role_label":{"type":"string","enum":["COACH","PEER LEADER"],"nullable":true},"presence":{"$ref":"#/components/schemas/WaitingRoomPresence"}}},"WaitingRoomLeader":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid","nullable":true},"first_name":{"type":"string"},"display_name":{"type":"string"},"role_label":{"type":"string","enum":["COACH","PEER LEADER"],"nullable":true}}},"WaitingRoomSessionInfo":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["unscheduled","scheduled","in_progress","completed","cancelled","rescheduled"]},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"duration_minutes":{"type":"integer","nullable":true},"timezone":{"type":"string"},"format":{"type":"string","enum":["Virtual","In-Person"]},"cohort_id":{"type":"string","format":"uuid"},"group_id":{"type":"string","format":"uuid","nullable":true},"group_name":{"type":"string","nullable":true}}},"WaitingRoomReflectionState":{"type":"object","properties":{"status":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","COMPLETED"]},"considered_for_prep":{"type":"boolean"}}},"WaitingRoomNoShowState":{"type":"object","nullable":true,"description":"MED-1006 (E4.0f) — non-null only for a peer-led Live Session Delivery\nsession that has entered the day-of no-show window (still scheduled,\npast the confirmed-leader head-start). Null otherwise (normal card).\n","properties":{"header_scenario":{"type":"string","enum":["leader_not_arrived","leader_declined"]},"leader_first_name":{"type":"string"},"eligibility":{"type":"string","enum":["volunteered","no_response","passive"],"description":"This member's volunteer eligibility variant (AC6/AC16)."},"countdown_ends_at":{"type":"string","format":"date-time","description":"When the session is declared missed (AC4/AC19)."},"ai_features_enabled":{"type":"boolean"},"session_number":{"type":"integer","nullable":true}}},"WaitingRoomPayload":{"type":"object","properties":{"session":{"$ref":"#/components/schemas/WaitingRoomSessionInfo"},"leader":{"$ref":"#/components/schemas/WaitingRoomLeader"},"ai_features_enabled":{"type":"boolean"},"attendees":{"type":"array","items":{"$ref":"#/components/schemas/WaitingRoomAttendee"}},"reflection":{"$ref":"#/components/schemas/WaitingRoomReflectionState"},"no_show":{"$ref":"#/components/schemas/WaitingRoomNoShowState"},"server_now":{"type":"string","format":"date-time"}}},"LoginResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"tokens":{"type":"object","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"}}}}}}},"User":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"job_title":{"type":"string"},"avatar_url":{"type":"string"},"initials":{"type":"string"},"display_name":{"type":"string"},"personas":{"type":"array","items":{"type":"object"}}}},"ResendWebhookPayload":{"type":"object","properties":{"type":{"type":"string","enum":["email.sent","email.delivered","email.delivery_delayed","email.complained","email.bounced","email.opened","email.clicked"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"email_id":{"type":"string"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"}}}}}}},"paths":{"/absence-email-templates":{"get":{"summary":"List all absence email templates","description":"Returns all absence email templates with active/inactive status, escalation tier, and count of rules referencing each template","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Templates retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/AbsenceEmailTemplateWithRuleCount"}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new absence email template","description":"Creates a new absence email template with name, subject, body, escalation tier, and active status","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","subject_template","body_template","escalation_tier"],"properties":{"name":{"type":"string","description":"Template name"},"subject_template":{"type":"string","description":"Email subject with variable placeholders"},"body_template":{"type":"string","description":"Email body with variable placeholders"},"escalation_tier":{"type":"string","enum":["first_warning","first_warning_with_offer","mid_escalation","deferral_offer","forced_deferral"],"description":"Escalation tier this template belongs to"},"is_active":{"type":"boolean","default":true}}}}}},"responses":{"201":{"description":"Template created successfully"},"401":{"description":"Unauthorized"},"409":{"description":"Template with this name already exists"},"422":{"description":"Validation failed"}}}},"/absence-email-templates/{id}":{"get":{"summary":"Get an absence email template by ID","description":"Returns a single absence email template","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Template ID"}],"responses":{"200":{"description":"Template retrieved successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"}}},"patch":{"summary":"Update an absence email template","description":"Updates an existing template's name, subject, body, escalation tier, or active status. Changes apply to future emails only.","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"subject_template":{"type":"string"},"body_template":{"type":"string"},"escalation_tier":{"type":"string","enum":["first_warning","first_warning_with_offer","mid_escalation","deferral_offer","forced_deferral"]},"is_active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Template updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"},"409":{"description":"Template with this name already exists"},"422":{"description":"Validation failed"}}},"delete":{"summary":"Delete an absence email template","description":"Deletes a template. Blocked if any active rules reference the template.","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Template deleted successfully"},"400":{"description":"Cannot delete template referenced by active rules"},"401":{"description":"Unauthorized"},"404":{"description":"Template not found"}}}},"/absence-email-rules":{"get":{"summary":"List all absence email rules","description":"Returns all tier-based absence email rules with their mapped template names. Optionally filtered by escalation tier.","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"escalation_tier","schema":{"type":"string","enum":["first_warning","first_warning_with_offer","mid_escalation","deferral_offer","forced_deferral"]},"required":false,"description":"Filter rules by escalation tier"}],"responses":{"200":{"description":"Rules retrieved successfully"},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new absence email rule","description":"Creates a new tier-based rule mapping an escalation tier + absence scenario to a template or no-email flag. Prevents duplicate rules.","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["escalation_tier","absence_type","priority"],"properties":{"escalation_tier":{"type":"string","enum":["first_warning","first_warning_with_offer","mid_escalation","deferral_offer","forced_deferral"]},"absence_type":{"type":"string","enum":["unexcused","business","personal","any"]},"is_first_session":{"type":"boolean","default":false},"no_email":{"type":"boolean","default":false},"template_id":{"type":"string","format":"uuid","nullable":true},"priority":{"type":"integer","minimum":0}}}}}},"responses":{"201":{"description":"Rule created successfully"},"400":{"description":"Invalid template_id/no_email combination"},"401":{"description":"Unauthorized"},"409":{"description":"Duplicate rule exists"},"422":{"description":"Validation failed"}}}},"/absence-email-rules/coverage":{"get":{"summary":"Get absence rule coverage analysis","description":"Returns coverage analysis for all escalation tiers. Identifies gaps where no rule exists for a given tier + absence type combination.","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Coverage analysis retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"all_covered":{"type":"boolean"},"tiers":{"type":"array","items":{"type":"object","properties":{"escalation_tier":{"type":"string"},"covered":{"type":"boolean"},"gaps":{"type":"array"},"total_rules":{"type":"integer"}}}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/absence-email-rules/{id}":{"patch":{"summary":"Update an absence email rule","description":"Updates an existing tier-based rule's parameters. Changes apply to future queue generation only.","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"escalation_tier":{"type":"string","enum":["first_warning","first_warning_with_offer","mid_escalation","deferral_offer","forced_deferral"]},"absence_type":{"type":"string","enum":["unexcused","business","personal","any"]},"is_first_session":{"type":"boolean"},"no_email":{"type":"boolean"},"template_id":{"type":"string","format":"uuid","nullable":true},"priority":{"type":"integer"}}}}}},"responses":{"200":{"description":"Rule updated successfully"},"400":{"description":"Invalid template_id/no_email combination"},"401":{"description":"Unauthorized"},"404":{"description":"Rule not found"},"409":{"description":"Duplicate rule exists"},"422":{"description":"Validation failed"}}},"delete":{"summary":"Delete an absence email rule","description":"Permanently deletes a rule. This may create coverage gaps.","tags":["Absence Email Management"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Rule deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Rule not found"}}}},"/api/ai/in-session-context/{sessionId}":{"get":{"summary":"Get in-session context for a session (internal service-to-service)","description":"Internal-only endpoint consumed by the session-intelligence microservice to pull authoritative cohort/session context for in-session AI nudges. Authenticated by the shared-secret `x-internal-key` header (NOT coach bearer auth). Returns the session's topic, guide content, duration, and group participant roster. Field names match session-intelligence's `StartSessionData`/`ParticipantContext` so the caller maps them directly.\nResponds **503** when `INTERNAL_API_KEY` is not configured (the internal surface is unavailable; callers should fall back to the legacy path) and **401** when the key is missing or incorrect.\n","tags":["AI"],"parameters":[{"in":"header","name":"x-internal-key","required":true,"schema":{"type":"string"},"description":"Shared internal service-to-service secret (matches INTERNAL_API_KEY)."},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The session whose context to fetch."}],"responses":{"200":{"description":"In-session context for the session.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"sessionId":{"type":"string","format":"uuid"},"cohortId":{"type":"string","format":"uuid"},"topicName":{"type":"string"},"guideContent":{"type":"string","nullable":true,"description":"Plain-text session guide, or null when none is uploaded."},"durationMinutes":{"type":"integer"},"segmentTiming":{"type":"object","nullable":true,"description":"Pacing windows; always null today (caller defaults it)."},"participants":{"type":"array","items":{"type":"object","properties":{"firstName":{"type":"string"},"lastName":{"type":"string"},"primaryGoal":{"type":"string","nullable":true},"developmentGoal":{"type":"string","nullable":true},"role":{"type":"string","nullable":true},"businessUnit":{"type":"string","nullable":true}}}}}}}}}}},"401":{"description":"Missing or incorrect `x-internal-key` header."},"404":{"description":"Session not found."},"503":{"description":"INTERNAL_API_KEY not configured on medley-api."}}}},"/cohorts/{cohortId}/attendance-follow-ups/summary":{"get":{"summary":"Get attendance follow-up summary counts for a cohort","description":"Returns lightweight pending/addressed/total counts for banners, callouts, notifications, and drill-down CTAs. Includes per-session pending breakdown and per-participant pending counts.\n","tags":["Attendance Follow-Ups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Follow-up summary retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total_pending":{"type":"integer"},"total_addressed":{"type":"integer"},"total_ever_generated":{"type":"integer"},"sessions_with_pending":{"type":"integer"},"sessions":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_number":{"type":"integer"},"session_date":{"type":"string","format":"date-time"},"pending_count":{"type":"integer"}}}},"participant_pending_counts":{"type":"object","additionalProperties":{"type":"integer"},"description":"Map of cohort_participant_id to pending follow-up count"}}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/{cohortId}/attendance-follow-ups":{"get":{"summary":"Get attendance follow-up queue for a cohort","description":"Returns all absence email queue entries across all sessions in the cohort, grouped by session (most recent first). Supports filtering by session and status.\n","tags":["Attendance Follow-Ups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"query","name":"session_id","required":false,"schema":{"type":"string","format":"uuid"},"description":"Filter to a specific session"},{"in":"query","name":"status","required":false,"schema":{"type":"string","enum":["pending","sent","skipped","no_template"]},"description":"Filter by queue entry status (default shows all)"}],"responses":{"200":{"description":"Queue entries retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"total_pending":{"type":"integer"},"sessions":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_number":{"type":"integer"},"session_date":{"type":"string","format":"date-time"},"pending_count":{"type":"integer"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/AttendanceFollowUpEntry"}}}}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/attendance-follow-ups/participants":{"get":{"summary":"List follow-ups as one row per participant (Level 1)","description":"Participant-centric view of the attendance follow-ups queue for a cohort. Each returned row represents one participant anchored to their primary follow-up (pending first, else most recent past), with aggregate counts for pending/sent/skipped follow-ups across the cohort and group membership so the Level 1 \"Group by: Session | Group\" toggle can pivot client-side. Supports the same session_id and status filters as the flat queue list.\n","tags":["Attendance Follow-Ups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"session_id","schema":{"type":"string","format":"uuid"},"description":"Scope participants to those with at least one follow-up in this session"},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","sent","skipped","no_template"]},"description":"Scope participants to those with at least one follow-up of this status"}],"responses":{"200":{"description":"Participant summaries"},"401":{"description":"Unauthorized"}}}},"/cohorts/{cohortId}/attendance-follow-ups/{entryId}":{"patch":{"summary":"Update a queue entry's subject, body, send/skip status, skip note, or template override","description":"Allows CMs to edit the email subject and body inline before sending, toggle send/skip, record a skip note, or swap the active template. Passing override_template_id triggers a server-side re-render of subject/body using the target template's text plus fresh variable substitution; any rendered_subject/rendered_body passed in the same request are ignored in favor of the re-render. Setting status=skipped records skipped_at and skipped_by_user_id using the authenticated CM. Setting status=pending clears all skip metadata.\n","tags":["Attendance Follow-Ups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"entryId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"rendered_subject":{"type":"string","description":"Updated email subject line (ignored if override_template_id is set)"},"rendered_body":{"type":"string","description":"Updated email body HTML (ignored if override_template_id is set)"},"status":{"type":"string","enum":["pending","skipped"],"description":"Toggle send (pending) or skip (skipped)"},"skip_note":{"type":"string","nullable":true,"description":"Optional short note recorded alongside status=skipped"},"override_template_id":{"type":"string","format":"uuid","nullable":true,"description":"Template to switch the entry to; triggers server-side re-render of subject/body"}}}}}},"responses":{"200":{"description":"Entry updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AttendanceFollowUpEntry"}}}}}},"400":{"description":"Cannot edit a sent entry, or could not re-render with the selected template"},"401":{"description":"Unauthorized"},"404":{"description":"Entry not found in this cohort"}}}},"/cohorts/{cohortId}/attendance-follow-ups/send":{"post":{"summary":"Batch send all pending attendance follow-up emails","description":"Sends all queue entries with status 'pending' via the Resend email integration. Each email includes the coach as CC and the CM as reply-to. Failed emails are marked individually — one failure does not stop the batch.\n","tags":["Attendance Follow-Ups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid","description":"Optional — limit batch to a specific session"}}}}}},"responses":{"200":{"description":"Batch send completed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sent_count":{"type":"integer"},"failed_count":{"type":"integer"},"skipped_count":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"entry_id":{"type":"string"},"participant_name":{"type":"string"},"template_name":{"type":"string"},"coach_email":{"type":"string"},"status":{"type":"string","enum":["sent","failed"]},"error":{"type":"string"}}}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/{cohortId}/attendance-follow-ups/{entryId}/override":{"post":{"summary":"Override duplicate prevention for a sent entry","description":"Resets a previously sent entry back to 'pending' status so it can be re-sent in the next batch. Requires frontend confirmation before calling.\n","tags":["Attendance Follow-Ups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"entryId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Entry reset to pending for re-send","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AttendanceFollowUpEntry"},"message":{"type":"string"}}}}}},"400":{"description":"Entry is not in sent status"},"401":{"description":"Unauthorized"},"404":{"description":"Entry not found in this cohort"}}}},"/auth/login":{"post":{"summary":"Authenticate user","description":"Authenticate user with email and password, returns access and refresh tokens","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AuthTokens"}}}}}},"401":{"description":"Invalid credentials"},"422":{"description":"Validation error"}}}},"/auth/refresh":{"post":{"summary":"Refresh access token","description":"Get a new access token using a valid refresh token","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Token refreshed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AuthTokens"}}}}}},"401":{"description":"Invalid or expired refresh token"}}}},"/auth/me":{"get":{"summary":"Get current user profile","description":"Returns the authenticated user's profile information","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User profile retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/UserProfile"}}}}}},"401":{"description":"Unauthorized"}}}},"/auth/logout":{"post":{"summary":"Logout current session","description":"Revokes the current refresh token, ending this session","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Logged out successfully"},"401":{"description":"Unauthorized"}}}},"/auth/logout-all":{"post":{"summary":"Logout from all devices","description":"Revokes all refresh tokens for the current user","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Logged out from all devices successfully"},"401":{"description":"Unauthorized"}}}},"/auth/sessions":{"get":{"summary":"Get active sessions","description":"Returns all active sessions for the current user","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sessions retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"user_agent":{"type":"string"},"ip_address":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/auth/validate":{"get":{"summary":"Validate access token","description":"Checks if the current access token is valid","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Token is valid","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"}}}}}}}},"401":{"description":"Token is invalid or expired"}}}},"/auth/invitation/{token}":{"get":{"summary":"Validate invitation token","description":"Check if an invitation token is valid and return user info","tags":["Auth"],"parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"},"description":"The invitation token"}],"responses":{"200":{"description":"Valid invitation","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"}}}}}}}},"404":{"description":"Invalid or expired invitation"}}}},"/auth/invitation/accept":{"post":{"summary":"Accept invitation and set password","description":"Accept an invitation by setting a password. Returns auth tokens on success.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string","description":"The invitation token"},"password":{"type":"string","format":"password","description":"The user's new password (min 8 characters)"}}}}}},"responses":{"200":{"description":"Invitation accepted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"message":{"type":"string"},"user":{"$ref":"#/components/schemas/User"},"tokens":{"$ref":"#/components/schemas/AuthTokens"}}}}}}}},"400":{"description":"Invalid request"},"404":{"description":"Invalid or expired invitation"}}}},"/auth/impersonate":{"post":{"summary":"Start impersonating a user","description":"Admin-only. Start an impersonation session for a target user. Cannot impersonate other admins.","tags":["Auth"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["targetUserId"],"properties":{"targetUserId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Impersonation session started"},"400":{"description":"Cannot impersonate admin users"},"403":{"description":"Not authorized"},"404":{"description":"Target user not found"}}}},"/auth/end-impersonate":{"post":{"summary":"End impersonation session","description":"End the current impersonation session and return to admin view","tags":["Auth"],"security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"refreshToken":{"type":"string","description":"The impersonation refresh token to revoke"},"originalRefreshToken":{"type":"string","description":"The admin's own refresh token from before the impersonation. It is revoked, because new admin tokens are returned."}}}}}},"responses":{"200":{"description":"Impersonation ended, admin tokens returned"},"400":{"description":"Not currently impersonating"}}}},"/auth/users-by-persona/{persona}":{"get":{"summary":"List users by persona","description":"Get all active users with a specific persona. Used for impersonation user picker. Admin-only.","tags":["Auth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"persona","required":true,"schema":{"type":"string","enum":["internal_user","coach","participant","partner"]}}],"responses":{"200":{"description":"List of users"},"400":{"description":"Invalid persona"}}}},"/auth/impersonation-log":{"get":{"summary":"Get impersonation audit log","description":"Returns paginated impersonation session history. Admin-only.","tags":["Auth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}}],"responses":{"200":{"description":"Impersonation log entries"}}}},"/auth/magic-link/verify":{"post":{"summary":"Verify magic link and authenticate","description":"Validates the magic link token and returns authentication tokens","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"The magic link token"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/UserProfile"},"tokens":{"$ref":"#/components/schemas/AuthTokens"},"isFirstLogin":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid or expired token"}}}},"/auth/magic-link/request":{"post":{"summary":"Request a magic link","description":"Sends a magic link email for passwordless login. Only available for coach, partner, and participant personas.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Request processed (email sent if account exists with correct persona)"},"429":{"description":"Rate limit exceeded"}}}},"/auth/forgot-password":{"post":{"summary":"Request password reset","description":"Sends a password reset email if the email exists. Always returns success to prevent email enumeration.","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Request processed (email sent if account exists)"}}}},"/auth/reset-password/validate":{"get":{"summary":"Validate password reset token","description":"Check if a password reset token is valid before showing the reset form","tags":["Auth"],"parameters":[{"in":"query","name":"token","required":true,"schema":{"type":"string"},"description":"The password reset token"}],"responses":{"200":{"description":"Token is valid","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"email":{"type":"string"}}}}}}}},"404":{"description":"Invalid or expired token"}}}},"/auth/reset-password":{"post":{"summary":"Reset password with token","description":"Reset the user's password using a valid reset token","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string"},"password":{"type":"string","format":"password"}}}}}},"responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Invalid request"},"404":{"description":"Invalid or expired token"}}}},"/auth/admin/reset-password":{"post":{"summary":"Admin-initiated password reset","description":"Send a password reset email to a user (requires authentication)","tags":["Auth"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Password reset email sent"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/admin/billcom/status":{"get":{"summary":"Get Bill.com connection status","description":"Returns whether the system currently has an active session with Bill.com.","tags":["Bill.com Integration"],"responses":{"200":{"description":"Connection status returned","content":{"application/json":{"schema":{"type":"object","properties":{"connected":{"type":"boolean"},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/billcom/test-connection":{"post":{"summary":"Test Bill.com connection","description":"Attempts to authenticate with Bill.com to verify credentials are valid.","tags":["Bill.com Integration"],"responses":{"200":{"description":"Test result returned","content":{"application/json":{"schema":{"type":"object","properties":{"connected":{"type":"boolean"},"message":{"type":"string"},"last_checked":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/billcom/vendors/search":{"get":{"summary":"Search Bill.com vendors","description":"Searches Bill.com vendor records by name or email for linking to coaches.","tags":["Bill.com Integration"],"parameters":[{"in":"query","name":"q","required":true,"schema":{"type":"string"},"description":"Search term (name or email)"},{"in":"query","name":"coach_email","schema":{"type":"string"},"description":"Coach email for exact match detection"},{"in":"query","name":"refresh","schema":{"type":"string","enum":["true"]},"description":"When 'true', busts and re-fetches the cached vendor directory before searching (MED-1174 manual refresh)"}],"responses":{"200":{"description":"Vendor search results"},"400":{"description":"Missing search query"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/billcom/coaches/{coachUserId}/vendor":{"get":{"summary":"Get vendor mapping for a coach","description":"Returns the Bill.com vendor mapping for a specific coach.","tags":["Bill.com Integration"],"parameters":[{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Vendor mapping returned"},"401":{"description":"Unauthorized"},"404":{"description":"Coach profile not found"}}},"post":{"summary":"Link a coach to a Bill.com vendor","description":"Maps a coach to a Bill.com vendor record for payment processing.","tags":["Bill.com Integration"],"parameters":[{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["vendor_id","vendor_name"],"properties":{"vendor_id":{"type":"string","description":"Bill.com vendor ID"},"vendor_name":{"type":"string","description":"Vendor display name"}}}}}},"responses":{"200":{"description":"Vendor linked successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Coach profile not found"},"422":{"description":"Validation error"}}},"delete":{"summary":"Unlink a coach from a Bill.com vendor","description":"Removes the Bill.com vendor mapping from a coach profile.","tags":["Bill.com Integration"],"parameters":[{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Vendor unlinked"},"401":{"description":"Unauthorized"}}}},"/api/admin/billcom/coaches/{coachUserId}/vendor/suggestions":{"get":{"summary":"Get suggested vendor matches for a coach","description":"Returns Bill.com vendors that may match a coach based on name and email.","tags":["Bill.com Integration"],"parameters":[{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"refresh","schema":{"type":"string","enum":["true"]},"description":"When 'true', busts and re-fetches the cached vendor directory before suggesting (MED-1174 manual refresh)"}],"responses":{"200":{"description":"Suggested vendor matches"},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"}}}},"/api/admin/billcom/orphaned-bills":{"get":{"summary":"List orphaned Bill.com bills tracked by SGOS","description":"Returns the count and detail of failed payments that still carry a Bill.com bill id —\norphaned, potentially-payable bills left behind when a payment failed after its bill\nwas created but the automatic void could not complete (MED-1237 AC2). Read-only;\nused to surface the volume of orphans before running the reconciliation cleanup.\n\nOnly surfaces orphans created after the MED-1237 fix shipped. Orphans from prior\noccurrences have no persisted bill id and require a one-time Bill.com-side scan.\n","tags":["Bill.com Integration"],"responses":{"200":{"description":"Orphaned bill count and detail","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"count":{"type":"integer","description":"Number of tracked orphaned bills"},"orphaned_bills":{"type":"array","items":{"type":"object"}}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden — requires an internal role"},"500":{"description":"Internal server error"}}}},"/api/admin/billcom/orphaned-bills/cleanup":{"post":{"summary":"Reconcile (void) orphaned Bill.com bills","description":"Voids each orphaned Bill.com bill that SGOS is tracking (failed payments carrying a\nbill id) and clears the persisted bill id on success, so a payment SGOS reports as\nfailed no longer corresponds to a payable bill in Bill.com (MED-1237 AC3, AC5).\nIdempotent — safe to re-run; already-voided bills are treated as reconciled.\n\nRestricted to Admin (`internal_admin`) because it moves financial state in Bill.com.\n","tags":["Bill.com Integration"],"responses":{"200":{"description":"Reconciliation summary","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","properties":{"total":{"type":"integer"},"voided":{"type":"integer"},"still_orphaned":{"type":"integer"},"details":{"type":"array","items":{"type":"object"}}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden — requires the Admin role"},"500":{"description":"Internal server error"}}}},"/api/calendly/connect":{"get":{"summary":"Initiate Calendly OAuth flow","description":"Returns the Calendly authorization URL for the logged-in coach to begin OAuth connection","tags":["Calendly"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Authorization URL returned","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"authorization_url":{"type":"string","format":"uri"}}}}}}}},"401":{"description":"Authentication required"},"404":{"description":"Coach profile not found"}}}},"/api/calendly/callback":{"get":{"summary":"Handle Calendly OAuth callback","description":"OAuth callback endpoint that exchanges the authorization code for tokens,\nvalidates the coach's Calendly plan (Standard+ required), stores tokens,\ncreates a webhook subscription, and auto-selects event types if only one exists.\n","tags":["Calendly"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"code","schema":{"type":"string"},"required":true,"description":"Authorization code from Calendly"},{"in":"query","name":"state","schema":{"type":"string"},"required":true,"description":"State parameter containing coach profile ID"}],"responses":{"200":{"description":"Calendly connected successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CoachCalendlySettings"}}}}}},"400":{"description":"Invalid code, plan validation failed, or missing parameters"},"401":{"description":"Authentication required"}}}},"/api/calendly/disconnect":{"post":{"summary":"Disconnect Calendly","description":"Removes the coach's Calendly connection, deletes webhook subscription, and clears all tokens","tags":["Calendly"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Calendly disconnected"},"401":{"description":"Authentication required"},"404":{"description":"Coach profile not found"}}}},"/api/calendly/status":{"get":{"summary":"Get Calendly connection status","description":"Returns connection status, connected date, and event type mapping configuration","tags":["Calendly"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Connection status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"connected":{"type":"boolean"},"connected_at":{"type":"string","format":"date-time","nullable":true},"event_types_configured":{"type":"boolean"},"event_type_20min_uri":{"type":"string","nullable":true},"event_type_45min_uri":{"type":"string","nullable":true},"event_type_60min_uri":{"type":"string","nullable":true},"unmapped_durations":{"type":"array","items":{"type":"integer"},"description":"Durations (in minutes) that have no mapped event type"},"booking_health":{"type":"object","nullable":true,"description":"MED-1363. What can stop participant bookings. Null when not connected or when Calendly fails. Cached 10 minutes.\n","properties":{"plan_stage":{"type":"string","enum":["trial","free","paid"],"nullable":true},"plan":{"type":"string","nullable":true},"required_questions":{"type":"array","items":{"type":"object","properties":{"event_type_name":{"type":"string"},"durations":{"type":"array","items":{"type":"integer"}},"question_names":{"type":"array","items":{"type":"string"}}}}},"invite_email":{"type":"string","nullable":true,"description":"MED-1365. The coach address that a Google invite fallback uses. Read on each request, not cached.\n"}}}}}}}}}},"401":{"description":"Authentication required"}}}},"/api/calendly/event-types":{"get":{"summary":"List coach's Calendly event types","description":"Fetches active event types from the coach's Calendly account for mapping to SGOS scheduling types","tags":["Calendly"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of event types","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"uri":{"type":"string"},"name":{"type":"string"},"duration":{"type":"integer"},"kind":{"type":"string","enum":["solo","group"]},"active":{"type":"boolean"},"scheduling_url":{"type":"string"}}}}}}}}},"401":{"description":"Authentication required"},"404":{"description":"Coach not connected to Calendly"}}}},"/api/calendly/event-type-mappings":{"put":{"summary":"Update event type mappings","description":"Map Calendly event types by duration (E22-6):\n- 20 min — check-ins\n- 45 min — 1:1 coaching sessions (45 min)\n- 60 min — 1:1 coaching sessions (60 min)\n","tags":["Calendly"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"calendly_event_type_uri_20min":{"type":"string","nullable":true,"description":"Calendly event type URI for 20-minute sessions (check-ins)"},"calendly_event_type_uri_45min":{"type":"string","nullable":true,"description":"Calendly event type URI for 45-minute sessions"},"calendly_event_type_uri_60min":{"type":"string","nullable":true,"description":"Calendly event type URI for 60-minute sessions"}}}}}},"responses":{"200":{"description":"Mappings updated"},"400":{"description":"A mapped event type has an enabled required question (MED-1363). The message names the question.\n"},"401":{"description":"Authentication required"},"404":{"description":"Coach profile not found"}}}},"/api/calendly/availability":{"get":{"summary":"Fetch available time slots","description":"Server-side proxy to Calendly's availability API. Results cached for 3 minutes\nto respect rate limits (~500 req/min). Calendly returns max 7-day windows.\n","tags":["Calendly"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"event_type_uri","required":true,"schema":{"type":"string"},"description":"Calendly event type URI"},{"in":"query","name":"start_time","required":true,"schema":{"type":"string","format":"date-time"},"description":"Start of availability window (ISO 8601)"},{"in":"query","name":"end_time","required":true,"schema":{"type":"string","format":"date-time"},"description":"End of availability window (ISO 8601, max 7 days from start)"}],"responses":{"200":{"description":"Available time slots","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string","enum":["available","invitees_remain"]},"invitees_remaining":{"type":"integer"},"start_time":{"type":"string","format":"date-time"},"end_time":{"type":"string","format":"date-time"},"scheduling_url":{"type":"string"}}}}}}}}},"400":{"description":"Missing required parameters"},"401":{"description":"Authentication required"}}}},"/api/coach/sessions/{sessionId}/active-state":{"get":{"summary":"Get composite state for the Active Session screen","description":"Returns everything the Active Session screen needs in one round-trip:\nsession metadata, group context, modality (for AC7 gating), sections,\nslide deck, leader notes, current section + cumulative timers, coach\nin-session notes, and the participants roster.\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Active session state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ActiveSessionState"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Coach not authorized for this session/group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/coach/sessions/{sessionId}/presence":{"post":{"summary":"Heartbeat — mark the coach present on the active session (MED-952)","description":"Lightweight presence heartbeat polled by the coach Active Session screen\n(~4s cadence) while the session is `in_progress`. Refreshes the coach's\nlast-seen timestamp in `sessions.coach_presence` and, if the coach is\nreturning after the session was unattended, closes the open\n`coach_absence_log` interval (AC9/AC11).\n\nDrives the participant \"coach has temporarily stepped away\" notice: when\nevery assigned coach stops heartbeating (tab/window closed, crash, or a\ndeliberate Leave), the freshness window lapses and participants see the\nnotice within one poll. No-op when the session is not `in_progress`.\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Presence recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"type":"string","enum":["scheduled","in_progress","completed","cancelled","rescheduled"]},"coach_present":{"type":"boolean","description":"True once this heartbeat is recorded (always true for an in_progress session)."}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Coach not authorized for this session/group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/coach/sessions/{sessionId}/preview-state":{"get":{"summary":"Get read-only state for the Active Session Preview screen (MED-873, MED-965)","description":"Returns the same composite payload as `/active-state` but is the canonical\nread endpoint for the Preview screen. It works for sessions in any status\n(scheduled / in_progress / completed / etc.) and performs no state\ntransitions or domain-event emissions.\n\n**Authorized callers:**\n- Coaches assigned to the session's group (or co-coaches via\n  `cohort_coaches`) — full payload as in MED-873.\n- Internal staff (`internal_admin` / `internal_user`) — added in MED-965 so\n  cohort managers can preview from the Session Content page. The\n  coach-ownership check is skipped; structural guards still apply (must be\n  a group session, modality must be coach-led). Per AC4 of MED-965,\n  coach-specific fields are blanked for these callers:\n  `coach_session_notes_html` → empty string,\n  `coach_session_notes_updated_at` → null,\n  `leader_notes` → empty array,\n  `participants` → empty array,\n  `coach_present` → false.\n\nIV1 contract: calling this endpoint MUST NOT mutate session state,\nadvance section index, or notify participants.\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Preview state (same shape as ActiveSessionState)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ActiveSessionState"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Caller is neither the assigned coach nor an internal_admin/internal_user","OR the session fails the group/coach-led structural guards":null},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/coach/sessions/{sessionId}/start":{"post":{"summary":"Start the session (AC6)","description":"Transitions a scheduled group session to `in_progress`. Enforces the\nT-15 readiness gate (per AC6 + MED-791 AC1). Initializes\n`current_section_index = 0` so AC5 timers start accumulating.\n\nIdempotent: calling on an `in_progress` session returns the current\nstate with 200 rather than 409.\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Started — returns the active state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ActiveSessionState"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Coach not authorized for this session/group"},"404":{"description":"Session not found OR live_session_delivery flag off"},"409":{"description":"SESSION_NOT_READY (called before T-15) or SESSION_INVALID_STATE\n(e.g. session is already completed / cancelled).\n"}}}},"/api/coach/sessions/{sessionId}/end":{"post":{"summary":"End the session (AC6)","description":"Transitions an `in_progress` group session to `completed`. Flushes the\ncurrent section's elapsed time into `section_time_seconds` before\nclosing so AC5 totals remain accurate.\n\nIdempotent on `completed` (returns current state).\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Ended — returns the active state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ActiveSessionState"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Coach not authorized for this session/group"},"404":{"description":"Session not found OR live_session_delivery flag off"},"409":{"description":"SESSION_INVALID_STATE — session not in_progress"}}}},"/api/coach/sessions/{sessionId}/current-section":{"patch":{"summary":"Advance/retreat/jump to a section (AC3 + AC5)","description":"Updates `current_section_index` on the session. Before updating,\nelapsed time in the prior section is added to `section_time_seconds`\n(AC5 cumulative behavior across nav-away-and-back).\n\nPer Decision A2 (auto-story 2026-05-14), AC3 push-to-participants is\ndeferred — this endpoint persists state and emits a Node domain event,\nbut does not push to participant sockets in this story.\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["section_index"],"properties":{"section_index":{"type":"integer","minimum":0,"description":"0-based index into the session_guide parsed_outline."}}}}}},"responses":{"200":{"description":"Updated — returns the active state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ActiveSessionState"}}}}}},"400":{"description":"Bad body"},"401":{"description":"Unauthorized"},"403":{"description":"Coach not authorized for this session/group"},"404":{"description":"Session not found OR live_session_delivery flag off"},"409":{"description":"SESSION_INVALID_STATE — session not in_progress","or section_index out of range":null}}}},"/api/coach/sessions/{sessionId}/current-slide":{"patch":{"summary":"Select the \"main\" slide for the shared whiteboard (MED-976 / E3.3a)","description":"Sets `current_slide_page` on the session — the slide-deck page rendered\nlarge on the live whiteboard. The page must be one of the CURRENT\nsection's associated slides. Persisted on the session and reflected in\nboth the coach and participant active-state payloads, so every attendee\nsees the same slide (\"shared with all\"). Pass `null` to clear the\nselection (falls back to the section's first associated slide).\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["slide_page"],"properties":{"slide_page":{"type":"integer","minimum":1,"nullable":true,"description":"1-based slide-deck PDF page, or null to clear."}}}}}},"responses":{"200":{"description":"Updated — returns the active state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ActiveSessionState"}}}}}},"400":{"description":"Bad body"},"401":{"description":"Unauthorized"},"403":{"description":"Coach not authorized for this session/group"},"404":{"description":"Session not found OR live_session_delivery flag off"},"409":{"description":"SESSION_INVALID_STATE — session not in_progress","or slide_page not in the current section":null}}}},"/api/coach/sessions/{sessionId}/coach-notes":{"patch":{"summary":"Persist coach in-session notes (AC9)","description":"Saves the coach's in-session notes (right panel WYSIWYG). HTML is\nsanitized server-side via `sanitizeRichTextHtml` (same pipeline as\nMED-793 leader notes). A plain-text mirror is derived and stored for\npost-session search/preview.\n","tags":["Coach Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content_html"],"properties":{"content_html":{"type":"string","description":"Raw HTML from the RichTextEditor; sanitized server-side."}}}}}},"responses":{"200":{"description":"Saved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"updated_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Bad body"},"401":{"description":"Unauthorized"},"403":{"description":"Coach not authorized for this session/group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/coaches":{"get":{"summary":"List all coaches","description":"Get a paginated list of coaches with optional search and status filter","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term for coach name or email"},{"in":"query","name":"coach_type","schema":{"type":"string","enum":["medley","partner"]},"description":"Filter by coach type"},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated status filter"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Coaches retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Coach"}},"pagination":{"type":"object"}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new coach","description":"Create a new coach (creates user + persona + coach_profile)","tags":["Coaches"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["first_name","last_name","email","coaching_specializations","location","home_base_timezone"],"properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"bio":{"type":"string"},"expertise_areas":{"type":"array","items":{"type":"string"}},"coaching_specializations":{"type":"string"},"location":{"type":"string"},"home_base_timezone":{"type":"string"}}}}}},"responses":{"201":{"description":"Coach created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Coach"}}}}}},"401":{"description":"Unauthorized"},"409":{"description":"Email already exists"},"422":{"description":"Validation error"}}}},"/coaches/options":{"get":{"summary":"Get form options","description":"Get dropdown options for coach forms (statuses, industries, etc.)","tags":["Coaches"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Options retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"statuses":{"type":"array","items":{"type":"string"}},"industries":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/coaches/check-email/{email}":{"get":{"summary":"Check if email is available","description":"Verify if an email can be used for a new coach","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"email","required":true,"schema":{"type":"string","format":"email"}}],"responses":{"200":{"description":"Email availability checked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"available":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"}}}},"/coaches/{id}":{"get":{"summary":"Get coach by ID","description":"Retrieve a single coach with full details","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Coach retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Coach"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"}}},"put":{"summary":"Update a coach","description":"Update coach profile information","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"phone":{"type":"string"},"bio":{"type":"string"},"expertise_areas":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"hourly_rate_cents":{"type":"integer"}}}}}},"responses":{"200":{"description":"Coach updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Coach"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"},"422":{"description":"Validation error"}}},"delete":{"summary":"Delete a coach","description":"Soft delete a coach","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Coach deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"}}}},"/coaches/{id}/cohorts":{"get":{"summary":"Get coach's cohorts","description":"Get current and past cohort assignments for a coach","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cohorts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"current":{"type":"array","items":{"type":"object"}},"past":{"type":"array","items":{"type":"object"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"}}}},"/coaches/{id}/activity":{"get":{"summary":"Get coach's activity log","description":"Get audit trail of changes for a coach","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Activity log retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"action":{"type":"string"},"changes":{"type":"object"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"}}}},"/coaches/{id}/avatar":{"post":{"summary":"Upload coach avatar","description":"Upload a new avatar image for a coach","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"avatar":{"type":"string","format":"binary"}}}}}},"responses":{"200":{"description":"Avatar uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"avatar_url":{"type":"string"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"}}}},"/coaches/{id}/send-invitation":{"post":{"summary":"Send SGOS invitation","description":"Send or resend an SGOS invitation email with a magic link to the coach","tags":["Coaches"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Coach profile ID"}],"responses":{"200":{"description":"Invitation sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sent":{"type":"boolean","description":"Whether the invitation was sent"},"isResend":{"type":"boolean","description":"Whether this was a resend of a previous invitation"},"expiresAt":{"type":"string","format":"date-time","description":"When the magic link expires"}}},"message":{"type":"string"}}}}}},"400":{"description":"Coach does not have an email address"},"401":{"description":"Unauthorized"},"404":{"description":"Coach not found"},"429":{"description":"Rate limit exceeded (max 5 requests per email per hour)"}}}},"/api/coach/availability-requests/token/{token}":{"get":{"summary":"Get availability request by magic link token","description":"Retrieve availability request details using a magic link token. No authentication required.","tags":["Coach Portal"],"parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"},"description":"Magic link token from email"}],"responses":{"200":{"description":"Availability request with cohort details"},"404":{"description":"Request not found or expired"}}}},"/api/coach/availability-requests/token/{token}/respond":{"post":{"summary":"Submit availability response via magic link","description":"Submit coach availability response using a magic link token. No authentication required.","tags":["Coach Portal"],"parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"},"description":"Magic link token from email"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["interest_status"],"properties":{"interest_status":{"type":"string","enum":["interested","not_interested"]},"slot_availability":{"type":"object","description":"Per-slot availability (required if interested)","additionalProperties":{"type":"object","properties":{"status":{"type":"string","enum":["full","partial","unavailable"]},"conflicts":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"reason":{"type":"string"}}}}}}},"max_groups":{"type":"integer","minimum":1,"description":"Number of groups the coach is willing to take (mutually exclusive with is_flexible_groups)"},"is_flexible_groups":{"type":"boolean","description":"If true, coach is willing to take as many groups as needed (mutually exclusive with max_groups)"},"additional_notes":{"type":"string"}}}}}},"responses":{"200":{"description":"Response submitted successfully"},"400":{"description":"Invalid request or already responded"},"404":{"description":"Request not found or expired"}}}},"/api/coach/dashboard/stats":{"get":{"summary":"Get coach dashboard statistics","description":"Get statistics for the coach dashboard (pending requests, active programs, etc.)","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Dashboard statistics","content":{"application/json":{"schema":{"type":"object","properties":{"pending_requests":{"type":"integer"},"active_programs":{"type":"integer"},"upcoming_sessions":{"type":"integer"},"completed_this_month":{"type":"integer"}}}}}},"401":{"description":"Unauthorized"}}}},"/api/coach/compensation":{"get":{"summary":"Get compensation overview for the logged-in coach (E13-4)","description":"Returns the coach's compensation data across all cohorts they're assigned to,\nincluding line items, cohort metadata (name, partner, dates), and a computed\ndisplay status. Only Medley coaches have access (partner coaches excluded).\n","tags":["Coach Portal","Compensation"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Compensation overview with cohorts array","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"cohorts":{"type":"array","items":{"type":"object","description":"CoachCompensationResultWithCohort + derived fields"}}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - not a Medley coach"}}}},"/coach/miss-claims":{"get":{"summary":"The caller's own 1:1 no-show and late change claims (MED-1346)","description":"Response { claims }. ?decision=pending gives the claims that wait for an answer.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}]}},"/api/coach/availability-requests":{"get":{"summary":"List availability requests for logged-in coach","description":"Get all availability requests for the authenticated coach, sorted by status and request date.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of availability requests"},"401":{"description":"Unauthorized"}}}},"/api/coach/availability-requests/{requestId}/timeline":{"get":{"summary":"Get cohort timeline for an availability request","description":"Returns the cohort timeline for the cohort associated with this availability request. Coach must own the request.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"requestId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The availability request ID"}],"responses":{"200":{"description":"Timeline data"},"401":{"description":"Unauthorized"},"404":{"description":"Request not found"}}}},"/api/coach/availability-requests/{requestId}":{"get":{"summary":"Get availability request details","description":"Get full details of an availability request including cohort information and time slots.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"requestId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Availability request ID"}],"responses":{"200":{"description":"Availability request with cohort details"},"401":{"description":"Unauthorized"},"404":{"description":"Request not found"}}}},"/api/coach/availability-requests/{requestId}/respond":{"post":{"summary":"Submit availability response","description":"Submit coach availability response for a specific request.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"requestId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Availability request ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["interest_status"],"properties":{"interest_status":{"type":"string","enum":["interested","not_interested"]},"slot_availability":{"type":"object","description":"Per-slot availability (required if interested)"},"max_groups":{"type":"integer","minimum":1,"description":"Number of groups the coach is willing to take (mutually exclusive with is_flexible_groups)"},"is_flexible_groups":{"type":"boolean","description":"If true, coach is willing to take as many groups as needed (mutually exclusive with max_groups)"},"additional_notes":{"type":"string"}}}}}},"responses":{"200":{"description":"Response submitted successfully"},"400":{"description":"Invalid request or already responded"},"401":{"description":"Unauthorized"},"404":{"description":"Request not found"}}}},"/api/coach/cohorts":{"get":{"summary":"Get cohorts for logged-in coach","description":"Returns cohorts where the coach is assigned to at least one group.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of cohorts with details","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string"},"status":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"partner_name":{"type":"string"},"program_name":{"type":"string"},"participant_count":{"type":"integer"},"coach_group_count":{"type":"integer"},"session_count":{"type":"integer"},"sessions_completed":{"type":"integer"},"live_session_delivery_enabled":{"type":"boolean","description":"MED-1181 — cohort's group_coaching Live Session Delivery gate; the dashboard ANDs this with the global flag."}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/coach/profile":{"get":{"summary":"Get the logged-in coach's own profile","description":"Returns the coach's profile data excluding internal notes.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Coach profile data"},"401":{"description":"Unauthorized"},"404":{"description":"Coach profile not found"}}},"put":{"summary":"Update the logged-in coach's own profile","description":"Allows coaches to update their own profile fields. Internal notes, status, and hourly rate are excluded.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"bio":{"type":"string"},"location":{"type":"string"},"home_base_timezone":{"type":"string"},"coaching_specializations":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated coach profile"},"401":{"description":"Unauthorized"},"404":{"description":"Coach profile not found"}}}},"/api/coach/profile/avatar":{"post":{"summary":"Upload the logged-in coach's own avatar","description":"Allows coaches to upload or replace their profile avatar image. Accepts JPEG, PNG, GIF, and WebP formats up to 5MB.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["avatar"],"properties":{"avatar":{"type":"string","format":"binary","description":"Avatar image file (JPEG, PNG, GIF, or WebP, max 5MB)"}}}}}},"responses":{"200":{"description":"Avatar uploaded successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"avatar_url":{"type":"string","description":"Signed URL to the uploaded avatar"}}}}}}}},"400":{"description":"No file provided or invalid file type"},"401":{"description":"Unauthorized"},"404":{"description":"Coach profile not found"}}}},"/api/coach/cohorts/{cohortId}":{"get":{"summary":"Get cohort detail for logged-in coach (E8-8)","description":"Returns cohort details including the coach's groups and sessions with attendance info.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Cohort details with groups and sessions","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string"},"status":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"partner_name":{"type":"string"},"program_name":{"type":"string"},"participant_count":{"type":"integer"},"session_count":{"type":"integer"},"sessions_completed":{"type":"integer"},"live_session_delivery_enabled":{"type":"boolean","description":"MED-1181 — cohort's group_coaching Live Session Delivery gate; hides Prep/Start button + PREP AVAILABLE badge for non-LSD cohorts."},"modalities":{"type":"array","description":"Enabled modality types for the cohort (MED-1106), used to gate the coach Sessions tab modality tabs.","items":{"type":"string","enum":["group_coaching","one_on_one","workshop"]}},"groups":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"participant_count":{"type":"integer"},"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer"},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"status":{"type":"string"},"topic_name":{"type":"string"},"attendance_marked":{"type":"boolean"},"attendance_summary":{"type":"object","nullable":true},"finalized_at":{"type":"string","format":"date-time","nullable":true},"rsvp_summary":{"type":"object","nullable":true,"properties":{"total":{"type":"integer"},"accepted":{"type":"integer"},"declined":{"type":"integer"},"pending":{"type":"integer"},"tentative":{"type":"integer"}}}}}}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found or no access"}}}},"/api/coach/cohorts/{cohortId}/participants":{"get":{"summary":"Get the participants of the coach's groups in a cohort (MED-1367)","description":"Read-only. Returns the current members of the groups the logged-in coach leads, withdrawn members included, with a fixed list of fields. Staff-only fields are never returned.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Participants of the coach's groups"},"401":{"description":"Unauthorized"},"403":{"description":"Not a coach or staff user"},"404":{"description":"Cohort not found or the coach leads no group in it"}}}},"/api/coach/cohorts/{cohortId}/groups":{"get":{"summary":"Get the groups the coach leads in a cohort (MED-1367)","description":"Read-only. Returns each group the logged-in coach leads, with attendance, engagement and current members.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The coach's groups with members"},"401":{"description":"Unauthorized"},"403":{"description":"Not a coach or staff user"},"404":{"description":"Cohort not found or the coach leads no group in it"}}}},"/api/coach/cohorts/{cohortId}/events":{"get":{"summary":"Get cohort events for the logged-in coach","description":"Returns cohort events (immersion, orientation, huddles, wrap-up) that the coach has been invited to. Read-only view.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of cohort events the coach is invited to"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/api/coach/cohorts/{cohortId}/events/{eventId}/attendees":{"get":{"summary":"Get attendees for a cohort event (coach view)","description":"Returns attendees for attendance marking by a coach","tags":["Coach Portal"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Attendee list for marking"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/api/coach/cohorts/{cohortId}/events/{eventId}/attendance":{"post":{"summary":"Mark attendance for a cohort event (coach)","description":"Coaches can mark attendance for cohort events they are invited to (MED-642 AC7)","tags":["Coach Portal"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attendance"],"properties":{"attendance":{"type":"array","items":{"type":"object","required":["email","status"],"properties":{"email":{"type":"string"},"status":{"type":"string","enum":["attended","partial","absent"]}}}}}}}}},"responses":{"200":{"description":"Attendance marked successfully"},"400":{"description":"Event has not ended or invalid input"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/api/coach/cohorts/{cohortId}/events/{eventId}/resources":{"get":{"summary":"Get resources for a cohort event (coach view)","description":"Returns resources linked to a cohort event that are visible to coaches (access_coaches = true)","tags":["Coach Portal"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of resources"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/api/coach/cohorts/{cohortId}/resources":{"get":{"summary":"Get resources for a cohort visible to the coach","description":"Returns resources for a cohort that the coach has access to (access_coaches = true). Includes cohort-specific and evergreen resources.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"query","name":"search","schema":{"type":"string"},"required":false,"description":"Search resources by title or description"}],"responses":{"200":{"description":"List of resources accessible to the coach","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"resource_type":{"type":"string","enum":["file","link"]},"url":{"type":"string","nullable":true},"download_url":{"type":"string","nullable":true},"category_name":{"type":"string","nullable":true},"is_evergreen":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/api/coach/sessions/pending-attendance":{"get":{"summary":"Get sessions needing attendance","description":"Returns sessions that have ended but attendance has not been marked. Used for the attendance callout banner (E8-9).","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of sessions needing attendance","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer","description":"Number of sessions needing attendance"},"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer"},"scheduled_start":{"type":"string","format":"date-time"},"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/coach/sessions/upcoming":{"get":{"summary":"Get upcoming sessions for the coach","description":"Returns the next 5 upcoming sessions (attendance not yet submitted) for the coach's assigned groups, sorted by `scheduled_start` ascending.\n\nMED-781: Each row includes `participant_count` (group_members count) so the dashboard's Needs Attention rail can render group size for the Starts Today and Prep Needed cards without an extra fetch.\n","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of upcoming sessions","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"participant_count":{"type":"integer","description":"Number of non-deleted members in the session's group"},"live_session_delivery_enabled":{"type":"boolean","description":"MED-1181 — session cohort's group_coaching Live Session Delivery gate; the Starts Today / Prep Needed cards AND this with the global flag."}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/coach/sessions/pending-feedback":{"get":{"summary":"Get sessions with an unsubmitted post-session Medley Feedback Form","description":"MED-867: Returns every completed live-session-delivery (MED02) session for the logged-in coach whose Medley Feedback Form is not yet submitted (`feedback_submitted = false`). Drives the multi-item \"Submit Session Feedback\" Needs Attention card — a deliberate sibling of the Overdue Attendance card.\n\nScope: only sessions that actually have a post-session Medley Feedback Form are returned — group sessions in a cohort whose `group_coaching` modality has `ai_features_enabled = true`. Pre-MED02 / non-facilitation sessions are structurally excluded. Drafts count as outstanding (the card retires only on Submit). A card also retires when its cohort becomes past/over (status `completed`/`archived`, matching the Active Cohorts non-past definition).\n\nGated by the `live_session_delivery` feature flag (MED-886): when the flag is off this route returns 404 and the query is never run.\n","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Sessions awaiting feedback submission","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"sessions":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"The `live_session_delivery` feature flag is off (MED-886)"}}}},"/api/coach/sessions/most-recent-completed":{"get":{"summary":"Get the coach's most recently completed session","description":"MED-781: Returns the single most recently completed session for the coach (attendance marked AND `scheduled_end < NOW()`), or `null` if none exists. Drives the \"Session Report Ready\" Needs Attention card on the coach home dashboard.\n\nRetirement rule lives on the client: the card stays visible until a newer session is completed (which produces a newer `scheduled_end` and supersedes the previous one). Opening the report does NOT retire the card.\n","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Most recently completed session, or null","content":{"application/json":{"schema":{"oneOf":[{"type":"null"},{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_number":{"type":"integer","nullable":true},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"attendance_marked_at":{"type":"string","format":"date-time","nullable":true},"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"topic_name":{"type":"string","nullable":true,"description":"Topic display name (topic_override falls back to topics.name)"}}}]}}}},"401":{"description":"Unauthorized"},"404":{"description":"The `live_session_delivery` feature flag is off (MED-886)"}}}},"/api/coach/sessions/{sessionId}/participants":{"get":{"summary":"Get session participants for attendance modal","description":"Returns participants for a session with their current attendance status. Used for the Mark Attendance modal (E8-10).","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"responses":{"200":{"description":"Session details with participants","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_title":{"type":"string"},"session_number":{"type":"integer"},"session_date":{"type":"string","format":"date-time"},"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"participants":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"current_status":{"type":"string","enum":["pending","attended","partial","absent"]},"current_absent_reason":{"type":"string","enum":["business_conflict","personal_conflict","no_notice"],"nullable":true},"current_notes":{"type":"string","nullable":true},"current_coach_notes":{"type":"string","nullable":true,"description":"MED-979 — coach-authored per-participant free-text note from the post-session screen."}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/coach/sessions/{sessionId}/attendance":{"get":{"summary":"Get attendance records for a session","description":"Returns current attendance records and summary for a session.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"responses":{"200":{"description":"Attendance records with summary","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_title":{"type":"string"},"is_marked":{"type":"boolean"},"summary":{"type":"object","properties":{"total":{"type":"integer"},"attended":{"type":"integer"},"partial":{"type":"integer"},"absent":{"type":"integer"},"pending":{"type":"integer"},"percentage":{"type":"number"}}},"attendees":{"type":"array","items":{"$ref":"#/components/schemas/SessionAttendance"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"post":{"summary":"Mark attendance for a session","description":"Submit attendance records for all participants in a session. Optionally triggers post-session surveys.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attendance","send_surveys"],"properties":{"attendance":{"type":"array","description":"Attendance records for each participant","items":{"type":"object","required":["user_id","status"],"properties":{"user_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["attended","partial","absent"]},"absent_reason":{"type":"string","enum":["business_conflict","personal_conflict","no_notice"],"description":"Required if status is absent"},"notes":{"type":"string","description":"Optional notes about attendance"},"coach_notes":{"type":"string","maxLength":5000,"nullable":true,"description":"MED-979 — optional per-participant free-text note authored by the coach on the post-session \"Attendance & Engagement\" screen. Independent of `notes`."}}}},"send_surveys":{"type":"boolean","description":"If true, triggers post-session surveys to attended/partial participants"}}}}}},"responses":{"200":{"description":"Attendance marked successfully","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"attendance_count":{"type":"integer"},"attended_count":{"type":"integer"},"partial_count":{"type":"integer"},"absent_count":{"type":"integer"},"attendance_percentage":{"type":"number"},"surveys_triggered":{"type":"boolean"}}}}}},"400":{"description":"Validation error (e.g., absent without reason)"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/coach/sessions/{sessionId}/feedback":{"get":{"summary":"Get coach's own session feedback","description":"Returns the logged-in coach's feedback and concern flags for a session. Available to both Medley executive coaches and partner coaches (MED-888); the question keys differ by coach_type but persistence is unified in coach_session_feedback.responses.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"responses":{"200":{"description":"Coach feedback with concern flags and engagement ratings","content":{"application/json":{"schema":{"type":"object","properties":{"feedback":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"responses":{"type":"object","description":"Key-value pairs of question answers"},"submitted_at":{"type":"string","format":"date-time"}}},"concernFlags":{"type":"array","items":{"type":"object"}},"engagementRatings":{"type":"array","description":"Per-participant engagement ratings (1-5 scale)","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"participant_id":{"type":"string","format":"uuid"},"participant_name":{"type":"string"},"rating":{"type":"integer","minimum":1,"maximum":5}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"post":{"summary":"Submit session feedback, concern flags, and engagement ratings","description":"Submit post-session reflection answers, optionally flag participant concerns, and rate participant engagement (1-5). Sets session.feedback_submitted flag.\n\nMED-888: Both Medley executive coaches and partner coaches submit through this route. Partner coaches send `responses` keyed by `partner_*` question ids (see `medley-app-react/src/config/coachFeedbackQuestionSets.ts`). Partner-coach validations enforced server-side:\n  - `partner_what_worked_well`, `partner_ideas_for_improvement`, `partner_participant_insights`, `partner_insights_for_self`, `partner_needs_before_next_session`, `partner_anything_else`: string, max 1000 chars\n  - `partner_group_engagement`: integer 1-5\n","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["responses"],"properties":{"responses":{"type":"object","description":"Key-value pairs of reflection question answers"},"concern_flags":{"type":"array","description":"Optional concern flags for participants","items":{"type":"object","required":["participant_id","concern_type"],"properties":{"participant_id":{"type":"string","format":"uuid"},"concern_type":{"type":"string","enum":["low_engagement","attendance_issues","disruptive_behavior","personal_crisis","conflict_with_group_member","technical_difficulties","struggling_with_content","not_completing_prework","flight_risk","other"]},"notes":{"type":"string"}}}},"engagement_ratings":{"type":"array","description":"Optional per-participant engagement ratings (1-5 scale)","items":{"type":"object","required":["participant_id","rating"],"properties":{"participant_id":{"type":"string","format":"uuid"},"rating":{"type":"integer","minimum":1,"maximum":5}}}}}}}}},"responses":{"200":{"description":"Feedback submitted"},"400":{"description":"Feedback already submitted or validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"put":{"summary":"Update session feedback, concern flags, and engagement ratings","description":"Update existing post-session feedback. Replaces concern flags and engagement ratings from this coach.\n\nMED-888: Same partner-coach validation rules apply as for POST (see `partner_*` keys, 1000-char limit on free-text, integer 1-5 on `partner_group_engagement`).\n","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["responses"],"properties":{"responses":{"type":"object"},"concern_flags":{"type":"array","items":{"type":"object","required":["participant_id","concern_type"],"properties":{"participant_id":{"type":"string","format":"uuid"},"concern_type":{"type":"string"},"notes":{"type":"string"}}}},"engagement_ratings":{"type":"array","description":"Optional per-participant engagement ratings (1-5 scale)","items":{"type":"object","required":["participant_id","rating"],"properties":{"participant_id":{"type":"string","format":"uuid"},"rating":{"type":"integer","minimum":1,"maximum":5}}}}}}}}},"responses":{"200":{"description":"Feedback updated"},"401":{"description":"Unauthorized"},"404":{"description":"No existing feedback found"}}}},"/api/coach/sessions/{sessionId}/notes":{"get":{"summary":"Get coach's personal notes for a session","description":"Returns the authenticated coach's private note for the specified session. Returns null content if no note exists.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"responses":{"200":{"description":"Coach's personal note (or null content if none)","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"coach_id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"content":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Unauthorized"}}},"put":{"summary":"Upsert coach's personal notes for a session","description":"Creates or updates the authenticated coach's private note for the specified session. Used by the auto-save feature.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"Rich-text HTML content of the note"}}}}}},"responses":{"200":{"description":"Note saved successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/api/coach/cohorts/{cohortId}/huddles":{"get":{"summary":"List huddles for the coach in a cohort","description":"Returns all 1:1 huddle sessions for the logged-in coach's groups in a specific cohort. Supports filtering by status and participant.","tags":["Coach Portal - Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"query","name":"status","schema":{"type":"string","enum":["scheduled","in_progress","completed","cancelled","rescheduled"]},"description":"Filter by session status"},{"in":"query","name":"participant_id","schema":{"type":"string","format":"uuid"},"description":"Filter by participant"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"List of huddles with pagination","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"huddle_type":{"type":"string","enum":["intro","midpoint_checkin","general"]},"participant_name":{"type":"string"},"participant_email":{"type":"string"},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"duration_minutes":{"type":"integer"},"status":{"type":"string"},"video_link":{"type":"string","nullable":true},"google_calendar_event_id":{"type":"string","nullable":true}}}},"pagination":{"type":"object"}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new huddle (1:1 session)","description":"Creates a new 1:1 huddle session between the coach and a participant. Optionally sends a Google Calendar invite.","tags":["Coach Portal - Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["group_id","participant_id","huddle_type","scheduled_start","duration_minutes","timezone","send_invite"],"properties":{"group_id":{"type":"string","format":"uuid","description":"Group the participant belongs to"},"participant_id":{"type":"string","format":"uuid","description":"Participant for the 1:1"},"huddle_type":{"type":"string","enum":["general"],"description":"Type of huddle. Only general. Intro and midpoint check-ins are 1:1 rows (MED-1350)."},"title":{"type":"string","description":"Custom title (required for general type)"},"scheduled_start":{"type":"string","format":"date-time","description":"Start date/time (ISO 8601)"},"duration_minutes":{"type":"integer","minimum":15,"maximum":120,"description":"Duration in minutes"},"video_link":{"type":"string","format":"uri","description":"Video conferencing link (Zoom/Teams/Meet)"},"timezone":{"type":"string","description":"IANA timezone (e.g., America/New_York)"},"send_invite":{"type":"boolean","description":"If true, sends Google Calendar invite to coach and participant"}}}}}},"responses":{"201":{"description":"Huddle created successfully"},"400":{"description":"Validation error (participant not in group, missing title for general type)"},"401":{"description":"Unauthorized"}}}},"/api/coach/huddles/{huddleId}":{"get":{"summary":"Get a single huddle by ID","description":"Returns full details of a 1:1 huddle session including participant info and calendar status.","tags":["Coach Portal - Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"huddleId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Huddle (session) ID"}],"responses":{"200":{"description":"Huddle details"},"401":{"description":"Unauthorized"},"404":{"description":"Huddle not found"}}},"put":{"summary":"Update a huddle","description":"Update title, time, duration, video link, or send/update calendar invite. Coach must own the group.","tags":["Coach Portal - Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"huddleId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Huddle (session) ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"scheduled_start":{"type":"string","format":"date-time"},"duration_minutes":{"type":"integer","minimum":15,"maximum":120},"video_link":{"type":"string","format":"uri","nullable":true},"timezone":{"type":"string"},"send_invite":{"type":"boolean","description":"If true, sends or updates Google Calendar invite"}}}}}},"responses":{"200":{"description":"Huddle updated successfully"},"401":{"description":"Unauthorized"},"403":{"description":"Coach does not own this huddle"},"404":{"description":"Huddle not found"}}},"delete":{"summary":"Cancel a huddle","description":"Cancels a huddle session and sends calendar cancellation if a Google Calendar event exists. Coach must own the group.","tags":["Coach Portal - Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"huddleId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Huddle (session) ID"}],"responses":{"200":{"description":"Huddle cancelled"},"401":{"description":"Unauthorized"},"403":{"description":"Coach does not own this huddle"},"404":{"description":"Huddle not found"}}}},"/api/coach/huddles/{huddleId}/attendance":{"post":{"summary":"Mark attendance for a huddle","description":"Record whether the participant attended or did not attend the 1:1 huddle session.","tags":["Coach Portal - Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"huddleId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Huddle session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["attended","did_not_attend"],"description":"Attendance status"},"notes":{"type":"string","description":"Optional attendance notes"}}}}}},"responses":{"200":{"description":"Attendance recorded successfully"},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"403":{"description":"Coach does not own this huddle"},"404":{"description":"Huddle not found"}}}},"/api/coach/huddles/{huddleId}/feedback":{"post":{"summary":"Save coach feedback for a huddle","description":"Save post-huddle coach notes for a 1:1 session.","tags":["Coach Portal - Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"huddleId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Huddle session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["notes"],"properties":{"notes":{"type":"string","description":"Post-huddle coach feedback notes"}}}}}},"responses":{"200":{"description":"Feedback saved successfully"},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"403":{"description":"Coach does not own this huddle"},"404":{"description":"Huddle not found"}}}},"/api/coach/cohorts/{cohortId}/feedback/{surveyType}":{"get":{"summary":"Get coach's own feedback data for a midpoint/endpoint survey","description":"Returns survey config, questions, and the current coach's own submission (if any)","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["midpoint","endpoint"]}}],"responses":{"200":{"description":"Coach feedback data retrieved"},"404":{"description":"Cohort or config not found"}}},"post":{"summary":"Submit coach feedback for a midpoint/endpoint survey","description":"Creates a new feedback submission with the coach's responses","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["midpoint","endpoint"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["responses"],"properties":{"responses":{"type":"object","description":"Map of question_id to response value"}}}}}},"responses":{"201":{"description":"Feedback submitted successfully"},"400":{"description":"Already submitted or invalid data"},"403":{"description":"Coach not assigned to this cohort"}}}},"/api/coach/cohorts/{cohortId}/workshops":{"get":{"summary":"Get workshops assigned to the logged-in coach for a cohort","description":"Returns workshop definitions with session data where the coach is the facilitator. Used to populate the Workshops tab on the coach dashboard.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"List of workshops assigned to the coach","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"session_status":{"type":"string"},"scheduled_start":{"type":"string","format":"date-time"},"participant_count":{"type":"integer"}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/coach/cohorts/{cohortId}/workshops/{workshopId}/participants":{"get":{"summary":"Get participants for a workshop the coach facilitates","description":"Returns participant list for a workshop. Coach must be the assigned facilitator.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of workshop participants"},"401":{"description":"Unauthorized"},"404":{"description":"Workshop not found or coach is not the facilitator"}}}},"/api/coach/groups/{groupId}/booking-links":{"get":{"summary":"List the public booking links of a group (MED-1319)","description":"Returns one link for each touchpoint the cohort uses: intro, midpoint_checkin, and one one_on_one link for each 1:1 session length. Missing links are created on read. Only the coach of the group and staff can read them. When links is empty, empty_reason is cohort_closed, group_inactive, checkins_off or no_bookable_modality (MED-1353). Otherwise it is null.\n","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The links and the coach's Calendly state"},"403":{"description":"The caller is not the coach of this group"},"404":{"description":"Group not found"}}}},"/api/coach/groups/{groupId}/booking-links/{linkId}/regenerate":{"post":{"summary":"Replace a public booking link with a new token (MED-1319)","description":"The old token stops working at once.","tags":["Coach Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"linkId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The new link"},"403":{"description":"The caller is not the coach of this group"},"404":{"description":"The link is not a live link of this group"}}}},"/api/coach/sessions/{sessionId}/post-session":{"get":{"summary":"Get the composite post-session bundle (AC2)","description":"Single-round-trip fetch for the post-session screen. Returns the\nsession metadata, participants with attendance status, engagement\nratings, the coach's feedback (if any), and the per-participant\ncommitments.\n","tags":["Coach Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Bundle for the post-session screen","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PostSessionBundle"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/coach/sessions/{sessionId}/post-session/commitments":{"put":{"summary":"Bulk upsert per-participant commitments (AC5)","description":"Coach fill-in of a commitment for each listed participant. MED-1216:\nthe card now saves one row at a time (per-row Save), and a commitment\nmust be non-empty (whitespace-only is rejected, 422/400). The coach may\nfill in a commitment when none exists or edit a coach-authored one, but\nmay NOT overwrite a participant-authored row — such a write is rejected\nwith 409 so the client refreshes and shows the participant's own entry\n(\"saved by participant\").\n\nAI follow-up scheduling on saved entries → MED-893.\n","tags":["Coach Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"commitments":{"type":"array","items":{"type":"object","required":["participant_user_id","commitment_text"],"properties":{"participant_user_id":{"type":"string","format":"uuid"},"commitment_text":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Returns the post-upsert list of active commitments","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"commitments":{"type":"array","items":{"$ref":"#/components/schemas/PostSessionCommitment"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found OR live_session_delivery flag off"},"409":{"description":"Commitment is participant-authored and cannot be overwritten by a coach (MED-1216 / AC4)."},"422":{"description":"Validation failed (includes empty commitment_text — MED-1216 / AC1)."}}}},"/api/coach/sessions/{sessionId}/post-session/recap-email/send":{"post":{"summary":"Send the coach-authored recap email to session participants (MED-901 / AC12-AC15)","description":"Sends a recap email to the listed session participants. The coach\ncomposes Subject + Body HTML in the \"Send Recap Email\" modal and may\ninclude any of the seven recap-email merge tags\n(`{{first_name}}`, `{{cohort_name}}`, `{{session_number}}`,\n`{{session_date}}`, `{{group_name}}`, `{{topic_name}}`,\n`{{coach_name}}`). Tags are resolved **server-side** per recipient —\nnull values become empty strings, so the literal tag text never reaches\na participant (AC15).\n\nRecipients are intersected with the session's group membership before\nsending — IDs not in the group are silently dropped and reported in\n`skipped_not_in_session`.\n\nAuthorization: only the coach assigned to the session's group can call\nthis endpoint. Calls from other coaches receive 403. 1:1 sessions\n(no group) also receive 403 — recap email is group-only this story.\n\nMED-893 (post-session AI features) flows the AI-generated body through\nthis same endpoint without changes.\n","tags":["Coach Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["recipient_user_ids","subject","body_html"],"properties":{"recipient_user_ids":{"type":"array","minItems":1,"items":{"type":"string","format":"uuid"},"description":"User IDs of session participants to send to. Filtered to current group membership."},"subject":{"type":"string","minLength":1,"maxLength":250,"description":"Coach-authored subject line. May include merge tags — resolved server-side per recipient."},"body_html":{"type":"string","minLength":1,"maxLength":50000,"description":"Coach-authored body HTML from the RichTextEditor. May include merge tags — resolved server-side per recipient."}}}}}},"responses":{"200":{"description":"Send completed. Returns counts.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sent":{"type":"integer","description":"Successful queue publishes"},"failed":{"type":"integer","description":"Failed queue publishes"},"skipped_not_in_session":{"type":"integer","description":"Recipient IDs that were not current members of the session's group"}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Caller is not the coach assigned to this session","or session is 1:1 (no group)":null},"404":{"description":"Session not found OR live_session_delivery flag off"},"422":{"description":"Validation failed"}}}},"/api/coach/sessions/{sessionId}/prep":{"get":{"tags":["Coach Session Prep"],"summary":"Get the coach prep payload for a session","description":"Returns the composite payload for the Session Prep page (AC4):\nsession metadata, parsed agenda outline (from MED-784 content),\ncoach prep state, leader notes, group contexts,\nand the AI features flag. Authorization requires the caller to be\nassigned to the session's cohort via `cohort_coaches` and the\nsession's modality (if any) to be coach-led (IV3).\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Prep payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CoachPrepPayload"}}}}}},"401":{"description":"Unauthorized (not logged in)"},"403":{"description":"Coach is not assigned to this session","or session is not coach-led":null},"404":{"description":"Session not found"},"500":{"description":"Internal error"}}}},"/api/coach/sessions/{sessionId}/prep/heartbeat":{"post":{"summary":"Record the coach's waiting-room presence heartbeat (MED-1126)","description":"Stamps the coach's `session_attendance` presence heartbeat\n(`last_seen_at`, and `joined_at` on first contact) so the coach shows\nas present in the participant waiting room while prepping. Coaches have\nno waiting room of their own — the prep page pings this on the same ~4s\ncadence the participant waiting room polls. Coach-led sessions only;\nreuses the prep authorization gate.\n","tags":["Coach Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Presence heartbeat recorded"},"400":{"description":"Invalid sessionId format"},"401":{"description":"Unauthorized (not logged in)"},"403":{"description":"Coach is not assigned to this session","or session is not coach-led":null},"404":{"description":"Session not found"},"500":{"description":"Internal error"}}}},"/api/coach/sessions/{sessionId}/prep/mark-reviewed":{"post":{"tags":["Coach Session Prep"],"summary":"Mark this coach's prep for the session as reviewed (AC11)","description":"Sets `marked_reviewed_at = now()` and, if this is the coach's first-ever\nreviewed session, also sets `first_prep_completed_at` to suppress the\nAC5 first-time strong-prompt on future sessions.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Updated prep state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CoachSessionPrep"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"},"410":{"description":"Session is live — prep is no longer accessible (AC6)"}}},"delete":{"tags":["Coach Session Prep"],"summary":"Toggle off \"Marked As Reviewed\" for this coach × session","description":"Clears `marked_reviewed_at` so the coach can keep editing prep. AC11.","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Updated prep state"},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"},"410":{"description":"Session is live"}}}},"/api/coach/sessions/{sessionId}/leader-notes":{"put":{"tags":["Coach Session Prep"],"summary":"Upsert one leader-notes entry for a specific agenda step (AC9)","description":"Upserts the (coach, session, block_key, scope[, group_id]) row.\nPassing an empty `content_html` soft-deletes the row.\nPer AC8: scope='all_groups' shares the note across all of the coach's\ngroups for this session; scope='group' creates a per-group overlay.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["block_key","scope","content_html"],"properties":{"block_key":{"type":"string","example":"0:1"},"scope":{"$ref":"#/components/schemas/CoachLeaderNotesScope"},"group_id":{"type":"string","format":"uuid","nullable":true},"content_html":{"type":"string","description":"Sanitized HTML from the RichTextEditor. Empty string soft-deletes."}}}}}},"responses":{"200":{"description":"Saved (or null if soft-deleted)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"oneOf":[{"$ref":"#/components/schemas/CoachSessionLeaderNote"},{"type":"null"}]}}}}}},"400":{"description":"Invalid body / scope-group_id mismatch"},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"},"410":{"description":"Session is live"}}}},"/api/coach/sessions/{sessionId}/rsvp":{"put":{"tags":["Coach Session Prep"],"summary":"Record the coach's RSVP for this session (AC2)","description":"Records the coach's RSVP. A declined RSVP becomes the trigger for\nStory E4.0 reassignment (IV5) — this endpoint stores the declined\nstate and the index `idx_coach_session_prep_declined` makes the\ndeclined set discoverable for the downstream service when it ships.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["accepted","declined","tentative"]},"decline_reason":{"type":"string","description":"Optional. Only meaningful when status=declined."}}}}}},"responses":{"200":{"description":"Updated prep state"},"400":{"description":"Invalid status"},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"},"410":{"description":"Session is live"}}}},"/api/coach/sessions/{sessionId}/prep/checklist":{"patch":{"tags":["Coach Session Prep"],"summary":"Patch the \"Before you start\" checklist state","description":"Merges a partial set of boolean flags into the prep's checklist_state\nJSONB. v1 checklist keys: reviewed_group_sentiment, checked_agenda,\nnoted_carryover, confirmed_attendance.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["patch"],"properties":{"patch":{"type":"object","additionalProperties":{"type":"boolean"}}}}}}},"responses":{"200":{"description":"Updated prep state"},"400":{"description":"Invalid body"},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"},"410":{"description":"Session is live"}}}},"/api/coach/sessions/{sessionId}/prep-sheet.pdf":{"get":{"tags":["Coach Session Prep"],"summary":"Print-ready Prep Sheet PDF (AC13)","description":"Generates a text-only PDF of the coach's session agenda, including\nStep Content and Leader Notes across all steps (All Groups + per-group\noverlays). Excludes slides, attachments, and Session Materials per\nAC13. Streamed inline so the browser hands bytes to the viewer.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"PDF document","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"}}}},"/api/coach/sessions/{sessionId}/ai-listening":{"patch":{"tags":["Coach Session Prep"],"summary":"MED-876 AC3 — toggle AI Listening for this group's session","description":"Toggles `sessions.ai_listening_enabled` for the specified group session.\nRejected when the cohort's modality has `ai_features_enabled=false`\n(409 `AI_FEATURES_DISABLED`) or when the lock window has been reached\n— `scheduled_start - 15 min` — at which point the value is frozen so\nMED-792 / MED-872 can determine AI disclosure requirements before\nparticipants enter (409 `AI_LISTENING_LOCKED`). Per AC3, coach UI\ncontrol of this toggle is out of scope (see MED-866); V1 enforces\nonly the existing cohort-coach assignment.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated AI Listening state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"ai_listening_enabled":{"type":"boolean"},"ai_listening_locked":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid body"},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"},"409":{"description":"AI features disabled OR lock window reached"}}}},"/api/coach/sessions/{sessionId}/prep/ai-selections":{"patch":{"tags":["Coach Session Prep"],"summary":"MED-876 AC2 / IV3 — replace the In Use prompt selections doc","description":"Replaces this coach's `coach_session_prep.ai_prompt_selections` JSONB\ndocument atomically. The document is keyed by agenda segment\n`block_key`; each segment carries independent boolean toggles for the\nthree primary prompt slots (`purpose`, `speaking`, `stall_recovery`)\nand an optional `speaking_override` set when the coach picked one of\nthe two ALTERNATIVE PROMPT rows via \"Use This\". The AC1 banner count\nand the AC4 PDF inclusion both derive from this document. Rejected\n(409 `AI_FEATURES_DISABLED`) when the cohort's modality has\n`ai_features_enabled=false`.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["selections"],"properties":{"selections":{"type":"object","additionalProperties":{"type":"object","properties":{"selections":{"type":"object","properties":{"purpose":{"type":"boolean"},"speaking":{"type":"boolean"},"stall_recovery":{"type":"boolean"}}},"speaking_override":{"type":"object","nullable":true,"properties":{"prompt":{"type":"string"},"angle":{"type":"string"}}}}}}}}}}},"responses":{"200":{"description":"Updated prompt selections","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"ai_prompt_selections":{"type":"object"}}}}}}}},"400":{"description":"Invalid body"},"401":{"description":"Unauthorized"},"403":{"description":"Not assigned / not coach-led"},"404":{"description":"Session not found"},"409":{"description":"AI features disabled"},"410":{"description":"Session is live"}}}},"/api/cohorts/{cohortId}/billcom/coaches":{"get":{"summary":"Get coach vendor mapping status for a cohort","description":"Returns all assigned coaches with their Bill.com vendor mapping status.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Coach vendor status list"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/coaches/unmapped":{"get":{"summary":"Get unmapped coaches for a cohort","description":"Returns coaches not yet linked to a Bill.com vendor (payment blockers).","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Unmapped coaches list"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments":{"get":{"summary":"Get all payments for a cohort","description":"Returns all scheduled, processing, and completed payments for a cohort.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"payment_track","description":"Comma-separated tracks. flat_rate_monthly returns only the Flat Rate rows.","schema":{"type":"string"}},{"in":"query","name":"status","description":"Comma-separated statuses.","schema":{"type":"string"}}],"responses":{"200":{"description":"Payment list"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/schedule":{"post":{"summary":"Schedule flat-rate payments for a cohort","description":"Creates bills and schedules payments in Bill.com for all confirmed coach compensation. Returns a job ID for progress tracking.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Payment scheduling job created","content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"total_payments":{"type":"integer"}}}}}},"400":{"description":"Unmapped coaches or no confirmed compensation"},"401":{"description":"Unauthorized"},"409":{"description":"Payments already scheduled"}}}},"/api/cohorts/{cohortId}/billcom/payments/schedule/preview":{"get":{"summary":"Preview the flat-rate payments that scheduling would create","description":"MED-1383. Returns the bill count and total for the confirm dialog. Writes nothing.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The count and total that scheduling would create"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/coaches/{coachUserId}/void":{"post":{"summary":"Void future payments for a coach","description":"Voids all pending/scheduled future payments for a coach in a cohort (e.g., on coach reassignment).","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payments voided"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/summary":{"get":{"summary":"Get cohort-level payment summary","description":"Returns aggregate payment statistics including total confirmed, paid, remaining, and next payment date.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment summary"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/{paymentId}/audit":{"get":{"summary":"Get payment status change audit trail","description":"Returns all status change events for a specific payment with timestamps and sources.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"paymentId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Audit trail entries"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/{paymentId}/retry":{"post":{"summary":"Retry a failed payment","description":"Re-creates bill and payment in Bill.com for a previously failed payment.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"paymentId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Payment retried and rescheduled"},"400":{"description":"Payment is not in a failed state"},"401":{"description":"Unauthorized"},"404":{"description":"Payment not found"}}}},"/api/cohorts/{cohortId}/billcom/payments/net10/pending":{"get":{"summary":"Get pending net-10 payments for a cohort","description":"Returns attendance-dependent payments that are owed but not yet submitted to Bill.com. Computed on-demand from attendance and compensation data.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Pending net-10 payment list"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/net10/submitted":{"get":{"summary":"Get submitted net-10 payments for a cohort","description":"Returns net-10 payments already submitted to Bill.com with their current status.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Submitted net-10 payment list"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/net10/submit":{"post":{"summary":"Submit a single net-10 payment to Bill.com","description":"Creates a bill and schedules payment in Bill.com for a single attendance-dependent payment.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event_reference_id","event_reference_type","coach_user_id"],"properties":{"event_reference_id":{"type":"string","format":"uuid"},"event_reference_type":{"type":"string","enum":["cohort_event","session"]},"coach_user_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Payment submitted successfully"},"400":{"description":"Invalid request or coach not mapped to vendor"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/payments/net10/submit-all":{"post":{"summary":"Submit all pending net-10 payments to Bill.com","description":"Creates a job that submits all pending net-10 payments sequentially. Returns job ID for progress tracking.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Bulk submission job created","content":{"application/json":{"schema":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"total_payments":{"type":"integer"}}}}}},"400":{"description":"No submittable payments found"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/jobs/latest":{"get":{"summary":"Get latest payment scheduling job for a cohort","description":"Returns the most recent payment job of one track with progress info.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"payment_track","schema":{"type":"string","enum":["flat_rate_monthly","attendance_dependent_net_10"],"default":"flat_rate_monthly"}}],"responses":{"200":{"description":"Latest job info"},"401":{"description":"Unauthorized"}}}},"/api/cohorts/{cohortId}/billcom/jobs/{jobId}":{"get":{"summary":"Get payment scheduling job progress","description":"Returns progress details for a specific payment scheduling job.","tags":["Cohort Payments"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"jobId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Job progress details"},"401":{"description":"Unauthorized"},"404":{"description":"Job not found"}}}},"/api/cohorts/{cohortId}/coaches/{coachUserId}/availability-response":{"put":{"summary":"Submit availability response on behalf of a coach","description":"Allows a Cohort Manager to add or edit a coach's availability response. Records attribution (who entered it and when).","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The cohort ID"},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The coach's user ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["interest_status"],"properties":{"interest_status":{"type":"string","enum":["interested","not_interested"]},"slot_availability":{"type":"object","additionalProperties":{"type":"object","properties":{"status":{"type":"string","enum":["full","partial","unavailable"]},"conflicts":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"reason":{"type":"string"}}}}}}},"max_groups":{"type":"integer","minimum":1},"is_flexible_groups":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Availability response saved. Returns full coach detail for modal."},"400":{"description":"Invalid input or validation error"},"404":{"description":"Coach or availability request not found"},"500":{"description":"Internal server error"}}}},"/api/cohorts/{cohortId}/compensation":{"get":{"summary":"Get compensation configuration for a cohort","description":"Returns the compensation config, resolved rates (global merged with overrides),\nand the cohort's modality profile — which modalities are enabled, which 1:1\nsession types and durations are configured, and whether in-person events exist.\nThe modality profile (MED-731) is used by the Configure Compensation modal to\nfilter rate rows down to only the ones relevant to this cohort.\n","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Compensation config retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"config":{"nullable":true,"type":"object"},"resolved_rates":{"nullable":true,"type":"object"},"cohort_modalities":{"type":"array","items":{"type":"string","enum":["group_coaching","one_on_one","workshop"]}},"session_duration_minutes":{"type":"integer","nullable":true},"configured_one_on_one_session_types":{"type":"array","description":"1:1 session types actually configured on the cohort (MED-731). Drives which 1:1 SDA rows appear in the Configure Compensation modal.","items":{"type":"string","enum":["standard_1_1","two_on_one","360_debrief","hogan_debrief","other_assessment_debrief"]}},"configured_one_on_one_durations":{"type":"array","description":"1:1 session durations (minutes) configured on the cohort. Unused durations are hidden in the modal.","items":{"type":"integer"}},"has_in_person_events":{"type":"boolean","description":"True when the cohort has at least one in_person_booster event. Gates the In-Person Half/Full Day rate rows."}}}}}},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/compensation/configure":{"put":{"summary":"Configure compensation for a cohort","description":"Creates or updates the cohort's compensation config with audience type and optional rate overrides. Rejects if compensation is already confirmed.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["audience_type"],"properties":{"audience_type":{"type":"string","enum":["emerging","established","executive"]},"rate_overrides":{"type":"object","description":"Sparse rate overrides (same shape as rate card rates)"}}}}}},"responses":{"200":{"description":"Configuration saved"},"400":{"description":"Invalid input"},"404":{"description":"Cohort not found"},"409":{"description":"Compensation is confirmed - unlock first"}}}},"/api/cohorts/{cohortId}/compensation/confirm":{"post":{"summary":"Confirm compensation configuration","description":"Snapshots the resolved rates and locks the configuration. Cannot be changed until unlocked.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Compensation confirmed"},"404":{"description":"Config not found"},"409":{"description":"Already confirmed"}}}},"/api/cohorts/{cohortId}/compensation/unlock":{"post":{"summary":"Unlock a confirmed compensation configuration","description":"Clears the confirmed state and rate snapshot, allowing the config to be re-edited.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Compensation unlocked"},"400":{"description":"Not confirmed"},"404":{"description":"Config not found"}}}},"/api/cohorts/{cohortId}/compensation/coach/{coachUserId}/override":{"put":{"summary":"Set or reset a per-coach compensation override","description":"Sets a custom compensation amount for availability request emails. Pass null to reset.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["override_cents"],"properties":{"override_cents":{"type":"integer","nullable":true,"description":"Custom compensation amount in cents, or null to reset"}}}}}},"responses":{"200":{"description":"Override set successfully"},"404":{"description":"Coach or cohort not found"}}}},"/api/cohorts/{cohortId}/compensation/calculation":{"get":{"summary":"Get compensation calculation results for a cohort","description":"Returns the latest per-coach compensation calculation results with line-item breakdowns and cohort-level totals.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Calculation results retrieved"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/compensation/calculation/export-csv":{"get":{"summary":"Export compensation results as CSV (E13-4)","description":"Synchronously generates a CSV file containing compensation data for all active coaches\nin the cohort, with cohort metadata as a header block and one row per coach.\n","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"CSV file stream","content":{"text/csv":{"schema":{"type":"string"}}}},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/compensation/calculate":{"post":{"summary":"Trigger compensation recalculation","description":"Recalculates per-coach compensation for all assigned coaches in the cohort. Uses confirmed rate snapshot and current group/session data.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Calculation completed successfully"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/compensation/calculation/coach/{coachUserId}":{"get":{"summary":"Get a single coach's compensation calculation","description":"Returns detailed compensation breakdown for a specific coach including all line items.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Coach calculation retrieved"},"404":{"description":"Cohort or calculation not found"}}}},"/api/cohorts/{cohortId}/compensation/calculation/coach/{coachUserId}/confirm":{"post":{"summary":"Confirm a coach's compensation result","description":"Sets a system_calculated result to confirmed status. Any Medley user can confirm.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Compensation confirmed"},"400":{"description":"Result is not in system_calculated status"},"404":{"description":"Result not found"}}}},"/api/cohorts/{cohortId}/compensation/calculation/coach/{coachUserId}/adjust-confirm":{"post":{"summary":"Adjust line items and confirm a coach's compensation","description":"Applies inline adjustments to specific line items and confirms the result. Save = confirm.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["adjustments"],"properties":{"adjustments":{"type":"array","items":{"type":"object","required":["line_item_id","adjusted_total_cents"],"properties":{"line_item_id":{"type":"string","format":"uuid"},"adjusted_total_cents":{"type":"integer","minimum":0}}}}}}}}},"responses":{"200":{"description":"Adjustments applied and compensation confirmed"},"400":{"description":"Invalid adjustments or result is pending"},"404":{"description":"Result not found"}}}},"/api/cohorts/{cohortId}/compensation/calculation/bulk-confirm":{"post":{"summary":"Bulk confirm multiple coach compensation results","description":"Confirms all specified system_calculated results in a single operation.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["result_ids"],"properties":{"result_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"Results confirmed with count and updated summary"},"400":{"description":"Invalid result IDs"}}}},"/api/cohorts/{cohortId}/compensation/calculation/coach/{coachUserId}/audit":{"get":{"summary":"Get audit log for a coach's compensation","description":"Returns all audit entries (confirmations, adjustments, resets) for a coach's compensation result.","tags":["Cohort Compensation"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Audit entries retrieved"}}}},"/cohorts/{cohortId}/events":{"get":{"summary":"List all cohort events","description":"Returns all cohort-wide events (immersion, orientation, huddles, wrap-up) with RSVP summaries","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"List of cohort events with RSVP summaries"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/events/huddle":{"post":{"summary":"Create a new coach huddle event","description":"Creates a new coach huddle in the cohort_events table and updates the cohort's coach_huddle_dates array. The huddle can optionally have a scheduled date/time.","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"scheduled_start":{"type":"string","format":"date-time","nullable":true,"description":"Optional date/time for the huddle (ISO 8601)"}}}}}},"responses":{"201":{"description":"Coach huddle created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortEvent"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"500":{"description":"Internal server error"}}}},"/cohorts/{cohortId}/events/{eventId}":{"get":{"summary":"Get a cohort event by ID","description":"Returns a single cohort event with full details","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cohort event details"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/datetime":{"put":{"summary":"Update event date/time","description":"Updates the scheduled date/time for a cohort event. If invites have been sent, also updates the Google Calendar event.","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduled_start"],"properties":{"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"timezone":{"type":"string"}}}}}},"responses":{"200":{"description":"Event date/time updated"},"400":{"description":"Invalid input"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/invitations":{"post":{"summary":"Send calendar invitations for a cohort event","description":"Sends Google Calendar invitations to selected coaches, participants, and external emails. Creates invitation records and updates event status to SCHEDULED.","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"video_link":{"type":"string","description":"Video conferencing URL"},"scheduled_start":{"type":"string","format":"date-time","description":"Set date/time if not already scheduled"},"scheduled_end":{"type":"string","format":"date-time"},"timezone":{"type":"string"},"coach_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"participant_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"additional_emails":{"type":"array","items":{"type":"string","format":"email"}}}}}}},"responses":{"200":{"description":"Invitations sent successfully"},"400":{"description":"Invalid input or no recipients"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}},"get":{"summary":"Get RSVP list for a cohort event","description":"Returns all invitations with RSVP status details and summary counts","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"RSVP list with summary"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/invitations/summary":{"get":{"summary":"Get RSVP summary counts for a cohort event","description":"Returns aggregated RSVP counts (accepted, declined, pending, tentative)","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"RSVP summary counts"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/resend-invites":{"post":{"summary":"Resend invitations for a cohort event","description":"Adds new recipients to an already-sent event. Updates Google Calendar attendee list.","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"coach_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"participant_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"additional_emails":{"type":"array","items":{"type":"string","format":"email"}}}}}}},"responses":{"200":{"description":"Invitations resent"},"400":{"description":"Invites not yet sent"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/attendees":{"get":{"summary":"Get the attendance roster for a cohort event","description":"Returns the cohort roster for the event (coaches, plus participants for orientation, wrap-up, and in-person booster) with saved attendance, the Bill.com paid flag, and the coach pay for each coach row (MED-1349)","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Attendee list for marking"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/attendance":{"post":{"summary":"Mark attendance for a cohort event","description":"Records attendance for every required person on the roster. Available once the event has started. Sets event status to COMPLETED (MED-1349).","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attendance"],"properties":{"attendance":{"type":"array","items":{"type":"object","required":["email","status"],"properties":{"email":{"type":"string","format":"email"},"user_id":{"type":"string","format":"uuid"},"invitation_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["attended","partial","absent"]},"absent_reason":{"type":"string","enum":["business_conflict","personal_conflict","no_notice"]},"notes":{"type":"string"}}}},"cleared_user_ids":{"type":"array","description":"Users whose saved status the CM cleared. Only these saved rows are removed; a person left out of attendance keeps the saved row.","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"Attendance marked successfully"},"400":{"description":"Event has not started, a required person has no status, an attendee is not on the roster, or an absence has no reason"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/notes":{"get":{"summary":"Get internal notes for a cohort event","description":"Returns all internal notes for a cohort event, visible to Medley staff only","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of internal notes with author details"},"404":{"description":"Event not found"}}},"post":{"summary":"Create an internal note for a cohort event","description":"Adds a new internal note. Only visible to Medley staff.","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"Note content (HTML)"}}}}}},"responses":{"201":{"description":"Note created successfully"},"400":{"description":"Content is required"},"401":{"description":"Unauthorized"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/resources":{"get":{"summary":"Get resources for a cohort event","description":"Returns all resources linked to a cohort event","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of resources"},"404":{"description":"Event not found"}}},"post":{"summary":"Link a resource to a cohort event","description":"Associates an existing cohort resource with this event","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resource_id"],"properties":{"resource_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Resource linked to event"},"400":{"description":"Invalid resource or not in cohort"},"404":{"description":"Event not found"}}}},"/cohorts/{cohortId}/events/{eventId}/resources/{resourceId}":{"delete":{"summary":"Remove a resource from a cohort event","description":"Removes the association between a resource and this event (does not delete the resource)","tags":["Cohort Events"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"eventId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"resourceId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resource removed from event"},"404":{"description":"Event not found"}}}},"/api/cohorts/{cohortId}/matching-sessions":{"post":{"summary":"Create a matching session for a cohort (leg A)","description":"Always creates a new `matching_sessions` row for the calling admin (1:N model -- spec §14 step 6).\nIf the cohort has an active matching attempt with another admin, the new row joins it via the\nexisting `external_session_id` and matching-server is asked to re-mint a navigation token.\nOtherwise a fresh session is created on matching-server with the full §9.1 payload.\n\nPer §11.7 SGOS does NOT retry leg (C) automatically. On any failure the row's status\ntransitions to `failed`; the admin retries by clicking the button again (which creates a\nnew row).\n","tags":["MatchingSessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Session created (create-mode) or joined an existing attempt (re-mint mode).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMatchingSessionResponse"}}}},"400":{"description":"Pre-flight validation failure. Codes:\n- `COHORT_NO_INTAKE_FORM` -- cohort has no `intake_form_id` configured.\n- `NO_ELIGIBLE_PARTICIPANTS` -- no participant has non-empty `available_time_slots`.\n"},"401":{"description":"Unauthorized -- missing or invalid SGOS admin session."},"404":{"description":"Cohort not found."},"502":{"description":"Outbound matching-server call failed (per spec §13 error matrix). Codes:\n- `MATCHING_SERVER_AUTH_REJECTED` -- matching-server returned 401 (secret drift; oncall alert emitted).\n- `MATCHING_SERVER_UNREACHABLE` -- network failure or non-2xx other than 401.\n- `MATCHING_SERVER_INVALID_RESPONSE` -- response missing required fields.\n"},"503":{"description":"`MATCHING_SERVER_NOT_CONFIGURED` -- matching-server env vars are missing\n(only possible in non-production deployments).\n"}}},"get":{"summary":"List matching sessions for a cohort (MED-829 / E23-8)","description":"Returns all `matching_sessions` rows for the cohort that are NOT\n`expired` and NOT soft-deleted, sorted by `updated_at DESC`. Each row\nincludes the owner's display fields (first_name, last_name, avatar_url)\nfetched via a single LEFT JOIN. The cohort Groups tab sessions panel\npolls this endpoint every 30 s.\n\nThe list is intentionally small (per cohort, typically 1-3 rows) so\nthere is no pagination.\n","tags":["MatchingSessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Array of matching session list items.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/MatchingSession"},{"type":"object","properties":{"owner_first_name":{"type":"string","nullable":true},"owner_last_name":{"type":"string","nullable":true},"owner_avatar_url":{"type":"string","nullable":true}}}]}}}}}}},"401":{"description":"Unauthorized — missing or invalid SGOS admin session."},"404":{"description":"Cohort not found."}}}},"/api/cohorts/{cohortId}/matching-sessions/readiness":{"get":{"summary":"Check whether matching can be started for a cohort (MED-829 / E23-8)","description":"Pre-flight readiness probe used by the cohort Groups tab to gate the\nStart Matching button. Mirrors the same checks that\n`MatchingPayloadBuilder.build()` would throw on, so admins see the\nblockers before clicking instead of via a 4xx toast after.\n\nChecks (in priority order):\n- Cohort has `intake_form_id` configured.\n- At least one active, non-deleted participant has non-empty\n  `available_time_slots`.\n- The eligible-participant count is greater than or equal to the\n  cohort's `matching_min_group_size` (default 4 when NULL).\n\nAlways returns 200 with the readiness shape — never returns a 4xx for\n\"not ready\" cases. 404 only if the cohort itself doesn't exist.\n","tags":["MatchingSessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Readiness result.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","required":["ready","blocking_reasons","meta"],"properties":{"ready":{"type":"boolean"},"blocking_reasons":{"type":"array","items":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["COHORT_NO_INTAKE_FORM","NO_ELIGIBLE_PARTICIPANTS","INSUFFICIENT_PARTICIPANTS_FOR_MATCHING"]},"message":{"type":"string"}}}},"meta":{"type":"object","properties":{"eligible_participant_count":{"type":"integer"},"intake_form_id":{"type":"string","nullable":true},"effective_min_group_size":{"type":"integer"}}}}}}}}}},"401":{"description":"Unauthorized — missing or invalid SGOS admin session."},"404":{"description":"Cohort not found."}}}},"/cohorts/{cohortId}/public-bookings":{"get":{"summary":"List public bookings of a cohort that did not attach to a session","description":"MED-1389. Bookings made through a group booking link that hold a\ncoach's time but have no session: not canceled and not yet over.\nStaff see every booking of the cohort. MED-1399: a coach sees only\nthe bookings on their own links, and own=true gives staff the same\n(the coach page). Allowed under impersonation (QA round 2). With\nreplace, the member's upcoming booking that \"Replace current\nbooking\" would cancel (session_id, scheduled_start, coach_name,\nmember_name), or null.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The bookings, soonest first"},"403":{"description":"Not staff or a coach of this cohort"},"422":{"description":"Invalid cohort ID"}}}},"/cohorts/{cohortId}/public-bookings/{bookingId}/members":{"get":{"summary":"List the members an unattached public booking can be linked to","description":"MED-1399. The active members of the booking link group and of the\nother groups of the booking's coach in the cohort, the same rule as\nthe link. Staff, or the coach of the booking link. Allowed under\nimpersonation (QA round 2).\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"bookingId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"user_id, first_name, last_name, email and group_name of each member, by name"},"403":{"description":"Not staff or the coach of the booking"},"404":{"description":"No unattached booking with this id in this cohort"},"422":{"description":"Invalid id"}}}},"/cohorts/{cohortId}/public-bookings/{bookingId}/link":{"post":{"summary":"Link an unattached public booking to a cohort member","description":"MED-1399. Fills the member's earliest open session of the booking's\nkind (1:1 of the link length, intro or midpoint check-in) with the\nbooking, as a booking with the member's email would have. No email\nand no calendar change. Staff, or the coach of the booking link.\nAllowed under impersonation (QA round 2). Never replaces a booking.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"bookingId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["participant_id"],"properties":{"participant_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"The booking now holds the member's session"},"400":{"description":"not_in_group, the person is not an active member of a group of this coach"},"403":{"description":"Not staff or a coach of this cohort"},"404":{"description":"No unattached booking with this id in this cohort (for a coach, on their links)"},"409":{"description":"no_unscheduled_session: the member has no open session of this\nkind. already_linked, booking_not_linkable or row_taken: the\nbooking or the session changed.\n"},"422":{"description":"Invalid id"}}}},"/cohorts/{cohortId}/public-bookings/{bookingId}/add-session":{"post":{"summary":"Add a 1:1 session for a member and link an unattached booking to it","description":"MED-1417. For a 1:1 booking of a member who has no open 1:1 of the\nbooking's kind. Adds a 1:1 row with the booking's coach and length,\nand fills it with the booking. No email and no calendar change. Staff\nonly, because the session counts toward coach pay. Refused under\nimpersonation.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"bookingId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["participant_id"],"properties":{"participant_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"The new session holds the booking"},"400":{"description":"not_in_group, the person is not an active member of a group of this coach"},"403":{"description":"Not staff, or impersonating"},"404":{"description":"No unattached booking with this id in this cohort"},"409":{"description":"has_open_session: use the link instead. already_scheduled,\nno_one_on_one_sessions, not_one_on_one, already_linked,\nbooking_not_linkable or row_taken: nothing was changed.\n"},"422":{"description":"Invalid id"}}}},"/cohorts/{cohortId}/public-bookings/{bookingId}/replace":{"post":{"summary":"Replace a member's upcoming booking with an unattached public booking","description":"MED-1399 QA round 2, \"Replace current booking\". For a booking left\nunattached as already_scheduled, when the member's booking of that\nkind is still to come. Cancels that booking (Calendly cancel or Google\ninvite delete, with notices), then puts this booking on its session.\nThe body names the booking the confirm showed, from the list. Staff,\nor the coach of the booking link. Allowed under impersonation (QA round 3).\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"bookingId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["session_id","scheduled_start"],"properties":{"session_id":{"type":"string","format":"uuid"},"scheduled_start":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"The session now holds this booking; the old booking is canceled"},"403":{"description":"Not staff or the coach of the booking"},"404":{"description":"No unattached booking with this id in this cohort (for a coach, on their links)"},"409":{"description":"replace_changed or row_taken: something changed, nothing was\nchanged. replace_not_linked: the old booking was canceled, but this\nbooking could not be linked; use the link.\n"},"422":{"description":"Invalid id or start"},"502":{"description":"invite_cancel_failed, the old booking could not be canceled; nothing changed"}}}},"/cohorts/{cohortId}/public-bookings/{bookingId}/cancel":{"post":{"summary":"Cancel an unattached public booking","description":"MED-1389. Cancels the Calendly event, or deletes the Google Calendar\ninvite of a fallback booking, then marks the booking canceled. A\nbooking that is already canceled, here or in the vendor, answers 200.\nMED-1399 QA round 3: staff, or the coach of the booking link.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"bookingId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The booking is canceled"},"403":{"description":"Not staff or the coach of the booking"},"404":{"description":"No unattached booking with this id in this cohort (for a coach, on their links)"},"409":{"description":"calendly_booking_not_reachable: the booking is on a Calendly\naccount the coach no longer has connected; use remove. row_taken:\nthe booking changed.\n"},"422":{"description":"Invalid id"},"502":{"description":"The vendor did not confirm the cancel; the booking stays"}}}},"/cohorts/{cohortId}/public-bookings/{bookingId}/remove":{"post":{"summary":"Remove an unattached public booking that Calendly no longer shows","description":"MED-1389. Marks the booking canceled in Medley only, with no vendor\ncancel. It reads the invitee in Calendly again first and removes the\nbooking only when Calendly answers 403 or 404. MED-1399 QA round 3:\nstaff, or the coach of the booking link.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"bookingId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The booking is removed from the list"},"403":{"description":"Not staff or the coach of the booking"},"404":{"description":"No unattached booking with this id in this cohort"},"409":{"description":"Calendly can still read the booking (booking_reachable), or it changed"},"422":{"description":"Invalid id"},"502":{"description":"Calendly did not answer; the booking stays"}}}},"/cohorts":{"get":{"summary":"List all cohorts","description":"Get a paginated list of cohorts with optional filtering","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term for cohort name"},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated status filter"},{"in":"query","name":"partnerId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"programId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Cohorts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Cohort"}},"pagination":{"type":"object"}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new cohort","description":"Create a new cohort with modalities and configuration","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["partner_id","name","modalities"],"properties":{"partner_id":{"type":"string","format":"uuid"},"program_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"short_name":{"type":"string"},"code":{"type":"string"},"description":{"type":"string"},"cohort_type":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"preferred_days":{"type":"array","items":{"type":"string"}},"preferred_times":{"type":"array","items":{"type":"string"}},"participant_capacity":{"type":"integer"},"modalities":{"type":"array","items":{"type":"object"}}}}}}},"responses":{"201":{"description":"Cohort created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Cohort"}}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/cohorts/check-code/{code}":{"get":{"summary":"Check if cohort code is available","description":"Verify if a cohort code can be used","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string"},"description":"Cohort code to check (2-20 characters)"}],"responses":{"200":{"description":"Code availability checked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"available":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/check-availability":{"get":{"summary":"Check if cohort name or short name is available","description":"Case-insensitive uniqueness check for cohort name and/or short name","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"name","schema":{"type":"string"},"required":false,"description":"Cohort name to check"},{"in":"query","name":"short_name","schema":{"type":"string"},"required":false,"description":"Cohort short name to check"}],"responses":{"200":{"description":"Availability checked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"nameAvailable":{"type":"boolean"},"shortNameAvailable":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/status-counts":{"get":{"summary":"Get cohort counts by status","description":"Returns count of cohorts grouped by status","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Status counts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","additionalProperties":{"type":"integer"}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/{id}":{"get":{"summary":"Get cohort by ID","description":"Retrieve a single cohort by its UUID","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cohort retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Cohort"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"put":{"summary":"Update a cohort","description":"Update an existing cohort's basic information","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"short_name":{"type":"string"},"code":{"type":"string"},"description":{"type":"string"},"status":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"is_active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Cohort updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Cohort"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}},"delete":{"summary":"Delete a cohort","description":"Soft delete a cohort. Before flagging the cohort deleted, its AI-derived data (transcripts, airtime, themes, recap drafts, recap draft edits) is purged for all of the cohort's sessions — including soft-deleted ones — reusing the manual B3 purge (MED-1220). The purge is fail-closed: if it fails, the cohort is NOT deleted (it remains until the purge succeeds) and the error is returned. Each purge is recorded to cohort_ai_purge_audit_log.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Cohort deleted successfully (AI data purged first)"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error (id is not a valid UUID)"},"500":{"description":"AI-data purge failed; the cohort was NOT deleted (fail-closed). Retry the delete once the underlying issue is resolved — the purge is idempotent."}}}},"/cohorts/{id}/duplicate":{"post":{"summary":"Duplicate a cohort","description":"Create a copy of an existing cohort with the same modality configuration.\nThe duplicated cohort will have:\n- Same partner and program association\n- Same modality configuration (with workshops)\n- Same scheduling preferences (preferred days/times)\n- Name set to \"[Original Name] (copy)\" unless custom name provided\n- New auto-generated cohort code\n- No dates (start/end dates cleared)\n- No participants, groups, or coach assignments\n- Status set to 'draft'\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"ID of the cohort to duplicate"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Custom name for the duplicated cohort (optional)","example":"Leadership Cohort Q2 2025"}}}}}},"responses":{"201":{"description":"Cohort duplicated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Cohort"},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Source cohort not found"},"409":{"description":"Cohort name already exists for this program"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/overview-stats":{"get":{"summary":"Get overview statistics for a cohort","description":"Get aggregated stats for the cohort overview tab including sessions, participants, groups, tasks, milestones, and recent activity","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Overview stats retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sessions":{"type":"object","properties":{"completed":{"type":"integer"},"total":{"type":"integer"},"progressPercent":{"type":"integer"}}},"participants":{"type":"object","properties":{"total":{"type":"integer"},"active":{"type":"integer"}}},"groups":{"type":"object","properties":{"count":{"type":"integer"},"avgParticipantsPerGroup":{"type":"integer"},"changeThisWeek":{"type":"integer","nullable":true}}},"tasks":{"type":"object","properties":{"completed":{"type":"integer"},"total":{"type":"integer"},"progressPercent":{"type":"integer"}}},"milestones":{"type":"array","items":{"type":"object"}},"recentActivity":{"type":"array","items":{"type":"object"}},"health":{"type":"object"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/modality-progress":{"get":{"summary":"Get modality progress summary for a cohort","description":"Returns per-modality session completion stats for announced modalities. Used by the Modality Progress section on the cohort dashboard (MED-570).","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Modality progress retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"modality_type":{"type":"string","enum":["group_coaching","one_on_one","workshop"]},"label":{"type":"string"},"completed":{"type":"integer"},"total":{"type":"integer"},"percentage":{"type":"integer"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/health-summary":{"get":{"summary":"Get health summary with alerts for a cohort","description":"Computes health alerts on-the-fly from attendance, engagement, feedback, and survey data. Returns tiered alerts (risk, monitor, on-track) with metrics and trends.","tags":["Cohort Health"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Health summary retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"overallStatus":{"type":"string","enum":["at_risk","monitor","on_track"]},"statusMessage":{"type":"string"},"metrics":{"type":"object"},"tiers":{"type":"object"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/attendance-drilldown":{"get":{"summary":"Get attendance drill-down data for a cohort","description":"Returns per-participant, per-session, and per-group attendance data for the Attendance stat card drill-down (MED-425). Includes session indicators, status classification, trends, and SGOS email tracking.","tags":["Cohort Health"],"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid"},"required":true,"description":"Cohort ID"}],"responses":{"200":{"description":"Attendance drill-down data","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/satisfaction-drilldown":{"get":{"summary":"Get satisfaction drill-down data for a cohort","description":"Returns participant survey satisfaction distribution, coach engagement ratings, individual survey response rows, and coach status reports for the Satisfaction stat card drill-down (MED-426).","tags":["Cohort Health"],"parameters":[{"in":"path","name":"id","schema":{"type":"string","format":"uuid"},"required":true,"description":"Cohort ID"}],"responses":{"200":{"description":"Satisfaction drill-down data","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/health-alerts/dismiss":{"post":{"summary":"Dismiss a health alert","description":"Marks a specific health alert as dismissed for a cohort. The alert will still be computed but flagged as dismissed.","tags":["Cohort Health"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ruleKey","entityId"],"properties":{"ruleKey":{"type":"string","description":"The rule key of the alert to dismiss"},"entityId":{"type":"string","description":"The entity ID associated with the alert (UUID or composite key)"},"notes":{"type":"string","description":"Optional notes about the dismissal"},"triggerSnapshot":{"type":"object","description":"Snapshot of trigger data at dismissal time"}}}}}},"responses":{"200":{"description":"Alert dismissed successfully"},"400":{"description":"Bad request - missing required fields"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/health-alerts/address":{"post":{"summary":"Mark a health alert as addressed","description":"Marks a health alert as addressed with an optional note. Also addresses the corresponding participant flag if one exists.","tags":["Cohort Health"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ruleKey","entityId"],"properties":{"ruleKey":{"type":"string"},"entityId":{"type":"string","description":"Entity ID (UUID or composite key)"},"note":{"type":"string","description":"Optional note about how the concern was addressed"},"triggerSnapshot":{"type":"object"}}}}}},"responses":{"200":{"description":"Alert marked as addressed"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/health-alerts/restore":{"post":{"summary":"Restore a dismissed or addressed health alert","description":"Removes the dismissal record (soft-delete) so the alert becomes active again. Does not re-open any previously addressed participant flags.","tags":["Cohort Health"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ruleKey","entityId"],"properties":{"ruleKey":{"type":"string","description":"The rule key of the alert to restore"},"entityId":{"type":"string","description":"The entity ID associated with the alert (UUID or composite key)"}}}}}},"responses":{"200":{"description":"Alert restored successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"restored":{"type":"boolean"}}},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/health-alerts/dismiss-improvement":{"post":{"summary":"Dismiss an on-track improvement item","description":"Manually dismiss a health improvement item from the On Track section (MED-418). This hides the item from future health summary responses.","tags":["Cohort Health"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["historyId"],"properties":{"historyId":{"type":"string","format":"uuid","description":"The health_alert_history record ID to dismiss"}}}}}},"responses":{"200":{"description":"Improvement item dismissed successfully"},"400":{"description":"Bad request - historyId is required"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/flags":{"get":{"summary":"List active participant flags for a cohort","description":"Returns all active (unresolved) participant flags from all sources (coach and system)","tags":["Participant Flags"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"List of active flags"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"post":{"summary":"Create a new participant flag","description":"Creates a flag for a participant (coach or system source)","tags":["Participant Flags"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["participant_id","source_type","source_id","flag_category","flag_reason"],"properties":{"participant_id":{"type":"string","format":"uuid"},"source_type":{"type":"string","enum":["coach","system"]},"source_id":{"type":"string"},"flag_category":{"type":"string","enum":["attendance","participant_feedback","engagement","coach_flag"]},"flag_reason":{"type":"string"},"session_id":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Flag created"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/flags/participant/{participantId}":{"get":{"summary":"Get active flags for a participant","description":"Returns all active flags for a specific participant in a cohort","tags":["Participant Flags"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Participant user ID"}],"responses":{"200":{"description":"List of active flags for participant"},"401":{"description":"Unauthorized"}}}},"/cohorts/{id}/flags/session/{sessionId}":{"get":{"summary":"Get active flags for a session","description":"Returns all active flags associated with a specific session","tags":["Participant Flags"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"responses":{"200":{"description":"List of active flags for session"},"401":{"description":"Unauthorized"}}}},"/cohorts/{id}/flags/{flagId}/dismiss":{"patch":{"summary":"Dismiss a participant flag","description":"Marks a flag as dismissed (ignore, don't show anymore)","tags":["Participant Flags"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"flagId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Flag ID"}],"responses":{"200":{"description":"Flag dismissed"},"401":{"description":"Unauthorized"},"404":{"description":"Flag not found or already resolved"}}}},"/cohorts/{id}/flags/{flagId}/address":{"patch":{"summary":"Mark a participant flag as addressed","description":"Marks a flag as addressed with an optional note","tags":["Participant Flags"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"flagId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Flag ID"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"note":{"type":"string","description":"Optional note about how the concern was addressed"}}}}}},"responses":{"200":{"description":"Flag marked as addressed"},"401":{"description":"Unauthorized"},"404":{"description":"Flag not found or already resolved"}}}},"/cohorts/{id}/flags/{flagId}/history":{"get":{"summary":"Get history for a participant flag","description":"Returns the complete event history for a flag (created, dismissed, addressed, auto-resolved)","tags":["Participant Flags"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"flagId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Flag ID"}],"responses":{"200":{"description":"Flag history events"},"401":{"description":"Unauthorized"},"404":{"description":"Flag not found"}}}},"/cohorts/{id}/tasks/count":{"get":{"summary":"Get task counts for a cohort","description":"Get counts of total, template-generated, and manual tasks for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Task counts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total":{"type":"integer"},"templateTasks":{"type":"integer"},"manualTasks":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/phases":{"get":{"summary":"Get phases applicable to a cohort","description":"Get phases that have templates matching this cohort's type and modalities","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Phases retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskPhase"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/regenerate-tasks":{"post":{"summary":"Regenerate tasks for a cohort","description":"Regenerate tasks from templates for an existing cohort. Supports two modes - skip_existing (default) and replace_template_tasks.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","enum":["skip_existing","replace_template_tasks"],"description":"skip_existing (default) adds only new templates, replace_template_tasks deletes all template tasks and recreates"}}}}}},"responses":{"200":{"description":"Tasks regenerated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"tasksGenerated":{"type":"integer"},"tasksDeleted":{"type":"integer"},"mode":{"type":"string"}}},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/regenerate-due-dates":{"post":{"summary":"Regenerate task due dates","description":"Recalculate task due dates after cohort date changes.\nOnly updates tasks where:\n- Task has a template reference\n- Task due_date_manually_set = false (not manually edited by user)\n- Task status is not 'complete' or 'cancelled'\n\nManually edited due dates are preserved.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Due dates regenerated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"updated_count":{"type":"integer","description":"Number of tasks that had their due dates updated"},"eligible_count":{"type":"integer","description":"Total number of tasks eligible for regeneration"},"skipped_manual_count":{"type":"integer","description":"Number of tasks skipped because due date was manually set"}}},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/regenerate-team-assignments":{"post":{"summary":"Regenerate task assignees after team assignment changes","description":"Re-resolves task assignees from the updated team assignment mapping.\nOnly updates tasks where assignee_manually_set = false,\nstatus is not complete/cancelled, and job_title_id matches a changed role.\n","tags":["Cohorts"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["changedJobTitleIds"],"properties":{"changedJobTitleIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Job title IDs whose team assignment changed"}}}}}},"responses":{"200":{"description":"Regeneration results","content":{"application/json":{"schema":{"type":"object","properties":{"updatedCount":{"type":"integer"},"eligibleCount":{"type":"integer"},"skippedManualCount":{"type":"integer"}}}}}},"400":{"description":"Bad request"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/modalities":{"get":{"summary":"Get cohort modalities","description":"Retrieve all modalities for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Modalities retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CohortModality"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"put":{"summary":"Update cohort modalities","description":"Replace all modalities for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["modalities"],"properties":{"modalities":{"type":"array","items":{"type":"object","properties":{"modality_type":{"type":"string"},"session_count":{"type":"integer"},"session_frequency":{"type":"string"},"configuration":{"type":"object"}}}}}}}}},"responses":{"200":{"description":"Modalities updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/modalities/{modalityId}/settings":{"patch":{"summary":"Update modality settings (coaching sub-type, AI features, commitment tracking)","description":"MED-848 — Non-destructive update of a single modality's coaching sub-type,\nAI features toggle, Live Session Delivery gate, and (MED-1069) Commitment\nTracking toggle. Used by the Edit Cohort flow.\n\nEditability rules:\n- `ai_features_enabled` is editable any time post-creation (AC8)\n- `coaching_sub_type` is editable only until the modality is locked by\n  either of the following independent conditions (returns 400 once locked):\n    1. **MED-848 AC9** — `invites_sent=true` (session prep / announcement\n       emails have already gone out for this modality).\n    2. **MED-955 AC1/3/5** — any session in the cohort has a designated\n       `session_leader_id` (Coach Led / Peer Led leader has been assigned).\n       Reported as: _\"Modality type cannot be changed after session leaders have been assigned.\"_\n- `coaching_sub_type` is only valid for `modality_type='group_coaching'`. Returns 400 otherwise.\n\nGated by the `live_session_delivery` feature flag (MED-886). When the\nflag is off the endpoint returns 404.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"modalityId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Modality ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"coaching_sub_type":{"type":"string","enum":["coach_led","peer_led"],"nullable":true,"description":"Group coaching sub-type. Only valid when the modality is group_coaching.\nLocked once `invites_sent=true` (MED-848 AC9) OR once any session in\nthe cohort has a designated `session_leader_id` (MED-955 AC1/3/5).\n"},"ai_features_enabled":{"type":"boolean","description":"Whether AI-powered facilitation features are enabled for this modality."},"live_session_delivery_enabled":{"type":"boolean","description":"MED-948 (E1.10a) — per-modality Live Session Delivery in SGOS gate.\nWhen set to false, `ai_features_enabled` is reset to false server-side\n(AC8) so no stale \"enabled-but-hidden\" state persists. Distinct from the\nsystem-level `live_session_delivery` feature flag (MED-886).\n"},"commitment_tracking_enabled":{"type":"boolean","description":"MED-1069 (E1.10c) — per-modality Commitment Tracking toggle\n(group_coaching only). Enables the participant \"Reflect on Prior\nCommitments\" step and per-session commitment field for the cohort's\ngroup sessions. INDEPENDENT of `live_session_delivery_enabled` — it is\nNOT reset when that gate is turned off (unlike `ai_features_enabled`).\n"}}}}}},"responses":{"200":{"description":"Modality settings updated"},"400":{"description":"Validation failed, sub-type set on non-group_coaching modality, or sub-type\nlocked. Two lock reasons map to distinct error messages:\n  - _\"Modality type cannot be changed after session leaders have been assigned.\"_ (MED-955)\n  - _\"Coaching sub-type is locked because session prep emails have already been sent for this modality\"_ (MED-848 AC9)\n"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or modality not found, OR the `live_session_delivery` feature flag is off (MED-886)"},"422":{"description":"Request body validation error"}}}},"/cohorts/{id}/modalities/{modalityId}/announce-prerequisites":{"get":{"summary":"Validate announcement prerequisites for a modality","description":"Check whether all prerequisites are met before announcing a modality to participants. Returns validation result with specific blocking items if prerequisites are not satisfied.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"modalityId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Modality ID"}],"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","properties":{"valid":{"type":"boolean"},"errors":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or modality not found"}}}},"/cohorts/{id}/modalities/{modalityId}/announce":{"post":{"summary":"Announce a modality to participants","description":"Marks a modality as finalized and visible to participants. Optionally sends SGOS magic link invitation emails. Prerequisites must be satisfied before announcement.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"modalityId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Modality ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sendInvites"],"properties":{"sendInvites":{"type":"boolean","description":"If true, sends SGOS magic link emails to all active participants. If false, finalizes without emailing (skip invites)."}}}}}},"responses":{"201":{"description":"Modality announced successfully"},"400":{"description":"Prerequisites not met or validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or modality not found"},"409":{"description":"Modality already announced"}}}},"/cohorts/{id}/announce-all-prerequisites":{"get":{"summary":"Validate announcement prerequisites for all unannounced modalities","description":"Returns per-modality readiness status for cohort-level announce. Checks each unannounced modality for prerequisite satisfaction.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Per-modality prerequisite status"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/announce-all":{"post":{"summary":"Announce all ready modalities in a cohort","description":"Announces all configured modalities simultaneously. Sends a single consolidated invitation email to participants. Optionally accepts specific modality IDs to announce only a subset.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sendInvites"],"properties":{"sendInvites":{"type":"boolean","description":"If true, sends a consolidated invitation email to all active participants"},"modalityIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Optional subset of modality IDs to announce. If omitted, all ready modalities are announced."}}}}}},"responses":{"201":{"description":"Modalities announced successfully"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/announcement-email/template":{"get":{"summary":"Get default announcement email template","description":"Returns the default program announcement email template with merge tag definitions. Template sections are filtered based on cohort modalities.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Default template with merge tag definitions"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/announcement-email/preview":{"post":{"summary":"Preview announcement email with resolved merge tags","description":"Resolves merge tags using cohort data and a sample participant. Returns the rendered email preview (plain text with tags resolved) and any unresolvable tags.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","body"],"properties":{"subject":{"type":"string"},"body":{"type":"string"}}}}}},"responses":{"200":{"description":"Rendered email preview with resolved merge tags"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/announcement-email/send":{"post":{"summary":"Send announcement emails to all active participants","description":"Resolves merge tags per participant and sends individualized announcement emails via Resend. Plain text body is converted to HTML for email rendering. Logs each send for audit purposes.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","body"],"properties":{"subject":{"type":"string"},"body":{"type":"string"}}}}}},"responses":{"200":{"description":"Emails sent successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/announcement-email/test":{"post":{"summary":"Send a test announcement email to the logged-in user","description":"Sends a test email to the CM's own email address with merge tags resolved using sample data. Subject is prefixed with [TEST].","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","body"],"properties":{"subject":{"type":"string"},"body":{"type":"string"}}}}}},"responses":{"200":{"description":"Test email sent to CM"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/scheduling-preferences":{"put":{"summary":"Update scheduling preferences","description":"Update preferred days and times for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"preferred_days":{"type":"array","items":{"type":"string"}},"preferred_times":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Scheduling preferences updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/session-config":{"put":{"summary":"Update session configuration","description":"Update session count, frequency, and scheduling preferences for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"session_count":{"type":"integer","minimum":1,"description":"Number of coaching sessions"},"session_frequency":{"type":"string","enum":["weekly","biweekly","monthly"],"description":"Frequency of coaching sessions"},"preferred_days":{"type":"array","items":{"type":"string"},"description":"Preferred days for sessions"},"preferred_times":{"type":"array","items":{"type":"string"},"description":"Preferred times for sessions"}}}}}},"responses":{"200":{"description":"Session configuration updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/sessions":{"put":{"summary":"Update session dates","description":"Update scheduled dates for cohort sessions","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sessions"],"properties":{"sessions":{"type":"array","items":{"type":"object","properties":{"session_number":{"type":"integer"},"scheduled_date":{"type":"string","format":"date-time"}}}}}}}}},"responses":{"200":{"description":"Sessions updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/sessions/{sessionId}":{"patch":{"summary":"Update an individual session","description":"Update details of a specific session within a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","maxLength":200},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["scheduled","completed","cancelled"]},"zoom_link":{"type":"string","format":"uri","nullable":true},"location":{"type":"string","description":"MED-780. Free-text physical location for the calendar invite\n(e.g., \"Room 204, 123 Main St\"). When set, takes precedence\nover zoom_link as the calendar event's location field. The\nvideo link, if present, appears in the event description.\n","nullable":true,"maxLength":1000},"one_on_one_type":{"type":"string","enum":["standard_1_1","two_on_one","360_debrief","hogan_debrief","other_assessment_debrief"],"description":"1:1 coaching topic (MED-689). Only valid when\nsession_type = 'one_on_one_session'. Immutable once the\nsession status is anything other than 'unscheduled' — the\nAPI responds 400 if a change is attempted on a scheduled\nor later session.\n"}}}}}},"responses":{"200":{"description":"Session updated successfully"},"400":{"description":"Session does not belong to this cohort, one_on_one_type was set on\na non-1:1 session, or one_on_one_type change was attempted on a\nscheduled (or later) session.\n"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or session not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/sessions/{sessionId}/clear-date":{"post":{"summary":"Clear the date of a 1:1 session or check-in (MED-1350)","description":"Returns a dated one_on_one_session row to unscheduled. Coach, group,\ntype and duration stay. A Calendly booking is canceled and a Google\nCalendar invite is deleted first; if either cancel fails the row keeps\nits date and the call returns 409.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Date cleared"},"400":{"description":"Not a 1:1 row, no date, or a completed, cancelled or in progress row"},"404":{"description":"Session not found in this cohort"},"409":{"description":"The external booking could not be canceled; the date was kept"}}}},"/cohorts/{id}/sessions/{sessionId}/miss-claims":{"get":{"summary":"The no-show and late change claims of a 1:1 (MED-1346)","description":"Staff, or the coach of the session. Response { claims, paidMissCount }.","tags":["Cohorts"],"security":[{"bearerAuth":[]}]},"post":{"summary":"A cohort manager flags a 1:1 no-show or late change (MED-1346)","description":"Staff only. Body { kind, originalStart, changeAt }. Response 201 { claim }.\nConfirmed at once (paid, or unpaid_cap past 2 paid claims). 400 when the\nchange had 24 hours notice or more, or for an intro or midpoint check-in.\n409 for the same original start twice.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}]}},"/cohorts/{id}/sessions/{sessionId}/miss-claims/{claimId}/answer":{"post":{"summary":"Answer a pending 1:1 late change claim (MED-1346)","description":"Body { underTwentyFourHours: boolean }. Response { claim }. true confirms\nit (paid, or unpaid_cap past 2 paid claims); false declines it. Staff, or the coach of the claim\n(403 for another coach). 409 when the claim is already answered.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}]}},"/cohorts/{id}/sessions/{sessionId}/ai-listening":{"patch":{"summary":"Set AI Listening for a session (Cohort Manager / Admin)","description":"MED-866 E1.12. Cohort-Manager/Admin pre-lock control for AI Listening on\na group coaching session. Authorized server-side — only the Cohort\nManager (`internal_user`) and Admin (`internal_admin`) personas may call\nit; all other callers receive 403 (AC8). Works for both coach-led and\npeer-led sessions (AC7). Requires AI features enabled for the cohort's\ngroup coaching modality. The setting is frozen once the 15-minute lock\nwindow (`scheduled_start - 15 min`) has passed or the session is in\nprogress, returning 409 (AC10). Writes the single source-of-truth column\n`sessions.ai_listening_enabled` consumed by the active session screen and\nthe session-intelligence gate (IV5).\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Session ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"Desired AI Listening state for the session."}}}}}},"responses":{"200":{"description":"AI Listening updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"ai_listening_enabled":{"type":"boolean"},"ai_listening_locked":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized (not authenticated)"},"403":{"description":"Forbidden (caller is not a Cohort Manager or Admin)"},"404":{"description":"Session not found in this cohort"},"409":{"description":"AI features are disabled for the cohort's modality\n(`AI_FEATURES_DISABLED`), or the lock window has passed\n(`AI_LISTENING_LOCKED`).\n"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/purge-ai-data":{"post":{"summary":"Purge all AI-generated data for a cohort (internal admin only)","description":"MED-1196 (B3) — manual retention purge. Deletes every persisted\nAI-OUTPUT store for the cohort, across all of its sessions: session\ntranscripts, airtime aggregates, discussion themes, and recap email\ndrafts. Issues real deletes per store (does not rely on the sessions FK\ncascade, which a soft-delete does not trip). Does NOT touch core cohort,\nsession, participant-entered, or configuration data — including the kept\npre-session synthesis, prep AI-Note suggestions, and prep AI-prompt\nselections.\n\nAuthorization is `internal_admin` persona only, enforced server-side.\nCoaches, cohort managers (`internal_user`), and participants cannot\ninvoke it. The endpoint is gated behind the `live_session_delivery`\nfeature flag and returns 404 when the flag is off. Idempotent: a cohort\nwith no persisted AI data returns success with zero counts.\n","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"AI data purged (or nothing to purge)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohortId":{"type":"string","format":"uuid"},"sessionCount":{"type":"integer","description":"Sessions the purge fanned out over (incl. soft-deleted)."},"deleted":{"type":"object","properties":{"transcripts":{"type":"integer"},"airtime":{"type":"integer"},"themes":{"type":"integer"},"recapDrafts":{"type":"integer"}}},"totalDeleted":{"type":"integer","description":"Sum of rows removed across all stores."}}}}}}}},"401":{"description":"Unauthorized (not authenticated)"},"403":{"description":"Forbidden (caller is not an internal admin)"},"404":{"description":"Cohort not found, or the live_session_delivery flag is off"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/team-assignments":{"put":{"summary":"Update team assignments","description":"Update team role assignments for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignments"],"properties":{"assignments":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string"},"user_id":{"type":"string","format":"uuid"}}}}}}}}},"responses":{"200":{"description":"Team assignments updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/date-constraints":{"put":{"summary":"Update date constraints","description":"Update date constraints (blackout dates) for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["constraints"],"properties":{"constraints":{"type":"array","items":{"type":"object","properties":{"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"label":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Date constraints updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/primary-contacts":{"put":{"summary":"Update primary contacts","description":"Update primary partner contacts for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact_ids"],"properties":{"contact_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"Primary contacts updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/custom-fields":{"get":{"summary":"List custom field definitions","description":"Get all custom field definitions for a cohort","tags":["Custom Fields"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Custom fields retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"field_name":{"type":"string"},"field_label":{"type":"string"},"field_type":{"type":"string","enum":["text","number","date","boolean"]},"sort_order":{"type":"integer"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"post":{"summary":"Create custom field definition","description":"Create a new custom field definition for a cohort","tags":["Custom Fields"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["field_name","field_label"],"properties":{"field_name":{"type":"string","description":"Internal field name (used as key in JSONB)"},"field_label":{"type":"string","description":"Display label for the field"},"field_type":{"type":"string","enum":["text","number","date","boolean"],"default":"text"}}}}}},"responses":{"201":{"description":"Custom field created successfully"},"400":{"description":"Missing required fields"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"409":{"description":"Field name already exists"}}}},"/cohorts/{cohortId}/custom-fields/reorder":{"put":{"summary":"Reorder custom fields","description":"Update the display order of custom field definitions","tags":["Custom Fields"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["fieldIds"],"properties":{"fieldIds":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Ordered array of field IDs"}}}}}},"responses":{"200":{"description":"Fields reordered successfully"},"400":{"description":"Invalid fieldIds"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/custom-fields/{fieldId}":{"put":{"summary":"Update custom field definition","description":"Update a custom field's label or type","tags":["Custom Fields"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"fieldId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"field_label":{"type":"string"},"field_type":{"type":"string","enum":["text","number","date","boolean"]}}}}}},"responses":{"200":{"description":"Custom field updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or field not found"}}},"delete":{"summary":"Delete custom field definition","description":"Soft delete a custom field definition","tags":["Custom Fields"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"fieldId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Custom field deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or field not found"}}}},"/cohorts/{cohortId}/custom-fields/{fieldId}/values":{"put":{"summary":"Bulk update custom field values","description":"Update custom field values for multiple participants at once","tags":["Custom Fields"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"fieldId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["updates"],"properties":{"updates":{"type":"array","items":{"type":"object","required":["participantId"],"properties":{"participantId":{"type":"string","format":"uuid"},"value":{"type":"string","nullable":true}}}}}}}}},"responses":{"200":{"description":"Values updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"updatedCount":{"type":"integer"}}}}}}}},"400":{"description":"Invalid updates array"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or field not found"}}}},"/cohorts/{id}/job-assignments":{"get":{"summary":"Get job title assignments","description":"Get all job title assignments for a cohort","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Job title assignments retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CohortJobTitleAssignment"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"put":{"summary":"Update job title assignments","description":"Update all job title assignments for a cohort. Replaces existing assignments.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignments"],"properties":{"assignments":{"type":"array","items":{"type":"object","required":["job_title_id"],"properties":{"job_title_id":{"type":"string","format":"uuid","description":"ID of the job title"},"user_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of the user to assign (null to unassign)"}}}}}},"example":{"assignments":[{"job_title_id":"123e4567-e89b-12d3-a456-426614174000","user_id":"123e4567-e89b-12d3-a456-426614174001"},{"job_title_id":"123e4567-e89b-12d3-a456-426614174002","user_id":null}]}}}},"responses":{"200":{"description":"Job title assignments updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CohortJobTitleAssignment"}}}}}}},"400":{"description":"Invalid job title ID"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{id}/job-assignments/initialize":{"post":{"summary":"Initialize job title assignments","description":"Create unassigned job title entries for all active job titles. Use when setting up a new cohort.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"201":{"description":"Job title assignments initialized successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CohortJobTitleAssignment"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"409":{"description":"Job title assignments already exist for this cohort"}}}},"/cohorts/{id}/session-topics":{"get":{"summary":"Get session topic assignments","description":"Get all topic assignments for sessions in a cohort (zero state, before groups exist)","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Session topics retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CohortSessionTopic"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/session-topics/{sessionNumber}":{"put":{"summary":"Assign topic to session number","description":"Assign or update the topic for a session number (zero state, before groups exist)","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"sessionNumber","required":true,"schema":{"type":"integer","minimum":1},"description":"Session number (1-based)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["topic_id"],"properties":{"topic_id":{"type":"string","format":"uuid","description":"ID of the topic to assign"},"display_name":{"type":"string","nullable":true,"description":"Optional participant-facing label override (MED-832). When set,\nparticipants see this string in place of the canonical topics.name.\nInternal/CM surfaces and reporting always continue to use topics.name.\nEmpty string is normalized to NULL on the server (treated as \"no override\").\n"}}},"example":{"topic_id":"123e4567-e89b-12d3-a456-426614174000","display_name":"Welcome & Goal-Setting"}}}},"responses":{"200":{"description":"Topic assigned successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CohortSessionTopic"}}}}}},"400":{"description":"Invalid session number"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or topic not found"}}},"delete":{"summary":"Remove topic assignment from session","description":"Remove the topic assignment for a session number","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"sessionNumber","required":true,"schema":{"type":"integer","minimum":1},"description":"Session number (1-based)"}],"responses":{"204":{"description":"Topic assignment removed successfully"},"400":{"description":"Invalid session number"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found or topic assignment not found"}}}},"/api/cohorts/{cohortId}/timeline":{"get":{"summary":"Get cohort timeline for availability request","description":"Returns all cohort events and sessions organized as a timeline with month groupings and color-coded categories.","tags":["Cohorts"],"parameters":[{"in":"path","name":"cohortId","schema":{"type":"string","format":"uuid"},"required":true,"description":"The cohort ID"}],"responses":{"200":{"description":"Timeline data with events sorted chronologically"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/huddles":{"get":{"summary":"List huddles for a cohort (admin/CM read-only)","description":"Returns all 1:1 huddle sessions in a cohort. Read-only view for admin and cohort managers.","tags":["Cohort Huddles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"List of huddles in the cohort","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"huddle_type":{"type":"string","enum":["intro","midpoint_checkin","general"]},"participant_name":{"type":"string"},"participant_email":{"type":"string"},"group_name":{"type":"string"},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"duration_minutes":{"type":"integer"},"status":{"type":"string"},"video_link":{"type":"string","nullable":true}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/{cohortId}/resources":{"get":{"summary":"List resources for a cohort","description":"Get paginated resources for a cohort, including visible evergreen resources","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":20}},{"in":"query","name":"session_id","schema":{"type":"string","format":"uuid"},"description":"Filter by session"},{"in":"query","name":"category_id","schema":{"type":"string","format":"uuid"},"description":"Filter by category"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search in title and description"},{"in":"query","name":"access_level","schema":{"type":"string","enum":["coaches","participants","partners"]},"description":"Filter by access level"},{"in":"query","name":"user_role","schema":{"type":"string","enum":["admin","coach","participant","partner"]},"description":"Filter resources based on user role access permissions (non-admin users only see resources they have access to)"}],"responses":{"200":{"description":"Resources retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ResourceWithDetails"}},"pagination":{"type":"object"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/resources/counts":{"get":{"summary":"Get resource counts for a cohort","description":"Returns count of total and evergreen resources for a cohort","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resource counts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total":{"type":"integer"},"evergreen":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/resources/{resourceId}/hide":{"post":{"summary":"Hide evergreen resource from cohort","description":"Hide an evergreen resource so it doesn't appear in this cohort's resource list","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"resourceId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resource hidden successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"hidden":{"type":"boolean"}}},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Resource not found or not an evergreen resource"}}},"delete":{"summary":"Unhide evergreen resource from cohort","description":"Restore visibility of an evergreen resource in this cohort","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"resourceId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resource unhidden successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"hidden":{"type":"boolean"}}},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/{id}/status-readiness":{"get":{"summary":"Get status readiness stats for a cohort","description":"Returns completion percentage for current status phase and whether cohort is ready to advance","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Status readiness stats retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"currentStatus":{"type":"string","description":"Current cohort status"},"tasksCompleted":{"type":"integer","description":"Number of completed tasks in current status phase"},"tasksTotal":{"type":"integer","description":"Total tasks in current status phase"},"completionPercent":{"type":"integer","description":"Completion percentage (0-100)"},"readyToAdvance":{"type":"boolean","description":"True if >=90% complete"},"nextStatus":{"type":"string","nullable":true,"description":"Next status in progression, or null if at final status"},"previousStatus":{"type":"string","nullable":true,"description":"Previous status for reverting"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/advance-status":{"post":{"summary":"Advance cohort to next status","description":"Advances the cohort to the next status in progression if ready (>=90% complete)","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Cohort status advanced successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohort":{"$ref":"#/components/schemas/Cohort"},"previousStatus":{"type":"string"},"newStatus":{"type":"string"},"action":{"type":"string","enum":["advance"]}}}}}}}},"400":{"description":"Cannot advance (not ready or at final status)"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/revert-status":{"post":{"summary":"Revert cohort to previous status","description":"Reverts the cohort to the previous status in progression","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Cohort status reverted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohort":{"$ref":"#/components/schemas/Cohort"},"previousStatus":{"type":"string"},"newStatus":{"type":"string"},"action":{"type":"string","enum":["revert"]}}}}}}}},"400":{"description":"Cannot revert (at initial status)"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/cancel":{"post":{"summary":"Cancel a cohort","description":"Cancels the cohort, hiding it from active lists. Can be restored later.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Cohort cancelled successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohort":{"$ref":"#/components/schemas/Cohort"},"previousStatus":{"type":"string"},"newStatus":{"type":"string","enum":["cancelled"]},"action":{"type":"string","enum":["cancel"]}}}}}}}},"400":{"description":"Already cancelled or archived"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/restore":{"post":{"summary":"Restore a cancelled cohort","description":"Restores a cancelled cohort to its previous status","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Cohort restored successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohort":{"$ref":"#/components/schemas/Cohort"},"previousStatus":{"type":"string","enum":["cancelled"]},"newStatus":{"type":"string"},"action":{"type":"string","enum":["restore"]}}}}}}}},"400":{"description":"Cohort is not cancelled"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/archive":{"post":{"summary":"Archive a cohort (permanent)","description":"Archives a completed or cancelled cohort. This action cannot be undone.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Cohort archived successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohort":{"$ref":"#/components/schemas/Cohort"},"previousStatus":{"type":"string"},"newStatus":{"type":"string","enum":["archived"]},"action":{"type":"string","enum":["archive"]}}}}}}}},"400":{"description":"Cohort must be completed or cancelled to archive"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{id}/email-settings":{"get":{"summary":"Get granular email settings for a cohort","description":"Returns the 3-level cascading email settings (master > persona > individual email type) for the specified cohort.","tags":["Cohorts"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Email settings retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CohortEmailSettings"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"put":{"summary":"Update granular email settings for a cohort","description":"Replaces the full email settings JSONB for the specified cohort. Supports 3-level cascading control - master, persona, and individual email type toggles.","tags":["Cohorts"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CohortEmailSettings"}}}},"responses":{"200":{"description":"Email settings updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CohortEmailSettings"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{id}/sender-email":{"get":{"summary":"Get the cohort's sender address","description":"Returns sender_email (null means the default sender), default_sender_email, and allowed_domains.","tags":["Cohorts"]},"put":{"summary":"Set or clear the cohort's sender address","description":"Body { sender_email string or null }. Empty or null clears it. The domain must be in SENDER_ALLOWED_DOMAINS, otherwise 400.","tags":["Cohorts"]}},"/api/cohorts/{id}/feature-toggles":{"get":{"summary":"Get audience feature toggles for a cohort","description":"Returns the participant_features_enabled and coach_features_enabled toggle state. When OFF, audience-facing emails (except calendar invites) are suppressed, the audience cannot access the app for this cohort, and surveys are not scheduled.","tags":["Cohorts"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Feature toggles retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"participant_features_enabled":{"type":"boolean"},"coach_features_enabled":{"type":"boolean"},"participant_app_access_enabled":{"type":"boolean","description":"Raw column. Participant app access is effective only when participant_features_enabled is also true."}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"patch":{"summary":"Update audience feature toggles for a cohort","description":"Toggle participant and/or coach features ON or OFF. When OFF, all audience-facing emails (except calendar invites) are suppressed, the audience is redirected out of the app, and surveys are not scheduled.","tags":["Cohorts"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"participant_features_enabled":{"type":"boolean","description":"When false, suppresses participant emails, blocks participant app access, stops survey scheduling"},"coach_features_enabled":{"type":"boolean","description":"When false, suppresses coach emails, blocks coach app access, stops survey scheduling"},"participant_app_access_enabled":{"type":"boolean","description":"When false, participants cannot use the app. Participant emails continue without links into the app. Has effect only when participant_features_enabled is true."}}}}}},"responses":{"200":{"description":"Feature toggles updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"participant_features_enabled":{"type":"boolean"},"coach_features_enabled":{"type":"boolean"},"participant_app_access_enabled":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/workshops":{"get":{"summary":"List workshops for a cohort","description":"Returns all workshop definitions with linked session data (date, facilitator, participant count)","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"List of workshops with session data"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{id}/workshops/{workshopId}":{"patch":{"summary":"Update a workshop","description":"Update workshop definition fields (title, description, facilitator, duration) and linked session scheduling (date/time, meeting link). Notifies facilitator on assignment.","tags":["Cohorts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Workshop definition ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"facilitator_id":{"type":"string","format":"uuid"},"session_duration_minutes":{"type":"integer","minimum":15,"maximum":480},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"zoom_link":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated workshop with session data"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or workshop not found"}}}},"/cohorts/{id}/workshops/{workshopId}/participants":{"get":{"summary":"List participants assigned to a workshop","tags":["Workshops"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of participants assigned to the workshop"},"404":{"description":"Cohort or workshop not found"}}},"post":{"summary":"Add participants to a workshop","description":"Creates session_attendance records for the specified users. Skips duplicates.","tags":["Workshops"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_ids"],"properties":{"user_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"Number of participants added"},"400":{"description":"Invalid user_ids"},"404":{"description":"Cohort or workshop not found"}}}},"/cohorts/{id}/workshops/{workshopId}/participants/eligible":{"get":{"summary":"List cohort participants not yet assigned to a workshop","tags":["Workshops"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of eligible participants"},"404":{"description":"Cohort or workshop not found"}}}},"/cohorts/{id}/workshops/{workshopId}/participants/{userId}":{"delete":{"summary":"Remove a participant from a workshop","description":"Soft-deletes the participant's session_attendance record for this workshop. If the workshop is `all_participants`, auto-converts it to `subset`. If finalized, updates the Google Calendar event to remove the participant.\n","tags":["Workshops"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Workshop definition ID"},{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID of the participant to remove"}],"responses":{"200":{"description":"Participant removed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"removed":{"type":"boolean"}}}}}},"404":{"description":"Cohort, workshop, or participant not found"}}}},"/cohorts/{id}/workshops/{workshopId}/finalize":{"post":{"summary":"Finalize a workshop and send calendar invites (MED-921)","description":"Finalizes a workshop session and sends Google Calendar invites to its\nconfigured participants and facilitator. Mirrors the group-coaching\nfinalize flow but scoped to a single workshop.\n\n- Marks `sessions.finalized_at` and `invites_sent_at`.\n- Creates `session_attendance` records for each attendee.\n- Creates a Google Calendar event with the new Medley-branded title\n  (`Medley {workshop title}`) and description.\n- Falls back to ICS email attachment if Google Calendar is not configured.\n- Respects the cohort's `session_reminder` email-master-switch for\n  participants and the facilitator (attendance records still created).\n","tags":["Workshops"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"video_link":{"type":"string","description":"Optional video conferencing link saved on the workshop session and surfaced in the calendar event description."},"location":{"type":"string","description":"Optional free-text physical location (MED-780). When non-empty, wins over the video link for the calendar event's `location` field."}}}}}},"responses":{"200":{"description":"Workshop finalized","content":{"application/json":{"schema":{"type":"object","properties":{"finalized":{"type":"boolean"},"attendance_records_created":{"type":"integer"},"calendar_event_created":{"type":"boolean"},"emails_sent":{"type":"integer"},"errors":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Workshop missing scheduled date/time"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or workshop not found"}}}},"/cohorts/{id}/workshops/{workshopId}/resend-invites":{"post":{"summary":"Resend calendar invites for an already-finalized workshop (MED-921)","description":"Resends Google Calendar invites for a workshop that has already been\nfinalized. Mirrors the group-coaching resend behavior — surfaced in\nthe UI when the workshop is finalized AND at least one attendee still\nhas a pending RSVP (AC3).\n","tags":["Workshops"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"workshopId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"video_link":{"type":"string","description":"Optional override for the video link on resend. Falls back to the workshop's saved `zoom_link` when omitted."}}}}}},"responses":{"200":{"description":"Invites resent","content":{"application/json":{"schema":{"type":"object","properties":{"invites_sent":{"type":"integer"},"errors":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or workshop not found"}}}},"/api/dashboard/active-cohorts":{"get":{"summary":"Get active cohorts for the dashboard","description":"Returns all cohorts with LIVE status including partner info, session progress, and computed health status. Health is computed on-the-fly via the HealthAlertRulesEngine.","tags":["Dashboard"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of active cohorts with health status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string","enum":["live"]},"partner_name":{"type":"string"},"partner_code":{"type":"string","nullable":true},"completed_sessions":{"type":"integer"},"total_sessions":{"type":"integer"},"health_status":{"type":"string","enum":["at_risk","monitor","on_track"]}}}}}}}}},"401":{"description":"Unauthorized - authentication required"},"500":{"description":"Internal server error"}}}},"/api/dashboard/upcoming-sessions":{"get":{"summary":"Get upcoming sessions across all cohorts","description":"Returns the next 5 upcoming sessions across all LIVE cohorts. Sessions are grouped by cohort and session number, aggregating group counts.","tags":["Dashboard"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of upcoming sessions","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"partner_name":{"type":"string"},"session_number":{"type":"integer"},"topic_name":{"type":"string","nullable":true},"week_of":{"type":"string","format":"date"},"group_count":{"type":"integer"}}}}}}}}},"401":{"description":"Unauthorized - authentication required"},"500":{"description":"Internal server error"}}}},"/api/dashboard/attention-items":{"get":{"summary":"Get cohorts needing attention","description":"Returns health alerts for all LIVE cohorts grouped by severity (risk and monitor). Computes health on-the-fly and excludes dismissed alerts.","tags":["Dashboard"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Attention items grouped by risk and monitor","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"risk":{"type":"object","properties":{"count":{"type":"integer","description":"Number of distinct cohorts with risk alerts"},"items":{"type":"array","items":{"type":"object","properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"alert":{"type":"object"}}}}}},"monitor":{"type":"object","properties":{"count":{"type":"integer"},"items":{"type":"array","items":{"type":"object"}}}}}}}}}}},"401":{"description":"Unauthorized - authentication required"},"500":{"description":"Internal server error"}}}},"/api/dashboard/coaches-awaiting-contracts":{"get":{"summary":"Get coaches awaiting contract signatures","description":"Returns coaches assigned to LIVE cohorts who haven't returned signed contracts (contract_status is 'pending' or 'announced'), grouped by cohort.","tags":["Dashboard"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Coaches awaiting contracts grouped by cohort","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"coaches":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"avatar_url":{"type":"string","nullable":true},"contract_status":{"type":"string","enum":["pending","announced"]},"announced_at":{"type":"string","format":"date-time","nullable":true}}}}}}}}}}}},"401":{"description":"Unauthorized - authentication required"},"500":{"description":"Internal server error"}}}},"/api/data-library/participants":{"get":{"summary":"List participants across multiple cohorts","description":"Returns enriched participant data across multiple cohorts including group assignment, coach, attendance stats, active flags, survey completion status, and last email date.\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":25}},{"in":"query","name":"sortBy","schema":{"type":"string","default":"first_name"},"description":"Sort field (first_name, last_name, email, status, cohort, group, coach, attendance, role, department, location, enrolled_at, flag_severity)"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"ASC"}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by name or email"},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","active","withdrawn"]}},{"in":"query","name":"groupId","description":"One group UUID or a comma-separated list of group UUIDs (MDF-211)","schema":{"type":"string"}},{"in":"query","name":"coachId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"department","schema":{"type":"string"}},{"in":"query","name":"location","schema":{"type":"string"}},{"in":"query","name":"flaggedOnly","schema":{"type":"boolean"}},{"in":"query","name":"hasAbsences","schema":{"type":"boolean"}},{"in":"query","name":"midpointCompleted","schema":{"type":"boolean"}},{"in":"query","name":"endpointCompleted","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Participants retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/DataLibraryParticipant"}},"meta":{"type":"object","properties":{"pagination":{"type":"object"},"filterOptions":{"type":"object"}}}}}}}},"400":{"description":"Bad request - missing cohortIds"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/attendance":{"get":{"summary":"List attendance data across multiple cohorts","description":"Returns one row per participant per completed session with attendance status, coach notes, aggregate metrics, and participant demographics. Supports server-side pagination, filtering, sorting, and grouping.\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":100}},{"in":"query","name":"sortBy","schema":{"type":"string","default":"session_number"},"description":"Sort field"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"DESC"}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by participant name, email, or session topic"},{"in":"query","name":"status","schema":{"type":"string","enum":["active","withdrawn"]}},{"in":"query","name":"groupId","description":"One group UUID or a comma-separated list of group UUIDs (MDF-211)","schema":{"type":"string"}},{"in":"query","name":"coachId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"sessionNumber","schema":{"type":"string"},"description":"Comma-separated session numbers"},{"in":"query","name":"attendanceStatus","schema":{"type":"string"},"description":"Comma-separated statuses (attended, partial, excused_business, excused_personal, unexcused, not_marked)"},{"in":"query","name":"department","schema":{"type":"string"}},{"in":"query","name":"location","schema":{"type":"string"}},{"in":"query","name":"businessUnit","schema":{"type":"string"}},{"in":"query","name":"absentOnly","schema":{"type":"boolean"}},{"in":"query","name":"unexcusedOnly","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Attendance data retrieved successfully"},"400":{"description":"Bad request - missing cohortIds"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/survey-responses":{"get":{"summary":"List per-participant survey responses across cohorts","description":"Returns individual participant survey responses with parsed question data. Used by the Data Library Midpoint, Endpoint, and Mid vs End tabs.\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"formTypes","required":true,"schema":{"type":"string"},"description":"Comma-separated form types (mid_program, final_survey)"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":100}},{"in":"query","name":"sortBy","schema":{"type":"string","default":"first_name"}},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"ASC"}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"groupId","description":"One group UUID or a comma-separated list of group UUIDs (MDF-211)","schema":{"type":"string"}},{"in":"query","name":"coachId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"flaggedOnly","schema":{"type":"boolean"}},{"in":"query","name":"hasAbsences","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Survey responses retrieved successfully"},"400":{"description":"Bad request - missing required parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/coach-survey-responses":{"get":{"summary":"List per-coach survey responses across cohorts","description":"Returns individual coach feedback survey responses with parsed question data. Used by the Data Library Coach Midpoint and Coach Endpoint tabs.\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"surveyType","required":true,"schema":{"type":"string","enum":["midpoint","endpoint"]},"description":"Survey type (midpoint or endpoint)"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":100}},{"in":"query","name":"sortBy","schema":{"type":"string","default":"first_name"},"description":"Sort field (first_name, last_name, email, submitted_at)"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"ASC"}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by coach name or email"}],"responses":{"200":{"description":"Coach survey responses retrieved successfully"},"400":{"description":"Bad request - missing required parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/session-feedback":{"get":{"summary":"List per-participant post-session feedback across cohorts","description":"Returns one row per participant per session where post-session feedback was submitted. Includes session context columns and dynamic survey question columns parsed from raw_payload. Used by the Data Library Participant Session Feedback tab.\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":100}},{"in":"query","name":"sortBy","schema":{"type":"string","default":"session_number"},"description":"Sort field (session_number, session_date, first_name, last_name, email, coach, date_submitted)"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"DESC"}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by participant name, email, or session topic"},{"in":"query","name":"groupId","description":"One group UUID or a comma-separated list of group UUIDs (MDF-211)","schema":{"type":"string"}},{"in":"query","name":"coachId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"sessionNumber","schema":{"type":"string"},"description":"Comma-separated session numbers to filter by"},{"in":"query","name":"flaggedOnly","schema":{"type":"boolean"}},{"in":"query","name":"hasAbsences","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Session feedback retrieved successfully"},"400":{"description":"Bad request - missing cohortIds"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/sessions":{"get":{"summary":"List session summary data across multiple cohorts","description":"Returns one row per group session with attendance summary data, per-participant attendance status array, and aggregate metrics. By default returns only completed sessions. Pass sessionStatus=upcoming to retrieve not-yet-completed sessions, or sessionStatus=flagged to retrieve sessions with active, undismissed health alerts. Session Week and Session Month are derived client-side from session_date.\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":100}},{"in":"query","name":"sortBy","schema":{"type":"string","default":"session_date"},"description":"Sort field (session_date, cohort_name, coach_name, session_attendance_rate, total_attended, total_participants)"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"DESC"}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by cohort name or coach name"},{"in":"query","name":"cohortId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"groupId","description":"One group UUID or a comma-separated list of group UUIDs (MDF-211)","schema":{"type":"string"}},{"in":"query","name":"coachId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"sessionMonth","schema":{"type":"string"},"description":"Comma-separated YYYY-MM values to filter by month"},{"in":"query","name":"sessionNumber","schema":{"type":"string"},"description":"Comma-separated session numbers to filter by"},{"in":"query","name":"sessionStatus","schema":{"type":"string","enum":["completed","upcoming","flagged"]},"description":"Filter sessions by lifecycle state. `completed` returns sessions with status=completed. `upcoming` returns sessions that have not yet taken place (unscheduled/scheduled/in_progress). `flagged` returns sessions with at least one active, undismissed health alert. When omitted, defaults to completed-only.\n"}],"responses":{"200":{"description":"Session summary data retrieved successfully"},"400":{"description":"Bad request - missing cohortIds"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/cohorts":{"get":{"summary":"Get cohorts grouped by partner","description":"Returns all active cohorts grouped by partner organization for the Data Library sidebar.","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Cohort groups retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/DataLibraryCohortGroup"}}}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/coach-post-session":{"get":{"summary":"Get coach post-session feedback with attendance, engagement, and flags","description":"Returns one row per completed session per coach/group with post-session feedback, attendance pills, engagement ratings, concern flags, and coach notes.","tags":["Data Library"],"parameters":[{"in":"query","name":"cohortIds","schema":{"type":"string"},"required":true,"description":"Comma-separated cohort IDs"},{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"groupId","description":"One group UUID or a comma-separated list of group UUIDs (MDF-211)","schema":{"type":"string"}},{"in":"query","name":"coachId","schema":{"type":"string"}},{"in":"query","name":"sessionNumber","schema":{"type":"string"}},{"in":"query","name":"missingFeedback","schema":{"type":"boolean"}},{"in":"query","name":"hasFlags","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Coach post-session feedback data"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/data-library/one-on-ones":{"get":{"summary":"List 1:1 sessions across cohorts with status and coach feedback (MDF-253)","description":"Returns one row per 1:1 session (one_on_one_session and non_coaching_one_on_one) with participant, coach, group, scheduled time, status, booking method, the coach's attendance note, session notes, engagement rating and feedback answers. meta.summary counts each status over the filtered rows and gives the completion rate: completed / (completed + no-show + past canceled).\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}},{"in":"query","name":"sortBy","schema":{"type":"string","enum":["scheduled_start","participant_name","coach_name","cohort_name","status","session_number"]}},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"]}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Participant name or email, coach, cohort or group name"},{"in":"query","name":"cohortId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"coachId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated (completed, absent, canceled, needs_attendance, scheduled, not_scheduled, withdrawn)"},{"in":"query","name":"type","schema":{"type":"string"},"description":"Comma-separated one_on_one_type values"},{"in":"query","name":"sessionNumber","schema":{"type":"string"},"description":"Comma-separated 1:1 numbers (MED-1346)"},{"in":"query","name":"participantId","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"1:1 rows with meta.pagination, meta.filterOptions and meta.summary"},"400":{"description":"Bad request - missing or invalid cohortIds, coachId, cohortId or status"},"401":{"description":"Unauthorized"},"403":{"description":"Not Medley staff"}}}},"/api/data-library/one-on-ones/history":{"get":{"summary":"List each 1:1 date and status change (MED-1346)","description":"One row per change from one_on_one_schedule_events, newest first. A database trigger writes the rows, so Calendly and sync-job changes are included. Attendance shows as attended or absent by the current attendance row. missKind and missResult show the claim for the booking the change moved away from.\n","tags":["DataLibrary"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohortIds","required":true,"schema":{"type":"string"},"description":"Comma-separated cohort UUIDs"},{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"]}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Participant, coach or cohort name"},{"in":"query","name":"coachId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"participantId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"event","schema":{"type":"string"},"description":"Comma-separated (booked, rescheduled, date_cleared, canceled, no_show, attended, absent, attendance_cleared, initial)"}],"responses":{"200":{"description":"History rows with meta.pagination and meta.filterOptions"},"400":{"description":"Bad request - missing or invalid cohortIds, coachId, participantId or event"},"401":{"description":"Unauthorized"},"403":{"description":"Not Medley staff"}}}},"/data-library/saved-views":{"get":{"summary":"List the caller's saved views for one tab (MDF-38)","tags":["DataLibrary"],"parameters":[{"in":"query","name":"tab","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The caller's views","by name":null},"422":{"description":"Unknown tab"}}},"post":{"summary":"Save a view (MDF-38)","tags":["DataLibrary"],"description":"Body is { tab, name, body }. Name is trimmed, 1 to 80 characters, unique per user and tab. Body is at most 64 KB.","responses":{"201":{"description":"The new view"},"409":{"description":"The caller already has a view with this name on this tab"},"422":{"description":"Invalid tab","name or body":null}}}},"/data-library/saved-views/{id}":{"patch":{"summary":"Rename a view, replace its body, or both (MDF-38)","tags":["DataLibrary"],"responses":{"200":{"description":"The changed view"},"404":{"description":"No view with this id belongs to the caller"},"409":{"description":"The caller already has a view with this name on this tab"}}},"delete":{"summary":"Delete a view (MDF-38)","tags":["DataLibrary"],"responses":{"204":{"description":"Deleted"},"404":{"description":"No view with this id belongs to the caller"}}}},"/api/dev/sessions/{sessionId}/reflection-questions":{"post":{"summary":"(DEV ONLY) Seed reflection questions for a session","description":"Local/staging-only endpoint to bulk-create reflection questions for a\nsession. Returns 404 in production. Intended for QA seeding while the\nCM admin UI is being groomed for a future ticket.\n","tags":["Pre-Session Reflection"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["questions"],"properties":{"questions":{"type":"array","items":{"type":"object","required":["prompt"],"properties":{"prompt":{"type":"string"},"placeholder_text":{"type":"string","nullable":true},"sort_order":{"type":"integer"}}}}}}}}},"responses":{"201":{"description":"Questions created"},"400":{"description":"Bad request body"},"401":{"description":"Unauthorized"},"404":{"description":"Not available in production"}}}},"/api/exports":{"post":{"summary":"Create a new export request","description":"Queue a data export for async processing. User will receive email when ready.","tags":["Exports"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateExportRequest"}}}},"responses":{"201":{"description":"Export request created and queued","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"}}},"message":{"type":"string"}}}}}},"400":{"description":"Validation error or rate limit exceeded"},"401":{"description":"Unauthorized"}}},"get":{"summary":"List user's export requests","description":"Get paginated list of export requests for the authenticated user","tags":["Exports"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":20}},{"in":"query","name":"sortBy","schema":{"type":"string","default":"requested_at"}},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"DESC"}},{"in":"query","name":"entity_type","schema":{"type":"string","enum":["participants","coaches","partners","cohorts","groups","sessions","resources","users","survey_responses","attendance"]}},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","processing","completed","failed"]}}],"responses":{"200":{"description":"Paginated list of export requests","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ExportRequestWithDetails"}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/exports/pending-count":{"get":{"summary":"Get pending export count","description":"Get count of pending exports for rate limiting display","tags":["Exports"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Pending export count","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"pending_count":{"type":"integer"},"max_pending":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/exports/columns/{entityType}":{"get":{"summary":"Get available columns for an entity type","description":"Get column definitions for export modal column selection","tags":["Exports"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"entityType","required":true,"schema":{"type":"string","enum":["participants","coaches","partners","cohorts","groups","sessions","resources","users","survey_responses","attendance"]}}],"responses":{"200":{"description":"Available columns for entity type","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/AvailableColumnsResponse"}}}}}},"400":{"description":"Invalid entity type"},"401":{"description":"Unauthorized"}}}},"/api/exports/validate/matching-tool":{"post":{"summary":"Validate matching tool export availability","description":"Check how many participants are missing availability data for matching tool export","tags":["Exports"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["context_id"],"properties":{"context_type":{"type":"string","enum":["global","cohort","partner"],"default":"cohort"},"context_id":{"type":"string","format":"uuid"},"filter_context":{"type":"object"}}}}}},"responses":{"200":{"description":"Availability validation results","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total_participants":{"type":"integer"},"missing_availability_count":{"type":"integer"},"missing_availability_participants":{"type":"array","items":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"}}}}}}}}}}},"400":{"description":"Missing context_id"},"401":{"description":"Unauthorized"}}}},"/api/exports/validate/dnm-groupings":{"post":{"summary":"Preview DNM groupings for participants","description":"Calculate Do Not Match groupings and identify participants missing manager data. Used to preview grouping results before export.","tags":["Exports"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["context_id"],"properties":{"context_type":{"type":"string","enum":["global","cohort","partner"],"default":"cohort"},"context_id":{"type":"string","format":"uuid"},"filter_context":{"type":"object"}}}}}},"responses":{"200":{"description":"DNM groupings preview","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total_groups":{"type":"integer","description":"Number of distinct DNM groups"},"total_participants_with_groupings":{"type":"integer","description":"Number of participants assigned to a group"},"total_participants":{"type":"integer","description":"Total participants in context"},"missing_manager_count":{"type":"integer","description":"Participants without manager data"},"missing_manager_participants":{"type":"array","items":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"}}}}}}}}}}},"400":{"description":"Missing context_id"},"401":{"description":"Unauthorized"}}}},"/api/exports/{id}":{"get":{"summary":"Get export request by ID","description":"Get details of a specific export request (user can only view own exports)","tags":["Exports"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Export request details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ExportRequestWithDetails"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Cannot view another user's export"},"404":{"description":"Export request not found"}}}},"/api/exports/{id}/download":{"get":{"summary":"Get download URL for completed export","description":"Generate signed CloudFront URL for downloading export file (24h expiry)","tags":["Exports"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Download URL generated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ExportDownloadResponse"}}}}}},"400":{"description":"Export not completed or expired"},"401":{"description":"Unauthorized"},"403":{"description":"Cannot download another user's export"},"404":{"description":"Export request not found"}}}},"/feature-flags":{"get":{"summary":"Get all feature flags","description":"Returns the current state of all feature flags. Used by the frontend to conditionally enable/disable features.","tags":["Feature Flags"],"responses":{"200":{"description":"Feature flags retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"data_library":{"type":"boolean","description":"Whether the Data Library feature is enabled","example":false},"attendance_follow_ups":{"type":"boolean","description":"Whether the Attendance Follow-Ups feature (E19) is enabled","example":false},"multi_modality":{"type":"boolean","description":"Whether Multi-Modality features (E18 — 1:1 coaching, workshops, participant dashboard) are enabled","example":false},"matching_server_integration":{"type":"boolean","description":"Whether the matching-server integration (E23 — Start Matching button, sessions panel, override badges) is enabled. API endpoints (matching-sessions routes) are always accessible regardless of this flag.","example":false},"live_session_delivery":{"type":"boolean","description":"MED-886 — Gates MED02 Live Session Delivery API endpoints. When false, MED02-specific endpoints (e.g., PATCH /cohorts/{id}/modalities/{modalityId}/settings, GET /coach/sessions/most-recent-completed) return 404. Default is environment-aware (production=false, otherwise true).","example":true}}}}}}}}}}},"/api/feedback":{"post":{"summary":"Submit in-app feedback (bug report or feature request)","description":"Accepts multipart/form-data with feedback fields and optional file attachments. Files are uploaded to S3 and the submission is logged to Google Sheets. If the feedback is a blocking bug, an email alert is sent to configured recipients.\n","tags":["Feedback"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/FeedbackSubmission"}}}},"responses":{"201":{"description":"Feedback submitted successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/FeedbackResponse"},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized - authentication required"},"422":{"description":"Validation failed"},"429":{"description":"Too many requests - rate limit exceeded"},"500":{"description":"Internal server error"}}}},"/cohorts/{cohortId}/feedback-surveys":{"get":{"summary":"Get all feedback survey configs for a cohort","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Survey configs retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/FeedbackSurveyConfig"}}}}}}},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/feedback-surveys/status":{"get":{"summary":"Get survey configuration status summary","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Status summary retrieved"}}}},"/cohorts/{cohortId}/feedback-surveys/schedule-times":{"get":{"summary":"Get calculated schedule times for midpoint/endpoint surveys","description":"Calculates when midpoint and endpoint surveys should be sent based on session schedule","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Schedule times calculated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SurveyScheduleTimes"}}}}}}}}},"/cohorts/{cohortId}/feedback-surveys/results/summary":{"get":{"summary":"Get survey results summary for callout banner","description":"Returns total submission count and breakdown by survey type for the cohort-level callout banner","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Survey results summary retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total":{"type":"integer"},"by_type":{"type":"object","properties":{"post_session":{"type":"integer"},"midpoint":{"type":"integer"},"endpoint":{"type":"integer"}}},"has_results":{"type":"boolean"}}}}}}}},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/feedback-surveys/results/line-item":{"get":{"summary":"Get survey line item status for a group","description":"Returns survey collection status for a specific group and survey type, used in inline session table rows","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"group_id","required":true,"schema":{"type":"string","format":"uuid"},"description":"The group to check"},{"in":"query","name":"survey_type","required":true,"schema":{"type":"string","enum":["post_session","midpoint","endpoint"]},"description":"The survey type to check"}],"responses":{"200":{"description":"Line item status retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"survey_type":{"type":"string"},"is_configured":{"type":"boolean"},"schedule_status":{"type":"string","nullable":true},"sent_at":{"type":"string","format":"date-time","nullable":true},"deadline":{"type":"string","format":"date","nullable":true},"completed":{"type":"integer"},"total":{"type":"integer"},"state":{"type":"string","enum":["collecting","complete","future","not_configured"]},"scheduled_for":{"type":"string","format":"date-time","nullable":true}}}}}}}},"400":{"description":"Missing required query parameters"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/feedback-surveys/results":{"get":{"summary":"Get aggregated survey results for visualization","description":"Returns parsed and aggregated Paperform submission data grouped by section with response distributions for bar chart visualization","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"form_type","required":true,"schema":{"type":"string","enum":["session_feedback","mid_program","final_survey","post_session","midpoint","endpoint"]},"description":"The form type or survey type to aggregate results for"},{"in":"query","name":"group_id","required":false,"schema":{"type":"string","format":"uuid"},"description":"Optional group ID to filter results by group members"},{"in":"query","name":"session_id","required":false,"schema":{"type":"string","format":"uuid"},"description":"Optional session ID to filter results for a specific session"}],"responses":{"200":{"description":"Aggregated results retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"form_type":{"type":"string"},"survey_type":{"type":"string"},"total_submissions":{"type":"integer"},"total_participants":{"type":"integer"},"sections":{"type":"array","items":{"type":"object","properties":{"section_name":{"type":"string"},"questions":{"type":"array","items":{"type":"object","properties":{"question_id":{"type":"string"},"question_text":{"type":"string"},"question_type":{"type":"string"},"responses":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"count":{"type":"integer"}}}},"total_responses":{"type":"integer"},"satisfaction_percentage":{"type":"number","nullable":true}}}}}}}}}}}}}},"400":{"description":"Missing form_type query parameter"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/feedback-surveys/coach-responses/{surveyType}":{"get":{"summary":"Get coach feedback submissions for CM review","description":"Returns coach survey config, questions, and all coach submissions for the specified survey type","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["midpoint","endpoint"]}}],"responses":{"200":{"description":"Coach feedback data retrieved"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/feedback-surveys/{surveyType}":{"get":{"summary":"Get survey config by type","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["post_session","midpoint","endpoint"]}}],"responses":{"200":{"description":"Survey config retrieved (or defaults if not configured)"}}},"post":{"summary":"Create or update survey config","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["post_session","midpoint","endpoint"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["paperform_id","email_subject","email_template"],"properties":{"paperform_id":{"type":"string"},"email_subject":{"type":"string"},"email_template":{"type":"string"},"survey_deadline":{"type":"string","format":"date"}}}}}},"responses":{"200":{"description":"Survey config saved"}}},"delete":{"summary":"Delete a survey config","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["post_session","midpoint","endpoint"]}}],"responses":{"204":{"description":"Survey config deleted"},"404":{"description":"Survey config not found"}}}},"/cohorts/{cohortId}/feedback-surveys/{surveyType}/schedule":{"post":{"summary":"Schedule a midpoint/endpoint survey","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["midpoint","endpoint"]}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"survey_deadline":{"type":"string","format":"date"}}}}}},"responses":{"200":{"description":"Survey scheduled"},"400":{"description":"Survey not configured or no sessions found"}}},"delete":{"summary":"Cancel a scheduled survey","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["midpoint","endpoint"]}}],"responses":{"200":{"description":"Schedule cancelled"},"400":{"description":"Survey not scheduled"}}}},"/cohorts/{cohortId}/feedback-surveys/{surveyType}/preview":{"post":{"summary":"Generate email preview with sample data","tags":["Feedback Surveys"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"surveyType","required":true,"schema":{"type":"string","enum":["post_session","midpoint","endpoint"]}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email_subject":{"type":"string"},"email_template":{"type":"string"},"survey_deadline":{"type":"string","format":"date"}}}}}},"responses":{"200":{"description":"Email preview generated"}}}},"/api/admin/frameworks":{"get":{"summary":"List all frameworks","description":"Returns every framework in the global glossary, ordered alphabetically by name.","tags":["Frameworks"],"responses":{"200":{"description":"Frameworks retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Framework"}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - internal admin role required"},"500":{"description":"Internal server error"}}},"post":{"summary":"Create a framework","description":"Adds a framework with a required name and optional description. Rejects a duplicate name (case-insensitive) with 409.","tags":["Frameworks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FrameworkInput"}}}},"responses":{"201":{"description":"Framework created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Framework"}}}}}},"400":{"description":"Bad request - name is required"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - internal admin role required"},"409":{"description":"Conflict - a framework with this name already exists"},"422":{"description":"Validation failed"},"500":{"description":"Internal server error"}}}},"/api/admin/frameworks/{id}":{"get":{"summary":"Get a framework by id","description":"Returns a single framework by its unique identifier.","tags":["Frameworks"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"The framework id"}],"responses":{"200":{"description":"Framework retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Framework"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - internal admin role required"},"404":{"description":"Framework not found"},"500":{"description":"Internal server error"}}},"put":{"summary":"Update a framework","description":"Updates a framework's name and/or description. Rejects a rename that collides with another framework's name (case-insensitive) with 409.","tags":["Frameworks"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"The framework id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FrameworkInput"}}}},"responses":{"200":{"description":"Framework updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Framework"}}}}}},"400":{"description":"Bad request - name is required"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - internal admin role required"},"404":{"description":"Framework not found"},"409":{"description":"Conflict - a framework with this name already exists"},"422":{"description":"Validation failed"},"500":{"description":"Internal server error"}}},"delete":{"summary":"Delete a framework","description":"Soft-deletes a framework so it no longer appears in the glossary.","tags":["Frameworks"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"The framework id"}],"responses":{"204":{"description":"Framework deleted successfully"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - internal admin role required"},"404":{"description":"Framework not found"},"500":{"description":"Internal server error"}}}},"/cohorts/{cohortId}/groups":{"get":{"summary":"List groups in a cohort with optional pagination and search","description":"Returns groups for a cohort with member counts and coach info. Supports server-side pagination via limit/offset and search by group name or participant name/email. When no limit is provided, returns all groups.\n","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"query","name":"limit","schema":{"type":"integer","minimum":1},"required":false,"description":"Maximum number of groups to return"},{"in":"query","name":"offset","schema":{"type":"integer","minimum":0},"required":false,"description":"Number of groups to skip (for pagination)"},{"in":"query","name":"search","schema":{"type":"string"},"required":false,"description":"Search term to filter by group name or participant name/email"}],"responses":{"200":{"description":"Paginated list of groups","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GroupWithMembers"}},"pagination":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of groups matching the query"},"limit":{"type":"integer"},"offset":{"type":"integer"},"hasMore":{"type":"boolean","description":"Whether more groups are available beyond the current page"}}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"post":{"summary":"Create a new group","description":"Creates a new group in the cohort","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"maxSize":{"type":"integer"},"coachId":{"type":"string","format":"uuid"}}}}}},"responses":{"201":{"description":"Group created"},"400":{"description":"Invalid input"},"404":{"description":"Cohort not found"},"409":{"description":"Group name already exists"}}}},"/cohorts/{cohortId}/groups/import/preview":{"post":{"summary":"Preview bulk group import","description":"Parses CSV data and returns a preview of groups to be created and participants to be assigned","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupImportPreviewRequest"}}}},"responses":{"200":{"description":"Import preview","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/GroupImportPreviewResponse"}}}}}},"400":{"description":"Invalid input"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/groups/import":{"post":{"summary":"Execute bulk group import","description":"Creates groups and assigns participants based on the provided data","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GroupImportExecuteRequest"}}}},"responses":{"201":{"description":"Groups imported successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/GroupImportResult"},"message":{"type":"string"}}}}}},"400":{"description":"Invalid input"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/groups/{groupId}":{"get":{"summary":"Get a group by ID","description":"Returns a single group with its members","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Group details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/GroupWithMembers"}}}}}},"404":{"description":"Cohort or group not found"}}},"put":{"summary":"Update a group","description":"Updates a group's details","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"maxSize":{"type":"integer"},"coachId":{"type":"string","format":"uuid"},"isActive":{"type":"boolean"},"timeSlotIdentifier":{"type":"string","nullable":true,"description":"Time slot identifier to assign (e.g. \"ACME-TS1\"), or null to unassign"}}}}}},"responses":{"200":{"description":"Group updated. When time slot is changed, response includes _timeSlotUpdate with sessionsUpdated and sessionsSkippedInvited counts (MED-648).\n"},"400":{"description":"Invalid input, time slot identifier not found, or time slot locked (all future sessions have invitations sent)\n"},"404":{"description":"Cohort or group not found"},"409":{"description":"Group name already exists"}}},"delete":{"summary":"Delete a group","description":"Soft deletes a group","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Group deleted"},"404":{"description":"Cohort or group not found"}}}},"/cohorts/{cohortId}/groups/{groupId}/invite-status":{"get":{"summary":"Get invite lock status for a group","description":"Returns the invite status for a group's future sessions. Used to determine if the group's time slot can be edited (MED-648). When all future sessions have invites sent, the time slot is locked.\n","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Invite status for the group","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"totalFutureSessions":{"type":"integer","description":"Total number of future sessions"},"invitedSessions":{"type":"integer","description":"Future sessions with invites already sent"},"uninvitedSessions":{"type":"integer","description":"Future sessions without invites sent"},"isFullyLocked":{"type":"boolean","description":"Whether time slot editing is fully locked"}}}}}}}},"404":{"description":"Cohort or group not found"}}}},"/cohorts/{cohortId}/groups/{groupId}/members":{"post":{"summary":"Assign a participant to a group","description":"Adds a participant to the group, removing them from any other group in the same cohort","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Participant assigned to group"},"400":{"description":"User is not a participant in this cohort"},"404":{"description":"Cohort or group not found"}}}},"/cohorts/{cohortId}/groups/{groupId}/members/{userId}":{"delete":{"summary":"Remove a participant from a group","description":"Removes a participant from the group","tags":["Groups"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"userId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Participant removed from group"},"404":{"description":"Cohort or group not found"}}}},"/health":{"get":{"summary":"Basic health check","description":"Returns basic health status of the API including version and commit info","tags":["Health"],"responses":{"200":{"description":"API is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"type":"string","example":"healthy"},"version":{"type":"string","example":"0.1.0"},"commit":{"type":"string","example":"abc1234"},"build":{"type":"string","example":"42","nullable":true},"environment":{"type":"string","example":"staging"},"timestamp":{"type":"string","format":"date-time"}}}}}}}}}}},"/health/ready":{"get":{"summary":"Readiness check","description":"Checks if the API is ready to serve requests (including database connectivity)","tags":["Health"],"responses":{"200":{"description":"API is ready","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"type":"string","example":"ready"},"database":{"type":"string","example":"connected"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"503":{"description":"Service not ready","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":false},"error":{"type":"string"},"data":{"type":"object","properties":{"status":{"type":"string","example":"not ready"},"database":{"type":"string","example":"disconnected"}}}}}}}}}}},"/health/full":{"get":{"summary":"Full health check with worker status","description":"Returns health status of API and all connected microservices (exports, communications, session-intelligence)","tags":["Health"],"responses":{"200":{"description":"Full health status including workers","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"api":{"type":"object","properties":{"status":{"type":"string"},"version":{"type":"string"},"build":{"type":"string","nullable":true},"commit":{"type":"string"},"environment":{"type":"string"}}},"workers":{"type":"array","items":{"type":"object","properties":{"service":{"type":"string"},"version":{"type":"string"},"build":{"type":"string","nullable":true},"commit":{"type":"string"},"environment":{"type":"string"},"status":{"type":"string","enum":["healthy","unreachable","error"]}}}},"timestamp":{"type":"string","format":"date-time"}}}}}}}}}}},"/cohorts/{cohortId}/intake/forms":{"get":{"summary":"List available Paperform forms","description":"Returns a list of available forms from Paperform for selection.\nIf Paperform API is not configured, returns configured: false.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Forms retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"configured":{"type":"boolean","description":"Whether Paperform API is configured"},"forms":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["active","draft","archived"]}}}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/{cohortId}/intake/configure":{"post":{"summary":"Configure intake form for cohort","description":"Configure the intake form for a cohort without sending emails.\nThis generates unique URLs with sgos_participant_id for each participant.\nUse this to set up the form before sending, or to update the form selection.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["form_id"],"properties":{"form_id":{"type":"string","description":"Paperform form ID to configure"}}}}}},"responses":{"200":{"description":"Intake form configured successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"intake_form_id":{"type":"string"},"intake_form_title":{"type":"string"},"participants_configured":{"type":"integer","description":"Number of participants who received unique URLs"}}},"message":{"type":"string"}}}}}},"400":{"description":"Invalid form ID or form not found"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"delete":{"summary":"Clear intake form configuration","description":"Clears the intake form configuration for a cohort by nulling intake_form_id,\nintake_configured_at, and intake_configured_by on the cohorts table.\nDoes not affect previously sent intake emails or collected responses.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Intake configuration cleared successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/intake/send":{"post":{"summary":"Send intake form emails to participants","description":"Send intake form invitation emails to participants.\nIf participant_ids is not provided, sends to all participants who haven't received the form yet.\n\nSupported merge tags in subject and body:\n- {{first_name}} - Participant's first name\n- {{last_name}} - Participant's last name\n- {{full_name}} - Participant's full name\n- {{email}} - Participant's email\n- {{cohort_name}} - Cohort name\n- {{form_link}} or {{intake_form_link}} - Unique intake form URL\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["form_id"],"properties":{"form_id":{"type":"string","description":"Paperform form ID"},"subject":{"type":"string","description":"Email subject (supports merge tags)"},"body":{"type":"string","description":"Email body HTML (supports merge tags)"},"participant_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Specific participant IDs to send to (optional)"}}}}}},"responses":{"200":{"description":"Intake emails sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sent":{"type":"integer"},"failed":{"type":"integer"},"errors":{"type":"array","items":{"type":"string"}}}},"message":{"type":"string"}}}}}},"400":{"description":"Invalid request or no participants to send to"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or form not found"}}}},"/cohorts/{cohortId}/intake/test":{"post":{"summary":"Send a test intake email to the logged-in user","description":"Sends a test intake form email to the currently logged-in user's email address.\nUses sample merge tag data (e.g., \"John Doe\") to preview how the email will look.\nSubject is prefixed with \"[TEST]\" to distinguish from real emails.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["form_id"],"properties":{"form_id":{"type":"string","description":"Paperform form ID"},"subject":{"type":"string","description":"Email subject (supports merge tags)"},"body":{"type":"string","description":"Email body HTML (supports merge tags)"}}}}}},"responses":{"200":{"description":"Test email sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sent":{"type":"boolean"},"email":{"type":"string","description":"Email address the test was sent to"}}},"message":{"type":"string"}}}}}},"400":{"description":"Invalid request or form not found"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/intake/backfill":{"post":{"summary":"Backfill historical Paperform responses for a cohort's matching form","description":"MED-942. Pulls all historical submissions for the given Paperform form\nvia the Paperform API and matches them to cohort participants by email\n(the same email-based matching logic MED-865 uses for inbound webhooks).\n\n- **Read-only against Paperform** — no Paperform-side edits, deletions, or webhook resends.\n- **Does NOT send any emails** to participants.\n- **Idempotent** — already-processed submissions (per `form_submissions.paperform_submission_id`) are skipped.\n- **Bounded** — fails with a clear error if the form has more than 2000 submissions.\n\nReturns a classified summary the UI surfaces to the Cohort Manager\n(matched, already-processed, unmatched, ambiguous, no-email-on-submission, failed).\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["form_id"],"properties":{"form_id":{"type":"string","description":"Paperform form ID to pull submissions from"}}}}}},"responses":{"200":{"description":"Backfill complete — see summary for per-submission outcomes","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total_submissions_fetched":{"type":"integer"},"matched":{"type":"array","items":{"type":"object","properties":{"paperform_submission_id":{"type":"string"},"cohort_participant_id":{"type":"string","format":"uuid"},"participant_email":{"type":"string"}}}},"already_processed":{"type":"integer","description":"Count of submissions already in form_submissions for this paperform_submission_id"},"unmatched":{"type":"array","items":{"type":"object","properties":{"paperform_submission_id":{"type":"string"},"participant_email":{"type":"string"}}}},"ambiguous":{"type":"array","items":{"type":"object","properties":{"paperform_submission_id":{"type":"string"},"participant_email":{"type":"string"},"match_count":{"type":"integer"}}}},"no_email":{"type":"array","items":{"type":"object","properties":{"paperform_submission_id":{"type":"string"}}}},"failed":{"type":"array","items":{"type":"object","properties":{"paperform_submission_id":{"type":"string"},"error":{"type":"string"}}}},"duration_ms":{"type":"integer"}}},"message":{"type":"string"}}}}}},"400":{"description":"Validation or operational error. Possible cases:\n- `form_id` missing\n- Paperform API not configured\n- Soft cap exceeded (more than 2000 submissions)\n- Paperform API fetch failed\n- Cohort not found\n"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"500":{"description":"Unexpected server error"}}}},"/cohorts/{cohortId}/intake/recipients":{"get":{"summary":"Get participants with intake status for resend modal","description":"Returns all participants in the cohort with their intake form status.\nUsed to populate the participant selection in the Resend Intake Form modal.\nEach participant includes their status: completed, pending, or not_configured.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Recipients retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"intake_status":{"type":"string","enum":["completed","pending","not_configured"]},"intake_form_sent_at":{"type":"string","format":"date-time","nullable":true},"intake_completed_at":{"type":"string","format":"date-time","nullable":true},"intake_reminder_count":{"type":"integer"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/intake/stats":{"get":{"summary":"Get intake form completion statistics","description":"Returns statistics about intake form completion for the cohort.\nIncludes counts of sent, completed, pending, and not yet sent.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Statistics retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of participants"},"sent":{"type":"integer","description":"Number who received the form"},"completed":{"type":"integer","description":"Number who completed the form"},"pending":{"type":"integer","description":"Number who received but haven't completed"},"not_sent":{"type":"integer","description":"Number who haven't received the form"},"completion_rate":{"type":"integer","description":"Completion percentage (0-100)"},"intake_form_id":{"type":"string","nullable":true},"intake_configured_at":{"type":"string","format":"date-time","nullable":true}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/intake/remind":{"post":{"summary":"Send reminder emails to participants","description":"Send reminder emails to participants who received the intake form but haven't completed it.\nMaximum 3 reminders per participant.\n\nIf participant_ids is not provided, sends to all eligible participants.\n\nSupports the same merge tags as /send endpoint.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"subject":{"type":"string","description":"Email subject (supports merge tags)"},"body":{"type":"string","description":"Email body HTML (supports merge tags)"},"participant_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Specific participant IDs to remind (optional)"}}}}}},"responses":{"200":{"description":"Reminders sent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sent":{"type":"integer"},"skipped":{"type":"integer","description":"Number skipped due to reminder limit"},"errors":{"type":"array","items":{"type":"string"}}}},"message":{"type":"string"}}}}}},"400":{"description":"Intake form not configured"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/intake/submissions/{participantId}":{"get":{"summary":"Get form submissions for a participant","description":"Returns all form submissions for a specific participant within the cohort.\nIncludes intake forms, feedback forms, and other Paperform submissions.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Participant ID"}],"responses":{"200":{"description":"Form submissions retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"form_type":{"type":"string","enum":["intake","feedback","survey"]},"paperform_form_id":{"type":"string"},"paperform_submission_id":{"type":"string"},"raw_payload":{"type":"object"},"submitted_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or participant not found"}}}},"/cohorts/{cohortId}/intake/process-webhooks":{"post":{"summary":"Process pending webhook events","description":"Manually trigger processing of pending Paperform webhook events.\nThis processes events that are in 'received' or 'failed' (pending retry) status.\nNormally called by a cron job, but can be triggered manually if needed.\n","tags":["Intake"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID (used for authorization)"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","default":10,"description":"Maximum number of events to process"}}}}}},"responses":{"200":{"description":"Processing completed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"processed":{"type":"integer"},"failed":{"type":"integer"},"skipped":{"type":"integer"},"errors":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/api/internal/export-data/survey":{"post":{"summary":"Internal survey export data (service-to-service)","description":"Returns CSV-ready survey data ({ columns, rows }) for the exports worker. Reuses the live Data Library survey queries. Requires the x-internal-key header.\n","tags":["Internal"],"security":[{"internalKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["surveyKind","cohortIds"],"properties":{"surveyKind":{"type":"string","enum":["participant_survey","session_feedback","coach_survey","coach_post_session"]},"cohortIds":{"type":"array","items":{"type":"string","format":"uuid"}},"formTypes":{"type":"array","items":{"type":"string"}},"surveyTypes":{"type":"array","items":{"type":"string"}},"groupId":{"type":"string"},"coachId":{"type":"string"},"sessionNumber":{"type":"string"},"flaggedOnly":{"type":"boolean"},"hasAbsences":{"type":"boolean"},"search":{"type":"string"}}}}}},"responses":{"200":{"description":"Columns + flattened rows for the export"},"400":{"description":"Invalid request body"},"401":{"description":"Missing or invalid internal key"},"503":{"description":"Internal API not configured (INTERNAL_API_KEY unset)"}}}},"/job-titles":{"get":{"summary":"Get all job titles","description":"Get all active job titles available for task assignment","tags":["Job Titles"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Job titles retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/JobTitle"}}}}}}},"401":{"description":"Unauthorized"}}}},"/job-titles/{id}":{"get":{"summary":"Get job title by ID","description":"Get a single job title by its ID","tags":["Job Titles"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Job title ID"}],"responses":{"200":{"description":"Job title retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/JobTitle"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Job title not found"}}}},"/api/matching-sessions/{id}/resume":{"post":{"summary":"Resume a matching session (leg F)","description":"Re-mint a fresh single-use navigation token for an existing matching attempt.\n\nAccess is STAFF-only (`internal_admin`, `internal_user`), enforced by the role gate on\nthis route. There is no row-level ownership check: any staff user may resume any\nmatching session. This is deliberate, so several staff can collaborate on one\nmatching-server session. `matching_sessions.opened_by_admin_id` still records who\nSTARTED the session. The resuming user is recorded in the application log only.\n\nNote: this router mounts at the top level (`/api/matching-sessions`), outside the\n`/api/cohorts` tree, so it does NOT inherit `requireCohortAccess`. The role gate on\nthis route is the only access control here. Add one to every new route on this router.\n\nOn success the row transitions to `in_progress` (per the §7 state machine). The\nresponse includes `status` so the SPA can update its sessions list immediately\nwithout a follow-up GET.\n\nPer §11.7, an outbound failure to matching-server does NOT mutate the row's status.\nThe caller can retry by clicking Resume again.\n","tags":["MatchingSessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Token re-minted. Row transitioned to `in_progress`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResumeMatchingSessionResponse"}}}},"401":{"description":"Unauthorized -- missing or invalid SGOS session."},"403":{"description":"Forbidden -- the caller is not staff. The body is\n`{ success: false, error: 'Insufficient permissions' }` and carries NO `code` field.\n"},"404":{"description":"Matching session not found. Code: `MATCHING_SESSION_NOT_FOUND`.\n"},"409":{"description":"Row exists but cannot be resumed. Codes:\n- `MATCHING_SESSION_EXPIRED` -- row's status is `expired` (terminal).\n- `MATCHING_SESSION_STILL_CREATING` -- row's status is `submitting` (leg C in flight).\n- `MATCHING_SESSION_FAILED_NO_EXTERNAL_SESSION` -- row's status is `failed` and no\n  external_session_id exists, so there is nothing on matching-server to re-mint.\n"},"502":{"description":"Outbound matching-server call failed. Codes:\n- `MATCHING_SERVER_AUTH_REJECTED` -- matching-server returned 401 (secret drift; oncall alert emitted).\n- `MATCHING_SERVER_UNREACHABLE` -- network failure or non-2xx other than 401.\n- `MATCHING_SERVER_INVALID_RESPONSE` -- response missing `token`.\n"},"503":{"description":"`MATCHING_SERVER_NOT_CONFIGURED` -- matching-server env vars are missing\n(only possible in non-production deployments).\n"}}}},"/cohorts/{cohortId}/paperform-deletions":{"get":{"summary":"Get Paperform deletion banner state for a cohort","description":"Returns the banner state for the cohort Participants tab. Surfaces\nform_submissions that the daily sweep has soft-deleted within the last\n7 days, excluding rows classified as routine test-submission cleanup\n(submission lived less than 24 hours and was never processed).\n\n`shouldShow` is true when there are matching deletions AND at least one\nof them is newer than the user's last dismissal for this cohort.\n","tags":["PaperformDeletions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Banner state retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PaperformDeletionsBannerState"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation failed (invalid cohort ID)"},"500":{"description":"Internal server error"}}}},"/cohorts/{cohortId}/paperform-deletions/dismiss":{"post":{"summary":"Dismiss the Paperform deletion banner for the current user","description":"Marks the banner as dismissed for the authenticated user and cohort.\nDismissal is stored in `user_preferences` under the key\n`paperform_deletions_banner_dismissed_{cohortId}`. The banner reappears\nautomatically when the daily sweep detects a new deletion after this\ndismissal point.\n","tags":["PaperformDeletions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Banner dismissed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"dismissed_at":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation failed (invalid cohort ID)"},"500":{"description":"Internal server error"}}}},"/api/participant/sessions/{sessionId}/active-state":{"get":{"summary":"Get composite read-only state for the participant Active Session screen (AC","description":"Returns everything the participant active session screen needs in one\nround trip: session metadata, leader, sections, slide deck, current\nsection index, AI features flag, and the AC #12 commitment trigger.\n\nAuthorization: the caller must be an active member of the session's\ngroup (`group_members` row with `left_at IS NULL`). Otherwise 403.\n\nThis endpoint is intended to be polled at <= 2s cadence by the\nfrontend to satisfy AC #6 (participant view updates within 2 seconds\nof leader action) and IV2.\n","tags":["Participant Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Active session state for the participant","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantActiveSessionState"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/participant/sessions/{sessionId}/notes":{"get":{"summary":"Get the participant's private session notes (AC","description":"Returns the active notes row for the caller's (session, participant)\npair, or an empty string when none exists. Notes are strictly private:\nthere is no coach-facing read endpoint and the repository enforces\nparticipant scoping.\n","tags":["Participant Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Notes payload (may be empty)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantNotesResponse"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}},"put":{"summary":"Upsert (autosave) the participant's private session notes (AC","description":"Autosave path called by the frontend on debounced editor changes.\nEmpty `notes_html` after trim soft-deletes the row so the participant\ncan clear by emptying the editor. HTML is sanitized server-side\nbefore persisting (sanitizeRichTextHtml).\n","tags":["Participant Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantNotesUpsertRequest"}}}},"responses":{"200":{"description":"Updated notes payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantNotesResponse"}}}}}},"400":{"description":"Body missing notes_html or wrong type"},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/participant/sessions/{sessionId}/commitment":{"get":{"summary":"Get the participant's commitment for the session (AC","description":"Returns the active commitment row for the caller's (session, participant)\npair, or an empty string when none exists. Reads through\n`session_participant_commitments` — the same table the coach uses on\nthe MED-794 post-session screen — so the E3.4 view sees the commitment\nregardless of authorship.\n","tags":["Participant Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Commitment payload (may be empty)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantCommitmentResponse"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}},"put":{"summary":"Save the participant's commitment for the session (AC","description":"Participant-self-authored upsert. Writes through to\n`session_participant_commitments` with `coach_user_id = NULL` and\n`authored_by_user_id = participant`. MED-1216: `commitment_text` must be\nnon-empty (whitespace-only rejected, 400/AC1). The participant always\nwins — this write overwrites any coach-entered value and transfers\nownership to the participant, locking the coach out thereafter (AC5).\n","tags":["Participant Active Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantCommitmentUpsertRequest"}}}},"responses":{"200":{"description":"Updated commitment payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantCommitmentResponse"}}}}}},"400":{"description":"commitment_text missing, wrong type, or empty/whitespace-only (MED-1216 / AC1)"},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/participant/cohorts":{"get":{"summary":"List all cohorts the participant belongs to","description":"Returns all cohorts the authenticated participant is enrolled in, ordered by start date descending (most recent first).\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of cohorts","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"start_date":{"type":"string","format":"date","nullable":true},"end_date":{"type":"string","format":"date","nullable":true},"partner_name":{"type":"string","nullable":true}}}}}}}}},"401":{"description":"Unauthorized — authentication required"},"500":{"description":"Internal server error"}}}},"/api/participant/dashboard":{"get":{"summary":"Get participant dashboard data","description":"Returns all data needed for the participant dashboard, scoped to the authenticated participant. Includes cohort info, announced modalities with progress, upcoming sessions, starting soon banner data (sessions within 30 minutes), and action items (unscheduled 1:1 sessions). When the `live_session_delivery` feature flag is on, a non-null `refresh` object is included carrying the MED-938 / E3.5 two-column dashboard data (unified upcoming list with peer-leader state, completed sessions with feedback status, program/cohort-manager/group sidebar data, modality progress, and cohort-level resources). When the flag is off, `refresh` is null and the legacy fields drive the dashboard.\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"cohort_id","schema":{"type":"string","format":"uuid"},"required":false,"description":"Cohort ID to scope the dashboard to. If omitted, defaults to the most recently active cohort.\n"}],"responses":{"200":{"description":"Dashboard data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"cohort":{"type":"object","nullable":true},"announced_modalities":{"type":"array"},"upcoming_sessions":{"type":"array"},"starting_soon":{"type":"object","nullable":true},"action_items":{"type":"array"},"workshops":{"type":"array","nullable":true,"description":"Workshop sessions with attendance status and resources. Only present when the workshop modality is announced.\n"},"live_session_delivery_enabled":{"type":"boolean","description":"MED-1181 — the cohort's group_coaching Live Session Delivery gate. The dashboard gates its legacy LSD banners (live / starting-soon / peer-leader) and the two-column layout switch on this instead of raw data-presence.\n"},"refresh":{"type":"object","nullable":true,"description":"MED-938 / E3.5 two-column dashboard data. Non-null only when the live_session_delivery feature flag is on.\n","properties":{"is_peer_led":{"type":"boolean"},"upcoming_sessions":{"type":"array","description":"Unified list across all modalities (incl. group sessions), each with reflection + peer-leader state.\n"},"completed_sessions":{"type":"array","description":"Completed sessions with attendance + feedback status."},"program":{"type":"object","nullable":true},"cohort_manager":{"type":"object","nullable":true},"group":{"type":"object","nullable":true},"modalities":{"type":"array"},"resources":{"type":"array","description":"Cohort-level participant resources."}}}}}}}}}},"401":{"description":"Unauthorized — authentication required"},"500":{"description":"Internal server error"}}}},"/api/participant/sessions/{sessionId}/schedule":{"post":{"summary":"Schedule or reschedule a 1:1 coaching session","description":"Allows the authenticated participant to schedule an unscheduled 1:1 session or reschedule an already-scheduled 1:1 session. Sends a calendar invitation to both the participant and the assigned coach.\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The 1:1 session ID to schedule"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduled_start","timezone"],"properties":{"scheduled_start":{"type":"string","format":"date-time","description":"ISO 8601 UTC datetime for the session start"},"timezone":{"type":"string","description":"IANA timezone identifier (e.g., America/Denver)"},"meeting_link":{"type":"string","description":"Meeting URL (editable, pre-populated from coach profile)"}}}}}},"responses":{"200":{"description":"Session scheduled successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"scheduled":{"type":"boolean"},"session_id":{"type":"string"}}}}}}}},"400":{"description":"Bad request — invalid input or session not schedulable"},"401":{"description":"Unauthorized — authentication required"},"403":{"description":"Forbidden — session does not belong to this participant"},"404":{"description":"Session not found"},"500":{"description":"Internal server error"}}}},"/api/participant/sessions/{sessionId}/cancel":{"post":{"summary":"Cancel a scheduled Calendly session","description":"Cancels the participant's Calendly booking and updates the session status. For 1:1 sessions, the slot reverts to \"unscheduled\" so it can be rebooked. For check-ins, the status is set to \"cancelled\" (record stays for history). Only works for sessions with scheduling_method = 'calendly' and status = 'scheduled'.\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The session ID to cancel"}],"responses":{"200":{"description":"Session canceled successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"canceled":{"type":"boolean"},"session_id":{"type":"string"},"new_status":{"type":"string","enum":["unscheduled","cancelled"]}}}}}}}},"400":{"description":"Bad request — session not cancelable (wrong method or status)"},"401":{"description":"Unauthorized — authentication required"},"403":{"description":"Forbidden — session does not belong to this participant"},"404":{"description":"Session not found"},"500":{"description":"Internal server error"}}}},"/api/participant/sessions/{sessionId}/reschedule":{"post":{"summary":"Reschedule a scheduled Calendly session","description":"Reschedules a Calendly booking by canceling the existing booking and creating a new one at the selected time. The session status remains \"scheduled\" (AC7). If the new booking fails after cancel, the session reverts to \"unscheduled\" (1:1) or \"cancelled\" (check-in). Only works for sessions with scheduling_method = 'calendly' and status = 'scheduled'.\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The session ID to reschedule"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduled_start","timezone"],"properties":{"scheduled_start":{"type":"string","format":"date-time","description":"ISO 8601 UTC datetime for the new session start"},"timezone":{"type":"string","description":"IANA timezone identifier (e.g., America/Denver)"},"notes":{"type":"string","description":"Optional notes for the coach"}}}}}},"responses":{"200":{"description":"Session rescheduled successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"rescheduled":{"type":"boolean"},"session_id":{"type":"string"},"scheduling_method":{"type":"string"}}}}}}}},"400":{"description":"Bad request — session not reschedulable or new time unavailable"},"401":{"description":"Unauthorized — authentication required"},"403":{"description":"Forbidden — session does not belong to this participant"},"404":{"description":"Session not found"},"500":{"description":"Internal server error"}}}},"/api/participant/coaches/{coachId}/availability":{"get":{"summary":"Fetch coach's Calendly availability","description":"Returns available time slots for a coach's 1:1 event type from Calendly. If the coach is not connected to Calendly or hasn't configured a 1:1 event type, returns empty slots with connection status flags. Calendly returns max 7-day windows — paginate by shifting start/end times.\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"coachId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Coach user ID (not profile ID)"},{"in":"query","name":"start_time","required":true,"schema":{"type":"string","format":"date-time"},"description":"Start of availability window (ISO 8601)"},{"in":"query","name":"end_time","required":true,"schema":{"type":"string","format":"date-time"},"description":"End of availability window (ISO 8601, max 7 days from start)"}],"responses":{"200":{"description":"Availability data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"connected":{"type":"boolean","description":"Whether coach has Calendly connected"},"event_type_configured":{"type":"boolean","description":"Whether coach has a 1:1 event type mapped"},"slots":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string"},"start_time":{"type":"string","format":"date-time"},"end_time":{"type":"string","format":"date-time"}}}}}}}}}}},"400":{"description":"Missing required parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/participant/coaches/{coachId}/checkin-availability":{"get":{"summary":"Fetch coach's Calendly check-in availability","description":"Returns available time slots for a coach's check-in event type from Calendly. Uses the calendly_event_type_uri_20min mapping (check-in duration).\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"coachId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Coach user ID (not profile ID)"},{"in":"query","name":"start_time","required":true,"schema":{"type":"string","format":"date-time"},"description":"Start of availability window (ISO 8601)"},{"in":"query","name":"end_time","required":true,"schema":{"type":"string","format":"date-time"},"description":"End of availability window (ISO 8601, max 7 days from start)"}],"responses":{"200":{"description":"Check-in availability data","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"connected":{"type":"boolean"},"event_type_configured":{"type":"boolean"},"slots":{"type":"array"}}}}}}}},"400":{"description":"Missing required parameters"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/participant/check-ins/schedule":{"post":{"summary":"Schedule a group coaching check-in","description":"Creates a new check-in session record and books it via Calendly. Unlike 1:1 scheduling, the session is created at booking time. Only one active (scheduled) check-in per participant per cohort is allowed.\n","tags":["Participant Portal"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cohort_id","scheduled_start","timezone"],"properties":{"cohort_id":{"type":"string","format":"uuid","description":"Cohort ID"},"scheduled_start":{"type":"string","format":"date-time","description":"ISO 8601 UTC datetime for the check-in start"},"timezone":{"type":"string","description":"IANA timezone identifier (e.g., America/Denver)"},"notes":{"type":"string","description":"Optional notes for the coach"},"huddle_type":{"type":"string","enum":["intro","midpoint_checkin","general"],"description":"MED-699: Check-in type. Defaults to \"general\" (legacy behavior — creates a new session record). \"intro\" and \"midpoint_checkin\" update the pre-existing placeholder created by MED-697; the placeholder must be in status=unscheduled.\n"}}}}}},"responses":{"200":{"description":"Check-in scheduled successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"scheduled":{"type":"boolean"},"session_id":{"type":"string"},"scheduling_method":{"type":"string"}}}}}}}},"400":{"description":"Bad request — validation failed, active check-in exists (general), or no unscheduled placeholder found (intro/midpoint_checkin).\n"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/participant/sessions/{sessionId}/post-session/summary":{"get":{"summary":"Composite read for the participant Post-Session screen (AC1-AC17 bootstrap)","description":"Returns everything the post-session page needs in one round trip:\nsession metadata + cohort flags + feedback + notes + commitment +\npost-session step state + sidebar card data. The frontend calls this\nonce on page mount and uses scoped mutations afterwards.\n\nAuthorization: caller must be an active member of the session's\ngroup. Otherwise 403.\n","tags":["Participant Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Composite post-session payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantPostSessionSummary"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/participant/sessions/{sessionId}/post-session/feedback":{"get":{"summary":"Get the participant's post-session feedback row (Step 1)","description":"Returns the participant's current feedback (draft OR submitted) for\nthis session, or an empty draft shell when none exists.\n","tags":["Participant Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Feedback payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantFeedbackPayload"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}},"put":{"summary":"Autosave / Save Draft for the feedback form (AC5)","description":"Partial draft upsert. Any of the four fields may be omitted; only\nthe provided fields are merged into the row. Status is forced to\n'draft' — use the /submit endpoint to transition to 'submitted'.\n\nReturns 409 FEEDBACK_LOCKED if the row is already submitted (AC5\nlock).\n","tags":["Participant Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertFeedbackDraftRequest"}}}},"responses":{"200":{"description":"Updated draft payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantFeedbackPayload"}}}}}},"400":{"description":"Body field type mismatch"},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group","OR marked absent (MED-1063)":null},"404":{"description":"Session not found OR live_session_delivery flag off"},"409":{"description":"Feedback already submitted (FEEDBACK_LOCKED)"}}}},"/api/participant/sessions/{sessionId}/post-session/feedback/submit":{"post":{"summary":"Submit the participant's feedback (AC5)","description":"Flips the row to status='submitted', stamps submitted_at, and locks\nthe feedback (AC5). Phase 1 is one-way — subsequent draft writes on\nthis row return 409 FEEDBACK_LOCKED.\n\nsatisfaction_rating is REQUIRED at submission time. Text fields\nfall back to empty string if omitted.\n","tags":["Participant Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitFeedbackRequest"}}}},"responses":{"200":{"description":"Submitted feedback payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantFeedbackPayload"}}}}}},"400":{"description":"satisfaction_rating missing or out of 1-5 range"},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group","OR marked absent (MED-1063)":null},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/participant/sessions/{sessionId}/post-session/notes-reviewed":{"put":{"summary":"Mark Step 2 (Review Notes) as reviewed (AC14)","description":"Idempotently stamps notes_reviewed_at on\nsession_participant_post_session_state. Drives the Step 2 accordion\ncollapse and contributes to the Done-state computation (AC14).\n","tags":["Participant Post-Session"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Updated post-session state","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ParticipantPostSessionStatePayload"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not an active member of the session's group"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/cohorts/{cohortId}/participants":{"get":{"summary":"List participants for a cohort","description":"Get a paginated list of participants with optional filtering","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by name or email"},{"in":"query","name":"status","schema":{"type":"string","enum":["pending","active","withdrawn"]}},{"in":"query","name":"groupId","schema":{"type":"string","format":"uuid"}},{"in":"query","name":"flaggedOnly","schema":{"type":"string","enum":["true","false"]},"description":"When 'true', only return participants with open concern flags"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"Participants retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Participant"}},"meta":{"type":"object","properties":{"pagination":{"type":"object"},"summary":{"type":"object"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"post":{"summary":"Add a participant to cohort","description":"Create a new participant enrollment. Creates user if email doesn't exist.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","first_name","last_name"],"properties":{"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"},"personality_type":{"type":"string"},"title":{"type":"string"},"department":{"type":"string"},"location":{"type":"string"},"custom_fields":{"type":"object"}}}}}},"responses":{"201":{"description":"Participant added successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"409":{"description":"Participant already enrolled in cohort"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/participants/import/preview":{"post":{"summary":"Preview CSV import","description":"Validate CSV data and return preview with duplicate detection","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mappings","csvData"],"properties":{"mappings":{"type":"array","items":{"$ref":"#/components/schemas/CSVColumnMapping"}},"csvData":{"type":"array","items":{"type":"array","items":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Import preview generated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"totalRows":{"type":"integer"},"validRows":{"type":"integer"},"errorRows":{"type":"integer"},"duplicateRows":{"type":"integer"},"rows":{"type":"array"}}}}}}}},"400":{"description":"Email column mapping required"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/participants/import":{"post":{"summary":"Execute CSV import","description":"Import participants from CSV data with column mappings","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mappings","csvData"],"properties":{"mappings":{"type":"array","items":{"$ref":"#/components/schemas/CSVColumnMapping"}},"csvData":{"type":"array","items":{"type":"array","items":{"type":"string"}}},"skipDuplicates":{"type":"boolean","default":true}}}}}},"responses":{"200":{"description":"Import completed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"imported":{"type":"integer"},"skipped":{"type":"integer"},"usersCreated":{"type":"integer"},"errors":{"type":"array"}}}}}}}},"400":{"description":"Email column mapping required"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}":{"get":{"summary":"Get participant by ID","description":"Retrieve a single participant by ID","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Participant retrieved successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}},"put":{"summary":"Update participant","description":"Update participant enrollment details","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"personality_type":{"type":"string"},"title":{"type":"string"},"department":{"type":"string"},"location":{"type":"string"},"custom_fields":{"type":"object"},"status":{"type":"string","enum":["pending","active","withdrawn"]},"withdrawal_reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Participant updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"},"422":{"description":"Validation error"}}},"delete":{"summary":"Remove participant from cohort","description":"Soft delete participant enrollment","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Participant removed successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/enrollment-history":{"get":{"summary":"Get participant enrollment history","description":"Get other cohorts this participant is enrolled in (excluding current cohort)","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Enrollment history retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_name":{"type":"string"},"partner_name":{"type":"string"},"status":{"type":"string","enum":["pending","active","withdrawn"]},"enrolled_at":{"type":"string","format":"date-time"},"withdrawn_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/absence-follow-ups":{"get":{"summary":"Get all absence follow-ups for one participant (Level 2)","description":"MED-738: Returns the participant header context (name, group, coach) plus the full ordered list of pending and past absence follow-ups in this cohort — pending first (chronological newest-first), then past chronological newest-first. :participantId is the cohort_participant_id.\n","tags":["Attendance Follow-Ups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Participant follow-up detail"},"401":{"description":"Unauthorized"},"404":{"description":"No follow-ups found for this participant in this cohort"}}}},"/cohorts/{cohortId}/participants/{participantId}/cohort-history":{"get":{"summary":"Get participant cohort history","description":"Get full cohort history for a participant including all cohorts they've been enrolled in with attendance statistics","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cohort history retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"cohort_id":{"type":"string","format":"uuid"},"cohort_code":{"type":"string","nullable":true},"cohort_name":{"type":"string"},"cohort_status":{"type":"string","enum":["draft","in_design","in_prep","ready_to_launch","live","wrap_up","completed","cancelled","archived"]},"start_date":{"type":"string","format":"date","nullable":true},"end_date":{"type":"string","format":"date","nullable":true},"enrolled_at":{"type":"string","format":"date-time"},"sessions_attended":{"type":"integer"},"total_sessions":{"type":"integer"},"attendance_percentage":{"type":"integer","nullable":true}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/email-history":{"get":{"summary":"Get email history for a participant","description":"Get all emails sent to this participant within the current cohort context, ordered by most recent first","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Email history retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"email_type":{"type":"string"},"subject":{"type":"string"},"to_email":{"type":"string","format":"email"},"status":{"type":"string","enum":["queued","sent","delivered","delivery_delayed","bounced","complained","failed"]},"sent_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/absence-email-history":{"get":{"summary":"Get absence email history for a participant","description":"Returns all sent absence emails for a participant across all sessions in the cohort, ordered by most recent first. Only includes emails with status \"sent\".","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The cohort ID"},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The cohort participant ID"}],"responses":{"200":{"description":"List of sent absence emails for the participant","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"success"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"session_number":{"type":"integer"},"session_date":{"type":"string","format":"date-time"},"template_name":{"type":"string","nullable":true},"rendered_subject":{"type":"string","nullable":true},"rendered_body":{"type":"string","nullable":true},"sent_at":{"type":"string","format":"date-time"},"sent_by_name":{"type":"string","nullable":true}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or participant not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/identity":{"put":{"summary":"Update participant core identity","description":"Update first name, last name, or email. This affects all cohort enrollments for this user.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Identity updated successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"},"409":{"description":"Email already in use by another user"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/participants/{participantId}/withdraw":{"patch":{"summary":"Withdraw a participant from the cohort","description":"Soft-transitions participant status to 'withdrawn', records withdrawal date, and cancels scheduled emails. Use this instead of DELETE for live cohorts.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Participant withdrawn successfully"},"400":{"description":"Participant is already withdrawn"},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/reactivate":{"patch":{"summary":"Re-activate a withdrawn participant","description":"Restores participant status from 'withdrawn' to 'active', clears withdrawal date. Participant resumes appearing in future email send lists.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Participant re-activated successfully"},"400":{"description":"Only withdrawn participants can be re-activated"},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/cohort-attendance":{"get":{"summary":"Get cohort attendance and engagement ratings for a participant","description":"Returns attendance summary (attended, partial, absent counts) and per-session engagement ratings for the participant within this cohort","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cohort attendance data retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"sessions_attended":{"type":"integer"},"sessions_partial":{"type":"integer"},"sessions_absent":{"type":"integer"},"total_sessions":{"type":"integer"},"attendance_percentage":{"type":"integer","nullable":true},"engagement_ratings":{"type":"array","items":{"type":"object","properties":{"session_number":{"type":"integer","nullable":true},"session_title":{"type":"string"},"rating":{"type":"integer"},"coach_name":{"type":"string"},"session_date":{"type":"string","format":"date-time","nullable":true}}}},"average_engagement_rating":{"type":"number","nullable":true}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/survey-reminders":{"get":{"summary":"Get survey reminder history for a participant (MED-95)","description":"Get a list of survey reminders sent to this participant showing midpoint/endpoint survey nudges","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Survey reminder history retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"survey_type":{"type":"string","enum":["midpoint","endpoint"]},"nudge_number":{"type":"integer","enum":[1,2]},"sent_at":{"type":"string","format":"date-time"},"display_label":{"type":"string","description":"Human-readable label like \"Midpoint Survey Reminder 1\""}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Participant or cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/concern-flags":{"get":{"summary":"Get concern flags for a participant","description":"Returns all concern flags raised for this participant across all sessions.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of concern flags for the participant"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/participants/{participantId}/concern-flags/{flagId}":{"patch":{"summary":"Update concern flag status","description":"Resolve or dismiss a participant concern flag. Resolving marks the flag as addressed with optional resolution notes. Dismissing marks it as dismissed.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"flagId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["resolve","dismiss"],"description":"The action to take on the concern flag"},"resolution_notes":{"type":"string","description":"Optional notes about the resolution"}}}}}},"responses":{"200":{"description":"Concern flag updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["addressed","dismissed"]},"resolution_notes":{"type":"string"},"addressed_by_name":{"type":"string"}}}}}}}},"400":{"description":"Bad request - invalid action"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort, participant, or concern flag not found"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/participants/{participantId}/internal-notes":{"get":{"summary":"Get internal Medley notes for a participant (MED-877)","description":"Returns the participant's internal notes as sanitized HTML. Restricted to Medley admins and cohort managers only.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Internal notes retrieved","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"internal_notes":{"type":"string","nullable":true,"description":"Sanitized HTML or null when no notes exist"}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - requires internal_admin or internal_user role"},"404":{"description":"Participant not found"}}},"patch":{"summary":"Update internal Medley notes for a participant (MED-877)","description":"Saves internal notes for the participant. HTML is sanitized server-side before storage. Restricted to Medley admins and cohort managers only.","tags":["Participants"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"participantId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["internal_notes"],"properties":{"internal_notes":{"type":"string","nullable":true,"description":"HTML body of the note; pass null or empty string to clear"}}}}}},"responses":{"200":{"description":"Internal notes updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"internal_notes":{"type":"string","nullable":true}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - requires internal_admin or internal_user role"},"404":{"description":"Participant not found"},"422":{"description":"Validation error"}}}},"/partners":{"get":{"summary":"List all partners","description":"Get a paginated list of partners with optional search","tags":["Partners"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term for partner name"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Partners retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Partner"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new partner","description":"Create a new partner organization","tags":["Partners"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePartnerRequest"}}}},"responses":{"201":{"description":"Partner created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Partner"}}}}}},"401":{"description":"Unauthorized"},"409":{"description":"Partner code already exists"},"422":{"description":"Validation error"}}}},"/partners/check-code/{code}":{"get":{"summary":"Check if partner code is available","description":"Verify if a partner code can be used for a new partner","tags":["Partners"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"code","required":true,"schema":{"type":"string","pattern":"^[A-Za-z]{2,4}$"},"description":"Partner code to check (2-4 letters)"}],"responses":{"200":{"description":"Code availability checked","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"available":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"}}}},"/partners/{id}":{"get":{"summary":"Get partner by ID","description":"Retrieve a single partner by its UUID","tags":["Partners"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Partner retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Partner"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Partner not found"}}},"put":{"summary":"Update a partner","description":"Update an existing partner's information","tags":["Partners"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"primary_contact_name":{"type":"string"},"primary_contact_email":{"type":"string","format":"email"},"description":{"type":"string"},"website":{"type":"string","format":"uri"},"is_active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Partner updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Partner"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Partner not found"},"422":{"description":"Validation error"}}},"delete":{"summary":"Delete a partner","description":"Soft delete a partner (sets deleted_at timestamp)","tags":["Partners"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Partner deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Partner not found"}}}},"/partners/{id}/deletion-impact":{"get":{"summary":"Get partner deletion impact","description":"Returns counts of downstream records (active cohorts via programs, partner coaches) that would be orphaned by deleting this partner. Used by the UI to warn admins before confirming deletion.","tags":["Partners"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deletion impact retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"active_cohort_count":{"type":"integer","description":"Number of non-deleted cohorts attached to non-deleted programs of this partner"},"partner_coach_count":{"type":"integer","description":"Number of non-deleted coach_profiles with this partner_id"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Partner not found"}}}},"/partners/{partnerId}/coaches":{"get":{"summary":"List partner coaches","description":"Get all coaches with coach_type 'partner' linked to this partner via partner_id. Includes assigned group counts.","tags":["Partners"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"partnerId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Partner organization ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":50}},{"in":"query","name":"sortBy","schema":{"type":"string","enum":["name","email","timezone","groups","status"],"default":"name"}},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["ASC","DESC"],"default":"ASC"}}],"responses":{"200":{"description":"Partner coaches retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string"},"home_base_timezone":{"type":"string"},"status":{"type":"string","enum":["onboarding","activated","inactive"]},"assigned_group_count":{"type":"integer"},"avatar_url":{"type":"string"}}}},"pagination":{"type":"object"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Partner not found"}}}},"/partners/{partnerId}/contacts":{"get":{"summary":"List partner contacts","description":"Get all contacts for a specific partner","tags":["Partner Contacts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"partnerId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contacts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PartnerContact"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Partner not found"}}},"post":{"summary":"Create a contact","description":"Create a new contact for a partner","tags":["Partner Contacts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"partnerId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["first_name","last_name","email"],"properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"},"job_title":{"type":"string"}}}}}},"responses":{"201":{"description":"Contact created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PartnerContact"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Partner not found"},"422":{"description":"Validation error"}}}},"/partners/{partnerId}/contacts/{id}":{"get":{"summary":"Get contact by ID","description":"Retrieve a single partner contact by ID","tags":["Partner Contacts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"partnerId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contact retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PartnerContact"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Contact not found"}}},"put":{"summary":"Update a contact","description":"Update an existing partner contact","tags":["Partner Contacts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"partnerId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"email":{"type":"string","format":"email"},"job_title":{"type":"string"}}}}}},"responses":{"200":{"description":"Contact updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PartnerContact"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Contact not found"},"422":{"description":"Validation error"}}},"delete":{"summary":"Delete a contact","description":"Soft delete a partner contact","tags":["Partner Contacts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"partnerId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contact deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Contact not found"}}}},"/partners/{partnerId}/contacts/{id}/cohorts":{"get":{"summary":"Get contact's cohorts","description":"Get cohorts where this contact is the primary contact","tags":["Partner Contacts"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"partnerId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Cohorts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"status":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Contact not found"}}}},"/api/participant/sessions/{sessionId}/post-session":{"get":{"tags":["Peer Leader Post-Session"],"summary":"Composite bundle for the peer leader post-session screen","description":"Returns session metadata, participants with attendance, peer-leader\nengagement ratings, the peer leader's own feedback row (if any),\nAI features gate, next session date, and the recap email default\ntemplate. Single round-trip on page load.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Bundle","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PeerLeaderPostSessionBundle"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Caller is not the peer leader for this session"},"404":{"description":"Session not found"},"500":{"description":"Server error"}}}},"/api/participant/sessions/{sessionId}/post-session/leader-feedback":{"put":{"tags":["Peer Leader Post-Session"],"summary":"Upsert the peer leader's Medley feedback responses (AC9/10)","description":"NOTE the `leader-feedback` sub-path. The participant E3.4 post-session\nroutes (MED-806) are mounted on the same `/participant/sessions` prefix\nand own `/post-session/feedback`; the peer leader's two-section form is a\ndistinct surface persisting to `peer_leader_session_feedback`, so it uses\na disambiguated path to avoid the Express route collision.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["responses"],"properties":{"responses":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"responses":{"200":{"description":"Feedback persisted"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Caller is not the peer leader for this session"},"500":{"description":"Server error"}}}},"/api/participant/sessions/{sessionId}/post-session/attendance":{"put":{"tags":["Peer Leader Post-Session"],"summary":"Mark attendance + engagement for the session (AC4/5, IV2)","description":"Writes through the existing `MarkAttendanceRequest` backend — the same\n`session_attendance` + `participant_engagement_ratings` tables a coach\nwrites (IV2). The peer leader's own \"You\" row is just another entry in\n`attendance`. Status is one of attended/partial/absent; absent_reason is\nrequired for absent rows. Engagement ratings (1-5) are keyed by\nparticipant_id and apply to attended/partial participants.\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attendance"],"properties":{"attendance":{"type":"array","items":{"type":"object","required":["user_id","status"],"properties":{"user_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["attended","partial","absent"]},"absent_reason":{"type":"string","nullable":true,"enum":["business_conflict","personal_conflict","no_notice"]},"coach_notes":{"type":"string","nullable":true}}}},"send_surveys":{"type":"boolean"},"engagement_ratings":{"type":"array","items":{"type":"object","required":["participant_id","rating"],"properties":{"participant_id":{"type":"string","format":"uuid"},"rating":{"type":"integer","minimum":1,"maximum":5}}}}}}}}},"responses":{"200":{"description":"Attendance + engagement persisted"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Caller is not the peer leader for this session"},"404":{"description":"Session not found"},"500":{"description":"Server error"}}}},"/api/participant/sessions/{sessionId}/post-session/commitments":{"put":{"tags":["Peer Leader Post-Session"],"summary":"Upsert the peer leader's own commitment (AC3, IV3)","description":"MED-1133 (E4.5a) — PRIVACY: a peer leader may only record their OWN\ncommitment. The card now sends just the leader's row; any row whose\nparticipant_user_id is not the leader is dropped at the data layer (AC2)\nand logged for audit. The leader's row is stored as a self-authored\ncommitment (coach_user_id NULL) — the same single\n`(session_id, participant_user_id)` row E3.4 uses, so it cannot duplicate\nE3.4 data (IV3). MED-1216 / AC1: `commitment_text` must be non-empty\n(whitespace-only rejected, 400) — no blank saves. The response contains\nonly the leader's own commitment (0 or 1 row).\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["commitments"],"properties":{"commitments":{"type":"array","items":{"type":"object","required":["participant_user_id","commitment_text"],"properties":{"participant_user_id":{"type":"string","format":"uuid"},"commitment_text":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Commitments persisted (returns the active list)"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Caller is not the peer leader for this session"},"500":{"description":"Server error"}}}},"/api/participant/sessions/{sessionId}/post-session/recap-email/send":{"post":{"tags":["Peer Leader Post-Session"],"summary":"Send the peer-leader-authored recap email (AC14)","description":"Recipients are intersected with the session group's current members\nserver-side; non-members are dropped (`skipped_not_in_session`).\nSubject and body_html are templates — merge tags resolve per recipient\ninside `EmailService.sendCoachRecapEmail` which accepts a dynamic\nleader name (so \"coach\" semantics are abstracted).\n","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","body_html","recipient_user_ids"],"properties":{"subject":{"type":"string"},"body_html":{"type":"string"},"recipient_user_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"200":{"description":"Send complete","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PeerLeaderRecapEmailSendResult"}}}}}},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"},"403":{"description":"Caller is not the peer leader or session not eligible"},"404":{"description":"Session not found"},"500":{"description":"Server error"}}}},"/api/sessions/{sessionId}/peer-leader-rsvp":{"post":{"summary":"Record the assigned leader's RSVP for a peer-led session","description":"Updates `sessions.session_leader_rsvp_status` to `confirmed` or\n`declined` and writes a `session_audit_log` row with the corresponding\nenum value (`rsvp_confirmed` / `rsvp_declined`). The caller must be\nthe current `sessions.session_leader_id` and the session must not have\nstarted yet.\n","tags":["Peer Leader RSVP"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PeerLeaderRsvpRequest"}}}},"responses":{"200":{"description":"RSVP recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PeerLeaderRsvpResponse"}}}},"400":{"description":"Validation error (invalid sessionId or status)"},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader"},"404":{"description":"Session not found or soft-deleted"},"422":{"description":"Session has already started — RSVP locked"}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep":{"get":{"summary":"Get the peer leader prep composite payload (AC1/3/4/6/9/11/12)","description":"Returns the full payload backing the prep page: session header info,\nprep gating (unlocked/locked/live/declined), RSVP status, authored\nagenda content, persisted step markers, first-time flag, and CM name.\n\nAuthorization: the authenticated user MUST be `sessions.session_leader_id`\nand the session's modality must be peer_led. Otherwise 403.\n\nAC1 — when the leader's RSVP status is `declined`, the response still\nreturns 200 but with `prep_status='declined'` so the FE can redirect\nthe user back to the standard participant portal. The page short-circuits\non this signal rather than rendering the prep UI.\n\nAC4 — when the session is `in_progress` or `completed`, returns\n`prep_status='live'` so the FE shows the closed-out alert.\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Composite payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PeerLeaderPrepPayload"}}}}}},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader OR cohort is not peer-led"},"404":{"description":"Session not found","soft-deleted":null,"or live_session_delivery flag off":null}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep/heartbeat":{"post":{"summary":"Record the peer leader's waiting-room presence heartbeat (MED-1126)","description":"Stamps the peer leader's `session_attendance` presence heartbeat\n(`last_seen_at`, and `joined_at` on first contact) so the leader shows\nas present in the participant waiting room while prepping. Peer leaders\nnever enter the waiting room (prep → Start → active), so the prep page\npings this on the same ~4s cadence the waiting room polls. Peer-led\nsessions only; caller must be the designated session leader.\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Presence heartbeat recorded"},"400":{"description":"Invalid sessionId format"},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader OR cohort is not peer-led"},"404":{"description":"Session not found"}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep/mark-completed":{"post":{"summary":"Mark the prep sequence as completed (AC13)","description":"Persists `marked_completed_at = NOW()` on the user's\n`peer_leader_session_prep` row. On the user's first ever Mark\nas Completed, also sets `first_prep_completed_at = NOW()` so the\nAC12 first-time strong-prompt banner is suppressed on future loads.\n\nThe AC13 RSVP-intercept modal is enforced client-side (the FE\nroutes through `/peer-leader-rsvp` BEFORE calling this endpoint when\nthe rsvp_status is unconfirmed) — this endpoint does NOT inspect\nrsvp state.\n\nForbidden once the session is live (AC4).\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Marked complete; returns refreshed prep payload.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PeerLeaderPrepPayload"}}}}}},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader / session already live"},"404":{"description":"Session not found"}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep/step/2/reviewed":{"post":{"summary":"Toggle the Step 2 (Session Guide) reviewed marker (AC11)","description":"Sets or clears `peer_leader_session_prep.step2_reviewed_at` based on\nthe `reviewed` boolean. Drives the AC11 step-completion indicator\nfor Step 2 in the prep UI.\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PeerLeaderStepReviewedRequest"}}}},"responses":{"200":{"description":"Step 2 marker updated; returns refreshed prep payload.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PeerLeaderPrepPayload"}}}}}},"400":{"description":"Validation error (missing or non-boolean `reviewed`)"},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader"},"404":{"description":"Session not found"}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep/step/3/visited":{"post":{"summary":"Mark the Step 3 (Preview / Sandbox) visited (AC11)","description":"Sets `peer_leader_session_prep.step3_visited_at = NOW()` the first\ntime the peer leader opens the preview. Idempotent: subsequent\nvisits leave the original timestamp in place. Drives the AC11\nstep-completion indicator for Step 3.\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Step 3 marker set; returns refreshed prep payload.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PeerLeaderPrepPayload"}}}}}},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader"},"404":{"description":"Session not found"}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep/leader-notes":{"put":{"summary":"Upsert one per-agenda-section Leader Note for the peer leader (MED-1046 AC1-AC4)","description":"MED-1046 (E4.2a) — peer leaders inherit the coach per-section Leader\nNotes ability (a non-AI self-notes field). Upserts the\n(peer_leader_user_id, session, block_key) row in the shared\n`coach_session_leader_notes` store. Because a peer leader facilitates\nexactly one group (AC4), the note is always written with\nscope='all_groups' / group_id=null — the request carries no scope or\ngroup_id. An empty `content_html` soft-deletes the note.\n\nThe note is owned by the peer leader's own user id, which is what lets\nit surface unchanged in the active-session guide panel (AC6) — that\nread filters by the authenticated user id, and on `/participant/.../active`\nthe authenticated user IS the peer leader. No separate display path is\nadded (AC7).\n\nAuthorization: the caller must be the assigned `session_leader_id` for a\npeer-led session. Returns 410 once the session is live (notes are a prep\nsurface). Independent of AI being on or off (AC5).\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PeerLeaderSaveLeaderNoteRequest"}}}},"responses":{"200":{"description":"The saved note row, or `null` when an empty body soft-deleted it.\n","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","nullable":true,"description":"The upserted coach_session_leader_notes row (null on soft-delete)."}}}}}},"400":{"description":"Validation error (missing block_key or content_html)"},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader / not peer-led"},"404":{"description":"Session not found","or live_session_delivery flag off":null},"410":{"description":"Session is live — leader notes can no longer be edited"}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep/ai-selections":{"patch":{"summary":"Replace the peer leader's AI prompt In Use selections (AC1-AC2)","description":"MED-909 / E4.6 — replaces `peer_leader_session_prep.ai_prompt_selections`\natomically with the supplied document. The AC1 suggestions banner count\nand AC2 per-step In Use badges both derive from this document. The row\nis created lazily on the first selection.\n\nAuthorization: the caller must be the assigned `session_leader_id` for a\npeer-led session. Rejected with 409 `AI_FEATURES_DISABLED` when the\ncohort's modality has AI features off (the FE surface that writes here is\nalso absent in that case), and 410 once the session is live.\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PeerLeaderUpdateAiSelectionsRequest"}}}},"responses":{"200":{"description":"Updated selections document.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"ai_prompt_selections":{"type":"object","additionalProperties":true}}}}}}}},"400":{"description":"Validation error (selections must be an object)"},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader / not peer-led"},"409":{"description":"AI features disabled for this cohort's modality"},"410":{"description":"Session is live — selections can no longer be edited"}}}},"/api/participant/sessions/{sessionId}/peer-leader-prep/ai-listening":{"patch":{"summary":"Toggle AI Listening for the peer leader's session (MED-909 QA)","description":"Sets `sessions.ai_listening_enabled` for this peer-led session.\n\n**Deliberate divergence from MED-909 AC4** (which scoped AI Listening to\nCM control): per QA, the designated peer session leader must be able to\nenable/disable AI Listening the same way a coach can on coach prep —\notherwise nothing in the system can turn it on for a peer-led session and\nthe entire peer live-session AI layer is unreachable.\n\nAuthorization: the caller must be the assigned `session_leader_id` for a\npeer-led session. Rejected with 409 `AI_FEATURES_DISABLED` when the\ncohort's modality has AI features off, 409 `AI_LISTENING_LOCKED` once\nwithin 15 min of scheduled start, and 410 once the session is live.\n","tags":["Peer Leader Session Prep"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"True turns AI Listening on for this group's session; false turns it off."}}}}}},"responses":{"200":{"description":"Updated AI Listening state.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"ai_listening_enabled":{"type":"boolean"},"ai_listening_locked":{"type":"boolean"}}}}}}}},"400":{"description":"Validation error (enabled must be boolean)"},"401":{"description":"Unauthenticated"},"403":{"description":"Caller is not the assigned session leader / not peer-led"},"409":{"description":"AI features disabled for this cohort's modality","or AI Listening is locked":null},"410":{"description":"Session is live — AI Listening can no longer be changed"}}}},"/api/participant/sessions/{sessionId}/reflection":{"get":{"summary":"Get pre-session reflection page payload","description":"Returns the composite payload needed to render the participant\npre-session reflection page: session header, AI banner flag,\nreflection questions + the participant's existing responses,\ncomputed status, session details, group context, and lock flag.\n\nGated by `live_session_delivery` feature flag. Returns 403 when the\nauthenticated user is not enrolled in the session's cohort.\n","tags":["Pre-Session Reflection"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Reflection payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PreSessionReflectionPayload"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Participant not enrolled in this session's cohort"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/participant/sessions/{sessionId}/reflection/responses":{"put":{"summary":"Autosave a single reflection response","description":"Upserts a single response row for the (session, participant, question)\ntriple. Idempotent. Returns the updated row and the recomputed reflection\nstatus (NOT_STARTED / IN_PROGRESS / COMPLETED).\n\nReturns 410 Gone when the session has started — reflection is locked\nper IV5.\n","tags":["Pre-Session Reflection"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["question_id","response_text"],"properties":{"question_id":{"type":"string","format":"uuid"},"response_text":{"type":"string"}}}}}},"responses":{"200":{"description":"Response upserted; returns new computed status","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/ReflectionStatus"},"response":{"type":"object"}}}}}}}},"400":{"description":"Bad request body"},"401":{"description":"Unauthorized"},"403":{"description":"Participant not enrolled in this session's cohort"},"404":{"description":"Session not found OR live_session_delivery flag off"},"410":{"description":"Reflection is locked — session has started"}}}},"/api/participant/sessions/{sessionId}/reflection/save":{"post":{"summary":"Explicit Save — bulk upsert all reflection responses","description":"Bulk upserts an array of responses in a single transaction for the\nexplicit Save button flow. Returns the resulting response set and\nthe recomputed status.\n\nSame lock-after-start semantics as the autosave endpoint.\n","tags":["Pre-Session Reflection"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["responses"],"properties":{"responses":{"type":"array","items":{"type":"object","required":["question_id","response_text"],"properties":{"question_id":{"type":"string","format":"uuid"},"response_text":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Bulk save succeeded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/ReflectionStatus"},"responses":{"type":"array","items":{"type":"object"}}}}}}}}},"400":{"description":"Bad request body"},"401":{"description":"Unauthorized"},"403":{"description":"Participant not enrolled in this session's cohort"},"404":{"description":"Session not found OR live_session_delivery flag off"},"410":{"description":"Reflection is locked — session has started"}}}},"/api/participant/sessions/{sessionId}/reflection/prior-commitment":{"put":{"summary":"Autosave the participant's reflection on their prior commitment (MED-937 / E3.1b)","description":"Upserts the \"How do you think things went?\" reflection for the\nparticipant's prior-session commitment. The commitment_id is resolved\nserver-side from (cohort, group, session_number − 1, participant) —\nthe client never sends it. Empty text is a valid save (the AC\nexplicitly allows leaving the field blank).\n\nReturns 422 with code `prior_commitment_not_available` when the gates\nthat would have shown the section don't pass (cohort flag off, no\nprior session, or no commitment on the prior session).\n","tags":["Pre-Session Reflection"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reflection_text"],"properties":{"reflection_text":{"type":"string"}}}}}},"responses":{"200":{"description":"Reflection saved; returns the prior_commitment block","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PriorCommitmentBlock"}}}}}},"400":{"description":"Bad request body"},"401":{"description":"Unauthorized"},"403":{"description":"Participant not enrolled in this session's cohort"},"404":{"description":"Session not found OR live_session_delivery flag off"},"410":{"description":"Reflection is locked — session has started"},"422":{"description":"prior_commitment_not_available — section gates don't pass"}}}},"/api/participant/sessions/{sessionId}/volunteer":{"post":{"summary":"Record a peer-leader volunteer response (MED-813 / E4.1)","description":"Records the participant's response to a peer-leader volunteer call —\n\"Yes, I'll lead\" (`volunteered` → State 3) or \"I can't lead today\"\n(`declined` → State 6) — and returns the recomputed peer-leader block\nso the banner transitions immediately (IV5). Fires a CM-notification\nemail (fire-and-forget). Gated by `live_session_delivery`.\n","tags":["Pre-Session Reflection"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["response"],"properties":{"response":{"type":"string","enum":["volunteered","declined"]}}}}}},"responses":{"200":{"description":"Response recorded; returns the recomputed peer-leader block","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"peer_leader":{"oneOf":[{"$ref":"#/components/schemas/PeerLeaderBlock"},{"type":"null"}]},"cohortId":{"type":"string","format":"uuid"}}}}}}}},"400":{"description":"Invalid response value"},"401":{"description":"Unauthorized"},"403":{"description":"Participant not enrolled in this session's cohort"},"404":{"description":"Session not found OR live_session_delivery flag off"},"410":{"description":"Session has started — volunteering is locked"},"422":{"description":"Not a peer-led cohort (not_peer_led) or already the assigned leader (already_leader)"}}}},"/programs":{"get":{"summary":"List all programs","description":"Get a paginated list of programs with optional filtering","tags":["Programs"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search term for program name"},{"in":"query","name":"partnerId","schema":{"type":"string","format":"uuid"},"description":"Filter by partner ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Programs retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Program"}},"pagination":{"type":"object"}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new program","description":"Create a new program for a partner","tags":["Programs"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProgramRequest"}}}},"responses":{"201":{"description":"Program created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Program"}}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/programs/{id}":{"get":{"summary":"Get program by ID","description":"Retrieve a single program by its UUID","tags":["Programs"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Program retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Program"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Program not found"}}},"put":{"summary":"Update a program","description":"Update an existing program","tags":["Programs"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"is_active":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Program updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Program"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Program not found"},"422":{"description":"Validation error"}}},"delete":{"summary":"Delete a program","description":"Soft delete a program","tags":["Programs"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Program deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Program not found"}}}},"/api/public/booking/{token}":{"get":{"summary":"Describe a public booking link (MED-1319)","description":"Returns the touchpoint, the coach and whether booking is available. No login.","tags":["Public Booking"],"parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The link"},"404":{"description":"Unknown or revoked token (link_not_found)"},"429":{"description":"Too many requests (rate_limited)"}}}},"/api/public/booking/{token}/availability":{"get":{"summary":"Available times for a public booking link (MED-1319)","tags":["Public Booking"],"parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"}},{"in":"query","name":"start_time","required":true,"schema":{"type":"string","format":"date-time"}},{"in":"query","name":"end_time","required":true,"description":"At most 7 days after start_time","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"The slots"},"400":{"description":"Bad dates or a window over 7 days (validation_error)"},"404":{"description":"Unknown or revoked token (link_not_found)"},"409":{"description":"Booking is not available for this link (booking_unavailable)"}}}},"/api/public/booking/{token}/book":{"post":{"summary":"Book a session through a public booking link (MED-1319)","description":"Anybody with the link can book, with any name and email. When the email identifies an active member of the group with a free session row, the booking fills that row. Books at once through Calendly. The response never says whether the email matched.\n","tags":["Public Booking"],"parameters":[{"in":"path","name":"token","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","scheduled_start","timezone"],"properties":{"name":{"type":"string","maxLength":200},"email":{"type":"string"},"scheduled_start":{"type":"string","format":"date-time"},"timezone":{"type":"string"}}}}}},"responses":{"201":{"description":"Booked"},"400":{"description":"validation_error"},"404":{"description":"link_not_found"},"409":{"description":"booking_unavailable, slot_unavailable, or booking_blocked (the coach's Calendly refused: a free plan, the trial limit, or a required question)\n"},"429":{"description":"rate_limited (30 per hour per IP, 30 per day per link, 5 per hour per email)"},"502":{"description":"booking_failed"}}}},"/api/admin/rate-card":{"get":{"summary":"Get the current global rate card","description":"Returns the single global rate card with all compensation rates organized by category and audience type.","tags":["Rate Card"],"responses":{"200":{"description":"Rate card retrieved successfully"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - admin role required"},"404":{"description":"Rate card not found"}}},"put":{"summary":"Update rates on the global rate card","description":"Accepts a partial rates object, deep-merges into existing rates, and logs all field-level changes to the activity log.","tags":["Rate Card"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rates"],"properties":{"rates":{"type":"object","description":"Partial rate card object with only the rates to update"}}}}}},"responses":{"200":{"description":"Rate card updated successfully"},"400":{"description":"Bad request - invalid rates"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - admin role required"},"404":{"description":"Rate card not found"}}}},"/api/admin/rate-card/activity-log":{"get":{"summary":"Get rate card activity log","description":"Returns a paginated list of all rate card changes with user names and field-level diffs.","tags":["Rate Card"],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":20}}],"responses":{"200":{"description":"Activity log retrieved successfully"},"401":{"description":"Unauthorized"},"403":{"description":"Forbidden - admin role required"}}}},"/api/resources/categories":{"get":{"summary":"List all active resource categories","tags":["Resource Categories"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of resource categories","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ResourceCategory"}}}}}}}}}},"/api/resources/evergreen":{"get":{"summary":"List all evergreen resources (admin only)","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}},{"in":"query","name":"search","schema":{"type":"string"}},{"in":"query","name":"category_id","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Paginated list of evergreen resources"}}}},"/api/resources/upload-url":{"post":{"summary":"Get presigned URL for file upload","tags":["Resources"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["filename","content_type"],"properties":{"filename":{"type":"string"},"content_type":{"type":"string"},"cohort_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Presigned upload URL","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/PresignedUploadResponse"}}}}}}}}},"/api/resources/file":{"post":{"summary":"Create a file resource (after uploading to S3)","tags":["Resources"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFileResourceRequest"}}}},"responses":{"201":{"description":"Resource created"},"400":{"description":"Validation error"}}}},"/api/resources/link":{"post":{"summary":"Create a link resource","tags":["Resources"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkResourceRequest"}}}},"responses":{"201":{"description":"Resource created"},"400":{"description":"Validation error"}}}},"/api/resources/{id}":{"get":{"summary":"Get a resource by ID","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Resource details"},"404":{"description":"Resource not found"}}},"put":{"summary":"Update a resource","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"category_id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid","nullable":true},"access_coaches":{"type":"boolean"},"access_participants":{"type":"boolean"},"access_partners":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Resource updated"},"404":{"description":"Resource not found"}}},"delete":{"summary":"Delete a resource","tags":["Resources"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Resource deleted"},"404":{"description":"Resource not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/agenda":{"get":{"summary":"Get the structured agenda for a session (MED-975 / E1.3d)","description":"Returns the description + ordered sections + each section's visuals\nand leader prompts for the session's (cohort, modality, session_number)\nscope. Empty arrays when the session has no authored content yet\n(AC-AS1 empty state is rendered client-side from this shape).\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Agenda bundle (may be empty).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SessionAgendaResponse"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found or not eligible for session content authoring (non-group-coaching, no session_number, etc.)."}}},"post":{"summary":"Save the structured agenda for a session (MED-975 / E1.3d)","description":"Full-replace save: the payload is the authoritative state for the\nsession's (cohort, modality, session_number) scope. The service\nruns a transaction that soft-deletes existing sections (and\ncascade-soft-deletes their visuals + prompts) and inserts the\npayload's sections fresh. Description is upserted on\nsession_number_config.\n\nPer AC-MC3, Save is always allowed for in-progress work — the\nendpoint does NOT enforce minimum-complete (section_name and\nduration_minutes are nullable). The UI computes and visualizes\nthe incomplete state.\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionAgendaUpsertRequest"}}}},"responses":{"200":{"description":"Saved; returns the canonical post-save bundle.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SessionAgendaResponse"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found or not eligible for session content authoring."},"422":{"description":"Validation failed (malformed payload)."}}}},"/cohorts/{cohortId}/sessions/{sessionId}/agenda/duplicate-from":{"post":{"summary":"Pre-populate the target editor with another session's authored content (MED-982 / E1.3g)","description":"Copies the source session's authored content (description, agenda\nsections + leader prompts + visuals, participant pre-session work\n+ reflection prompts) into the response for the target editor's\ndraft state. The agenda fields are NOT persisted to the target\nuntil the editor's normal Save (AC-S1); only the slide deck is\npersisted server-side at this point (AC-CB2 — S3 copy → new\nsession_content_files row for the target, replacing any existing\ntarget slide_deck row atomically).\n\nThe response applies the TARGET cohort's commitment-tracking gate\n(AC-CB6): per-section commitment toggles and the pre-session work\ncommitment_review_enabled flag are forced to false when the target\ncohort has tracking off, so the editor never inherits a stranded\nvalue it cannot surface.\n\nThe source can live in a different cohort. Both source and target\nmust resolve to a (cohort, modality='group_coaching', session_number)\ntriple — sessions without a session_number or whose cohort lacks an\nactive group_coaching modality return 404.\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source_session_id"],"properties":{"source_session_id":{"type":"string","format":"uuid","description":"A session id from the source cohort (any group's row at the source session_number — the service resolves to the canonical scope)."}}}}}},"responses":{"200":{"description":"Reconciled content payload + (if copied) new slide_deck_file metadata. The editor loads `sections` / `description` / `participant_pre_session_work` into draft state and refreshes its content-files list to pick up the new deck.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"description":{"type":"string","nullable":true},"sections":{"type":"array","items":{"$ref":"#/components/schemas/SessionAgendaSection"}},"participant_pre_session_work":{"$ref":"#/components/schemas/SessionAgendaPreSessionWork"},"cohort_commitment_tracking_enabled":{"type":"boolean"},"slide_deck_file":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"slot":{"type":"string","enum":["slide_deck"]},"original_filename":{"type":"string"},"mime_type":{"type":"string"},"source_s3_key":{"type":"string"},"converted_pdf_s3_key":{"type":"string","nullable":true},"processing_state":{"type":"string","enum":["pending","processing","ready","failed"]}}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Target or source session not found / not eligible."},"422":{"description":"Source has no authored content to copy."}}}},"/cohorts/{cohortId}/sessions/{sessionId}/agenda/complete":{"post":{"summary":"Mark session content complete — release to the cohort (MED-1232 / E1.3)","description":"The explicit, ONE-WAY \"Mark Complete\" action, distinct from Save. Save\npersists an in-progress draft privately; Mark Complete releases the\nauthored content so it becomes visible to participants, coaches, and\npeer leaders (AC5). Readiness is now manual — content that merely meets\nthe agenda field rule is NOT visible until this action runs (AC1, AC5).\n\nPrecondition (AC3): the PERSISTED agenda must meet the required-fields\nrule — at least one section, every section with a non-blank name and a\nduration. When it doesn't, the request is rejected 422 with a message\ndescribing what is missing; nothing is released. (The editor also\ndisables the action client-side, but this is the authoritative guard.)\n\nOne-way: there is no un-mark. After release the CM can keep editing and\nSave; content stays complete and edits go live (AC8). Re-calling this\nendpoint is idempotent and preserves the original completed_at. Only the\nsession_number_config release flag is touched — agenda section rows and\ntheir ids are untouched, so coach/peer Leader Notes and participant\nreflection answers keyed off those ids survive.\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Released; returns the post-mark agenda bundle (marked_complete=true).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SessionAgendaResponse"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found or not eligible for session content authoring."},"422":{"description":"Precondition not met — the agenda is missing required fields (message describes what)."}}}},"/cohorts/{cohortId}/sessions/by-number/{sessionNumber}/agenda":{"get":{"summary":"Get the structured agenda by session number (MED-1086 / E1.3m)","description":"Session-number-addressed sibling of\n`/cohorts/{cohortId}/sessions/{sessionId}/agenda`. Returns the same\nagenda bundle for the (cohort, group_coaching, session_number) scope,\nbut reachable before any groups exist. Returns an empty bundle for an\nun-authored session number (no 404).\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionNumber","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Agenda bundle (may be empty).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SessionAgendaResponse"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort has no active group_coaching modality, or sessionNumber is out of range."}}},"post":{"summary":"Save the structured agenda by session number (MED-1086 / E1.3m)","description":"Session-number-addressed full-replace save. A true upsert — a CM can\nauthor and save a brand-new session number before groups exist. Same\npayload and persistence as the sessionId POST.\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionNumber","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionAgendaUpsertRequest"}}}},"responses":{"200":{"description":"Saved; returns the canonical post-save bundle.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SessionAgendaResponse"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort has no active group_coaching modality, or sessionNumber is out of range."},"422":{"description":"Validation failed (malformed payload)."}}}},"/cohorts/{cohortId}/sessions/by-number/{sessionNumber}/agenda/complete":{"post":{"summary":"Mark session content complete by session number (MED-1232 / E1.3)","description":"Session-number-addressed sibling of\n`/cohorts/{cohortId}/sessions/{sessionId}/agenda/complete`, for the\npre-groups Build Content entry point (MED-1086 / E1.3m). A CM can\nrelease a session number's content before any groups (session rows)\nexist. Same one-way semantics and AC3 precondition (422 when the\npersisted agenda is missing required fields).\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionNumber","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Released; returns the post-mark agenda bundle (marked_complete=true).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/SessionAgendaResponse"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort has no active group_coaching modality, or sessionNumber is out of range."},"422":{"description":"Precondition not met — the agenda is missing required fields (message describes what)."}}}},"/cohorts/{cohortId}/sessions/by-number/{sessionNumber}/agenda/duplicate-from":{"post":{"summary":"Start a by-number session's content from an existing session (MED-1110)","description":"Session-number-addressed sibling of\n`/cohorts/{cohortId}/sessions/{sessionId}/agenda/duplicate-from`, for the\npre-groups Build Content entry point (MED-1086 / E1.3m). Copies the\nsource session's authored content — description, agenda sections\n(including leader prompts, with commitment toggles reconciled against the\ntarget cohort's `commitment_tracking_enabled`), and participant\npre-session work — and returns it for the editor's draft state.\n\nUnlike the sessionId variant, NO slide deck is copied: the deck copy is\nsessionId-bound (its S3 key path embeds a concrete session id) and the\ndeck upload slot is hidden pre-groups, so `slide_deck_file` is always\nnull. The agenda content is NOT persisted — the by-number Save endpoint\n(`POST .../by-number/{sessionNumber}/agenda`) is the only write path.\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionNumber","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["source_session_id"],"properties":{"source_session_id":{"type":"string","format":"uuid","description":"A session id from the source cohort (any group's row at the source session_number — the service resolves to the canonical scope)."}}}}}},"responses":{"200":{"description":"Reconciled content payload. The editor loads `sections` / `description` / `participant_pre_session_work` into draft state. `slide_deck_file` is always null in by-number mode.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"description":{"type":"string","nullable":true},"sections":{"type":"array","items":{"$ref":"#/components/schemas/SessionAgendaSection"}},"participant_pre_session_work":{"$ref":"#/components/schemas/SessionAgendaPreSessionWork"},"cohort_commitment_tracking_enabled":{"type":"boolean"},"slide_deck_file":{"type":"object","nullable":true,"description":"Always null in by-number mode (no session instance to attach a deck to)."}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Target (cohort/sessionNumber) not eligible, or source session not found / not eligible."},"422":{"description":"Source has no authored content to copy."}}}},"/cohorts/{cohortId}/sessions/by-number/{sessionNumber}/content":{"get":{"summary":"List session content files by session number (MED-1086 / E1.3m)","description":"Session-number-addressed sibling of\n`/cohorts/{cohortId}/sessions/{sessionId}/content`. Returns the shared\n(cohort, group_coaching, session_number) content files, the modality AI\nflag, and a header built from the cohort + session-number topic. `files`\nis empty until a deck is uploaded (deck upload stays on the sessionId\npath). Lets the by-number editor render before groups exist.\n","tags":["Session Content"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionNumber","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Content list + AI flag + header context."},"401":{"description":"Unauthorized"},"404":{"description":"Cohort has no active group_coaching modality, or sessionNumber is out of range."}}}},"/cohorts/{cohortId}/sessions/{sessionId}/content":{"get":{"summary":"List active session content files (MED-784 AC1, AC12, IV2)","description":"Returns the active (non-deleted) Session Guide and Slide Deck rows for\na session. Returns zero, one, or two rows depending on which slots the\nCM has populated. Empty array indicates the AC12 empty state.\n","tags":["Session Content"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Active content files for the session, plus the AC8 ai_features_enabled flag.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/SessionContentFile"}},"ai_features_enabled":{"type":"boolean","description":"AC8 — true when the session's modality has AI features enabled. Drives the informational banner on the editor page."},"session":{"type":"object","description":"MED-964 / E1.3a — header context (session title, cohort name, org name) rendered by the CM Session Content editor.","properties":{"title":{"type":"string","description":"The session title (e.g., \"Session 1\")."},"session_number":{"type":"integer","nullable":true},"cohort_name":{"type":"string","nullable":true},"organization_name":{"type":"string","nullable":true,"description":"Resolved via cohorts.program_id → programs.partner_id → organizations.name."}}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/content/{slot}":{"post":{"summary":"Upload (or replace) a session content file (MED-784 AC2, AC6, AC7, AC11)","description":"Multipart upload (field name `file`) for one of the two slots. If an\nactive row exists for the same (session, slot) pair, it is soft-deleted\natomically with the new row's insert (AC6 replace flow), preserving\nAC7's one-file-per-slot invariant. The conversion job is queued\nasynchronously; clients should poll the GET endpoint to observe\nprocessing_state transitioning pending → processing → ready/failed (AC11).\n","tags":["Session Content"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"slot","required":true,"schema":{"$ref":"#/components/schemas/SessionContentSlot"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Source file (PDF, DOCX, PPTX, etc. — see resourceUpload allowlist). Max 50 MB."}}}}}},"responses":{"201":{"description":"File accepted; conversion queued.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"file":{"$ref":"#/components/schemas/SessionContentFile"}}}}}}}},"400":{"description":"Missing file or invalid slot value."},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"},"500":{"description":"Storage or queue failure (CM should retry)."}}},"delete":{"summary":"Soft-delete the active file for a slot (MED-784)","description":"Clears a slot by soft-deleting the currently active row. Distinct from\nthe AC6 replace flow on POST: this leaves the slot empty rather than\nswapping it for a new file. The CM may then re-upload to the same slot\ncleanly. Returns 204 on success, 404 if the slot was already empty.\n","tags":["Session Content"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"slot","required":true,"schema":{"$ref":"#/components/schemas/SessionContentSlot"}}],"responses":{"204":{"description":"Slot cleared."},"401":{"description":"Unauthorized"},"404":{"description":"Session not found, or slot was already empty."}}}},"/cohorts/{cohortId}/sessions/{sessionId}/content/{slot}/retry":{"post":{"summary":"Retry conversion for a failed slot (MED-784 AC11)","description":"Re-enqueues the conversion job for the active row whose previous\nconversion failed. Resets processing_state from `failed` → `pending`\nand clears error_message. Only valid when the active row is in the\n`failed` state.\n","tags":["Session Content"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"slot","required":true,"schema":{"$ref":"#/components/schemas/SessionContentSlot"}}],"responses":{"200":{"description":"Retry accepted; conversion re-queued.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"file":{"$ref":"#/components/schemas/SessionContentFile"}}}}}}}},"400":{"description":"Active row is not in `failed` state, or queue publish failed."},"401":{"description":"Unauthorized"},"404":{"description":"Session not found, or no active file in this slot."}}}},"/cohorts/{cohortId}/sessions/{sessionId}/content/{slot}/pdf":{"get":{"summary":"Stream the converted PDF for a slot (MED-784 AC1, AC4, AC10)","description":"Returns the converted PDF binary for an active, ready slot. Used by the\nfrontend's PDF.js renderer to produce slide thumbnails (AC4) and the\nfull-size preview modal. Always served with `Content-Disposition: inline`\nper AC10 — no download affordance is offered to the client. Returns\n409 if the slot's processing_state is not 'ready'.\n","tags":["Session Content"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"slot","required":true,"schema":{"$ref":"#/components/schemas/SessionContentSlot"}}],"responses":{"200":{"description":"PDF binary","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session or slot not found"},"409":{"description":"Slot exists but not yet ready (still pending/processing/failed)"}}}},"/cohorts/{cohortId}/session-number-configs":{"get":{"summary":"List all session-number configs for a cohort","description":"Returns existing configs plus the distinct session numbers currently used by group_session and one_on_one_session records. Feeds the \"View by Session\" accordion structure.","tags":["SessionNumberConfig"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Configs and session-number universes","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"configs":{"type":"array","items":{"$ref":"#/components/schemas/SessionNumberConfig"}},"group_coaching_session_numbers":{"type":"array","items":{"type":"integer"}},"one_on_one_session_numbers":{"type":"array","items":{"type":"integer"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/session-number-configs/group-coaching/topic":{"put":{"summary":"Set or clear a Group Coaching topic for a session number","description":"Upserts the topic at the session-number level for group_coaching modality. The computed topic on each session is `session.topic_override ?? session_number_config.topic ?? topics.name(via topic_id) ?? null`, so propagation happens at read time — sessions with an explicit topic_override are untouched.","tags":["SessionNumberConfig"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["session_number"],"properties":{"session_number":{"type":"integer","minimum":1},"topic":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Topic saved; returns PropagationResult","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropagationResult"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/session-number-configs/group-coaching/bulk-topics":{"put":{"summary":"Bulk-set Group Coaching topics across multiple session numbers","description":"Atomically upserts topic for each (session_number, topic) pair. Used by the \"Set All Topics\" modal (MED-702 AC37-41). Individual sessions with topic_override set remain untouched because precedence is computed at read time.","tags":["SessionNumberConfig"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["topics"],"properties":{"topics":{"type":"array","minItems":1,"items":{"type":"object","required":["session_number"],"properties":{"session_number":{"type":"integer","minimum":1},"topic":{"type":"string","nullable":true}}}}}}}}},"responses":{"200":{"description":"Topics saved"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/session-number-configs/one-on-one/type":{"put":{"summary":"Propagate a 1:1 session type to all unscheduled sessions for a session number","description":"Writes sessions.one_on_one_type for every 1:1 session with the given session_number that is currently unscheduled. Already-scheduled sessions are intentionally NOT updated (MED-702 AC29). A SessionNumberConfig row is upserted for the 1:1 modality to record the intent; its topic column stays NULL (type is stored on sessions.one_on_one_type per E22-5b).","tags":["SessionNumberConfig"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["session_number","one_on_one_type"],"properties":{"session_number":{"type":"integer","minimum":1},"one_on_one_type":{"type":"string","enum":["standard_1_1","two_on_one","360_debrief","hogan_debrief","other_assessment_debrief"]}}}}}},"responses":{"200":{"description":"Type propagated","returns PropagationResult":null},"400":{"description":"Invalid session type"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/sessions":{"get":{"summary":"List all sessions for a cohort","description":"Returns all sessions for a cohort, optionally filtered by group or status","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"groupId","schema":{"type":"string","format":"uuid"},"description":"Filter by group ID"},{"in":"query","name":"status","schema":{"type":"string","enum":["scheduled","in_progress","completed","cancelled","rescheduled"]},"description":"Filter by status"}],"responses":{"200":{"description":"List of sessions"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/stats":{"get":{"summary":"Get session counts by status","description":"Returns counts of sessions grouped by status","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Session counts by status"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/with-rsvp":{"get":{"summary":"List sessions with RSVP summaries","description":"Returns all sessions for a cohort with their RSVP tracking summaries","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of sessions with RSVP summaries"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/by-session-number":{"get":{"summary":"Aggregate sessions grouped by session number (MED-702)","description":"Returns the cohort's sessions re-shaped for the \"View by Session\" toggle.\nFor `modality=group_coaching`, yields one entry per session number with the\nsession-number-level topic, aggregated attendance/engagement, and the list\nof groups whose session with that number feeds into the roll-up. For\n`modality=one_on_one`, yields one entry per session number with the\npropagated session type, scheduled/completed counts, and the participant\nrow list. When `coachUserId` is supplied, the result is scoped to that\ncoach's assigned groups (group coaching) or assigned participants (1:1).\n","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"modality","required":true,"schema":{"type":"string","enum":["group_coaching","one_on_one"]}},{"in":"query","name":"coachUserId","required":false,"schema":{"type":"string","format":"uuid"},"description":"If supplied, scopes results to this coach's assignments (Coach View)."}],"responses":{"200":{"description":"Sessions aggregated by session_number for the requested modality"},"400":{"description":"Missing or invalid modality"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/content-status":{"get":{"summary":"Per-session-number content-authoring status (MED-1086 / E1.3m / AC11)","description":"Returns the e1.3k content-status (`not_started` / `in_progress` /\n`complete` + over-length flag) for each session number of the cohort's\ngroup_coaching modality, derived independently of any group sessions.\nPowers the content-status badge on the no-groups zero state, where\n/by-session-number returns nothing (no group sessions to roll up). The\nBy-Session view gets the same data folded into /by-session-number.\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Array of { session_number, content_status } (content_status null for not-started numbers)."},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/with-display-status":{"get":{"summary":"List sessions with computed display status (E8-13)","description":"Returns all sessions with SCHEDULED/AWAITING_ATTENDANCE/COMPLETED status","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of sessions with display status"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/needing-attendance":{"get":{"summary":"Get sessions needing attendance (E8-11)","description":"Returns sessions where scheduled_end has passed but attendance is not marked","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of sessions needing attendance","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"sessions":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/finalization-status":{"get":{"summary":"Get finalization status for a cohort","description":"Returns readiness info and lists of finalized/unfinalized groups","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Finalization status","content":{"application/json":{"schema":{"type":"object","properties":{"ready":{"type":"boolean"},"total_sessions":{"type":"integer"},"group_count":{"type":"integer"},"date_range":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"end":{"type":"string","format":"date-time"}}},"unfinalized_groups":{"type":"array","items":{"$ref":"#/components/schemas/GroupFinalizationInfo"}},"finalized_groups":{"type":"array","items":{"$ref":"#/components/schemas/GroupFinalizationInfo"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/duplicate-candidates":{"get":{"summary":"List source-session candidates for the duplicate-from picker (MED-982 / E1.3g)","description":"Returns one row per unique session_number in the cohort's\ngroup_coaching modality, with a `has_authored_content` flag so the\npicker can grey out empty candidates (AC-EP3). The\n`session_id` is a representative row at that session_number — pass it\nverbatim back to POST `.../agenda/duplicate-from` as\n`source_session_id`; the service resolves to the canonical\n(cohort, modality, session_number) scope, identical for every\nper-group row at the same session_number.\n\nEmpty array when the cohort has no group_coaching modality or no\nsessions with a session_number — the picker handles \"nothing to\nchoose\" UI-side.\n","tags":["Session Agenda Authoring"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Candidates ordered by session_number ascending.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_number":{"type":"integer"},"title":{"type":"string"},"has_authored_content":{"type":"boolean"}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/cohorts/{cohortId}/sessions/finalize":{"post":{"summary":"Finalize sessions and prepare invites","description":"Marks sessions as finalized, persists the optional video link and\nphysical location, and creates attendance records for RSVP tracking.\n\nMED-780: `video_link` is now optional and may be empty (e.g., for\npartner-room sessions where there's no Zoom link). The optional\n`location` field is free-text physical address shown on the\ncalendar invite. When both are present, `location` wins as the\ncalendar event's location and the video link goes in the description.\n","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"group_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Optional - specific groups to finalize. If empty, finalizes all unfinalized groups."},"video_link":{"type":"string","description":"Optional video conferencing link for the sessions.\nEmpty/whitespace is treated as \"no link\".\n","nullable":true},"location":{"type":"string","description":"Optional free-text physical location (e.g., \"Room 204,\n123 Main St\"). When set, becomes the calendar event's\n`location` field; the video link, if present, appears\nin the event description.\n","nullable":true,"maxLength":1000}}}}}},"responses":{"200":{"description":"Sessions finalized","content":{"application/json":{"schema":{"type":"object","properties":{"finalized_count":{"type":"integer"},"attendance_records_created":{"type":"integer"},"groups_finalized":{"type":"array","items":{"type":"string"}}}}}}},"400":{"description":"Bad request - no sessions to finalize"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/resend-invites":{"post":{"summary":"Resend invites for finalized sessions","description":"Resends calendar invites for already finalized sessions","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["group_ids"],"properties":{"group_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Groups to resend invites for"}}}}}},"responses":{"200":{"description":"Invites resent"},"400":{"description":"Bad request - missing group_ids"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/generate":{"post":{"summary":"Generate sessions for all groups in a cohort","description":"Auto-generates sessions for groups with time slots assigned","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Sessions generated"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/one-on-one":{"get":{"summary":"List 1:1 coaching participants with their session slots","description":"Returns all active participants in the cohort with their one_on_one_session records. E18-1.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of participants with their 1:1 session slots"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/one-on-one/generate":{"post":{"summary":"Generate empty 1:1 session slots for all participants","description":"Creates N empty one_on_one_session records per participant who don't have slots yet. N = modality session_count. E18-1 AC7.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Number of session slots created"},"400":{"description":"Cohort does not have 1:1 modality configured"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/sessions/leaders-grid":{"get":{"summary":"Get the Session Leaders grid for a peer_led cohort (MED-906)","description":"Returns the rows-and-columns data backing the Session Leaders tab on\nthe cohort page. Available only for cohorts whose group_coaching\nmodality has coaching_sub_type='peer_led'. Gated by the\n`live_session_delivery` feature flag.\n\nCells reflect the current `sessions.session_leader_id` value — manual\nassignments made via `PUT /api/cohorts/:cohortId/sessions/:sessionId/leader`\n(MED-812) appear here. The \"Generate Rotation Order\" algorithm is\ndeferred to a follow-up ticket.\n","tags":["Sessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Grid response with groups (rows) and sessions (columns).","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"group_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"member_count":{"type":"integer"},"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"}}}}}}},"sessions":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_number":{"type":"integer","nullable":true},"topic_name":{"type":"string","nullable":true},"scheduled_start":{"type":"string","format":"date-time","nullable":true},"scheduled_end":{"type":"string","format":"date-time","nullable":true},"group_id":{"type":"string","format":"uuid"},"leader":{"type":"object","nullable":true,"properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"avatar_url":{"type":"string","nullable":true}}}}}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Feature flag is off OR cohort has no group_coaching modality."},"422":{"description":"Validation error. Possible `code` values:\n- `not_peer_led` — cohort's group_coaching modality is not peer_led\n"}}}},"/api/cohorts/{cohortId}/sessions/generate-leader-rotation":{"post":{"summary":"Generate a suggested session leader rotation (MED-906 / E4.0c)","description":"Generates a session-leader rotation order for every group in a\npeer_led cohort using IvsE and prep-preference scores extracted on\ndemand from each participant's intake `form_submissions.raw_payload`.\n\nThe proposal is NOT persisted — the FE displays it and lets the CM\nreview + edit per cell before committing the batch via\n`POST /finalize-leader-rotation`. Participants without a completed\nintake submission are surfaced under `skipped_participants` and\nexcluded from the rotation rather than being assigned a neutral\ndefault that would bias the result.\n\nGated by the `live_session_delivery` feature flag.\n","tags":["Sessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Rotation proposal generated successfully.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"assignments":{"type":"array","items":{"type":"object","properties":{"session_id":{"type":"string","format":"uuid"},"session_number":{"type":"integer","nullable":true},"topic_name":{"type":"string","nullable":true},"scheduled_start":{"type":"string","nullable":true},"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"proposed_leader":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"ivse_average":{"type":"number"},"prep_preference_average":{"type":"number"}}}}}},"skipped_participants":{"type":"array","items":{"type":"object"}},"warnings":{"type":"array","items":{"type":"object"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Feature flag is off OR cohort has no group_coaching modality."},"422":{"description":"Validation error. Possible `code` values:\n- `not_peer_led` — cohort's group_coaching modality is not peer_led\n"}}}},"/api/cohorts/{cohortId}/sessions/finalize-leader-rotation":{"post":{"summary":"Commit a rotation proposal to session records (MED-906 / E4.0c)","description":"Bulk-writes session leader assignments from a (possibly CM-edited)\nrotation proposal. Each assignment is validated against the MED-812\nchain (cohort/session linkage, peer_led gate, lock cutoff, group\nmembership) and written inside a single transaction so the finalize\nclick is atomic. Each per-session write also records a\nsession_audit_log row.\n\nOnce finalized, subsequent edits go through the existing per-cell\nendpoint `PUT /api/cohorts/:cohortId/sessions/:sessionId/leader`.\n\nGated by the `live_session_delivery` feature flag.\n","tags":["Sessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["assignments"],"properties":{"assignments":{"type":"array","items":{"type":"object","required":["session_id","leader_id"],"properties":{"session_id":{"type":"string","format":"uuid"},"leader_id":{"type":"string","format":"uuid"}}}}}}}}},"responses":{"200":{"description":"Rotation finalized; response carries finalized_count and the refreshed grid."},"401":{"description":"Unauthorized"},"404":{"description":"Feature flag is off OR cohort/session not found."},"422":{"description":"Validation error. Possible `code` values:\n- `not_peer_led`, `session_not_in_cohort`, `session_already_started`,\n  `participant_not_in_group`\n"}}}},"/api/cohorts/{cohortId}/sessions/leader-rotation-participants":{"get":{"summary":"Per-group participant rotation rationale (MED-1009 / E4.0e AC5)","description":"Returns, per group in a peer_led cohort, every participant's\nintake-derived rotation rationale: IvsE average, prep-preference\naverage, topic-excitement selections (mapped to the group's session\nnumbers), prior coaching/leadership experience (a boolean plus the raw\nselections), and the free-text availability note.\n\nThis is a read-only presentation layer over the SAME scores the E4.0c\nrotation algorithm consumes — values are recomputed from each\nparticipant's latest intake `form_submissions.raw_payload` on demand,\nso they match the numbers the algorithm used. Backs the Participants\nsubpage and the refined Schedule drawer.\n\n\"Sessions assigned\" is NOT returned here — the FE derives it from the\nphase-aware proposal/grid state so it tracks the Schedule grid.\n\nGated by the `live_session_delivery` feature flag.\n","tags":["Sessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Per-group participant rationale.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"groups":{"type":"array","items":{"type":"object","properties":{"group_id":{"type":"string","format":"uuid"},"group_name":{"type":"string"},"member_count":{"type":"integer"},"participants":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"first_name":{"type":"string"},"last_name":{"type":"string"},"ivse_average":{"type":"number","nullable":true},"prep_preference_average":{"type":"number","nullable":true},"topic_excitement":{"type":"array","items":{"type":"object","properties":{"session_number":{"type":"integer"},"topic_name":{"type":"string"}}}},"prior_experience":{"type":"object","properties":{"has_experience":{"type":"boolean"},"choices":{"type":"array","items":{"type":"string"}}}},"availability_notes":{"type":"string","nullable":true}}}}}}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Feature flag is off OR cohort has no group_coaching modality."},"422":{"description":"Validation error. Possible `code` values:\n- `not_peer_led` — cohort's group_coaching modality is not peer_led\n"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/detail-stats":{"get":{"summary":"Get stats for a single session (MED-89)","description":"Returns session stats for the detail modal header - total sessions in group, attendance percentage, and feedback rating","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Session detail stats","content":{"application/json":{"schema":{"type":"object","properties":{"total_sessions_in_group":{"type":"integer"},"attendance_percentage":{"type":"number","nullable":true},"feedback_rating":{"type":"number","nullable":true}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}":{"get":{"summary":"Get a session by ID","description":"Returns a single session with details","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Session details"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"put":{"summary":"Update a session","description":"Updates session details","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"scheduled_start":{"type":"string","format":"date-time"},"scheduled_end":{"type":"string","format":"date-time"},"timezone":{"type":"string"},"zoom_link":{"type":"string"},"status":{"type":"string","enum":["scheduled","in_progress","completed","cancelled","rescheduled"]}}}}}},"responses":{"200":{"description":"Session updated"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/rsvp":{"get":{"summary":"Get RSVP summary for a session","description":"Returns RSVP counts and attendee list for a session","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"RSVP summary","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string"},"is_finalized":{"type":"boolean"},"summary":{"type":"object","properties":{"total":{"type":"integer"},"accepted":{"type":"integer"},"declined":{"type":"integer"},"pending":{"type":"integer"}}},"attendees":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"put":{"summary":"Update RSVP status for an attendee","description":"Updates the RSVP status for a specific attendee (for webhook or manual update)","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["user_id","status"],"properties":{"user_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","accepted","declined","tentative"]}}}}}},"responses":{"200":{"description":"RSVP updated"},"400":{"description":"Bad request - missing fields or invalid status"},"401":{"description":"Unauthorized"},"404":{"description":"Session or attendance record not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/participants":{"put":{"summary":"Update participants for a finalized session (MED-71)","description":"Add or remove participants from a finalized session. Updates calendar event and sends notifications.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"add_user_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"User IDs to add as participants"},"remove_user_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"User IDs to remove from participants"}}}}}},"responses":{"200":{"description":"Participants updated","content":{"application/json":{"schema":{"type":"object","properties":{"added":{"type":"integer"},"removed":{"type":"integer"},"calendar_updated":{"type":"boolean"}}}}}},"400":{"description":"Bad request - session not finalized"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/notes":{"get":{"summary":"List notes for a session (E8-12)","description":"Returns internal notes for a session (CM/Admin only). Notes are displayed in reverse chronological order.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Session notes","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string"},"session_title":{"type":"string"},"notes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"content":{"type":"string"},"author_name":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"post":{"summary":"Create a note for a session (E8-12)","description":"Add an internal note to a session. Notes are visible only to Medley team.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"Rich text content (HTML)"}}}}}},"responses":{"200":{"description":"Note created"},"400":{"description":"Bad request - missing content"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/attendance":{"get":{"summary":"Get attendance details for a session (E8-11)","description":"Returns participants with attendance status and submission info","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Attendance details","content":{"application/json":{"schema":{"type":"object","properties":{"session_id":{"type":"string"},"attendance_marked":{"type":"boolean"},"submission":{"type":"object","nullable":true,"properties":{"submitted_by_name":{"type":"string"},"submitted_at":{"type":"string","format":"date-time"}}},"summary":{"type":"object"},"participants":{"type":"array"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"post":{"summary":"Mark attendance for a session (E8-11)","description":"Mark attendance for first time. CM can choose to trigger surveys.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attendance"],"properties":{"attendance":{"type":"array","items":{"type":"object","required":["user_id","status"],"properties":{"user_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["attended","partial","absent"]},"absent_reason":{"type":"string","enum":["business_conflict","personal_conflict","no_notice"]},"notes":{"type":"string"}}}},"send_surveys":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Attendance marked"},"400":{"description":"Bad request - session not ended or invalid data"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"put":{"summary":"Update attendance for a session (E8-11)","description":"Edit existing attendance records. Does not re-trigger surveys.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["attendance"],"properties":{"attendance":{"type":"array","items":{"type":"object","required":["user_id","status"],"properties":{"user_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["attended","partial","absent"]},"absent_reason":{"type":"string","enum":["business_conflict","personal_conflict","no_notice"]},"notes":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Attendance updated"},"400":{"description":"Bad request - attendance not marked yet or invalid data"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}},"delete":{"summary":"Clear the attendance of a 1:1 or check-in (MED-1360)","description":"Undoes a wrong attendance mark. Resets each attendance row to pending in place (withdrawn rows stay withdrawn) and sets the session back to scheduled (unscheduled when it has no date). Refused when a payment for the session is already in Bill.com.\n","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Attendance cleared"},"400":{"description":"Not a 1:1 session, or no attendance to clear"},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"},"409":{"description":"A payment for the session is already in Bill.com"}}}},"/cohorts/{cohortId}/groups/{groupId}/sessions":{"get":{"summary":"Get sessions for a specific group","description":"Returns all sessions for a specific group","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"groupId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of sessions for the group"},"401":{"description":"Unauthorized"},"404":{"description":"Group not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/coach-feedback":{"get":{"summary":"Get coach feedback for a session (CM view)","description":"Returns all coach feedback submissions and concern flags for a session. Read-only CM view.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Coach feedback with concern flags","content":{"application/json":{"schema":{"type":"object","properties":{"feedback":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"coach_name":{"type":"string"},"responses":{"type":"object"},"submitted_at":{"type":"string","format":"date-time"}}}},"concernFlags":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/concern-flags":{"get":{"summary":"Get concern flags for a session","description":"Returns all participant concern flags for a session with details.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of concern flags"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/concern-flags/{flagId}":{"patch":{"summary":"Mark a concern flag as addressed","description":"Updates a concern flag status to addressed with optional resolution notes.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"flagId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"resolution_notes":{"type":"string","description":"Optional notes about how the concern was addressed"}}}}}},"responses":{"200":{"description":"Flag marked as addressed"},"401":{"description":"Unauthorized"},"404":{"description":"Flag not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/absence-email-queue":{"get":{"summary":"Get absence email queue for a session (E19-2)","description":"Returns the auto-generated absence email queue entries for a session. Queue is generated automatically when attendance is submitted or updated. Entries are ordered by participant name (alphabetical).\n","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Absence email queue entries","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"session_id":{"type":"string","format":"uuid"},"cohort_participant_id":{"type":"string","format":"uuid"},"participant_email":{"type":"string"},"participant_name":{"type":"string"},"cumulative_absence_count":{"type":"integer"},"absence_type":{"type":"string","enum":["unexcused","business","personal"]},"is_first_session":{"type":"boolean"},"template_id":{"type":"string","format":"uuid","nullable":true},"rendered_subject":{"type":"string","nullable":true},"rendered_body":{"type":"string","nullable":true},"coach_name":{"type":"string","nullable":true},"coach_email":{"type":"string","nullable":true},"cm_edited":{"type":"boolean"},"template_updated":{"type":"boolean"},"status":{"type":"string","enum":["pending","sent","skipped","no_template"]},"sent_at":{"type":"string","format":"date-time","nullable":true},"sent_by_user_id":{"type":"string","format":"uuid","nullable":true}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or session not found"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/topic-override":{"put":{"summary":"Set or clear the per-session topic override (MED-702)","description":"Sets sessions.topic_override. When present, this value takes precedence over the session-number-level topic and the legacy topic_id->topics.name. Pass null to clear the override and re-inherit the session-number topic.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"topic_override":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Override saved"},"401":{"description":"Unauthorized"},"422":{"description":"Validation error"}}}},"/cohorts/{cohortId}/sessions/{sessionId}/leader":{"put":{"summary":"Assign or reassign the session leader (MED-812 / E4.0a)","description":"Designates a peer leader for a single session in a group_coaching\ncohort with `coaching_sub_type = 'peer_led'`. Used by the CM portal's\nSessions list/card inline edit.\n\n**Gating:**\n- Endpoint returns 404 when the `live_session_delivery` feature flag\n  is disabled (indistinguishable from a non-existent route).\n- The session's modality must be group_coaching + peer_led. Other\n  modalities receive 422 `not_peer_led`.\n\n**Lock cutoff:** Reassignment is blocked once `now() >= scheduled_start`.\nAfter that boundary the API returns 422 `session_already_started`.\n\n**Roster validation:** The designated user must be a current member of\nthe session's group (`group_members` row with `left_at IS NULL` and\n`is_deleted = false`). Otherwise 422 `participant_not_in_group`.\n\n**Audit:** Each successful designation writes a `session_audit_log`\nrow with action `leader_designated` (first assignment, previous was\nnull) or `leader_reassigned` (subsequent changes). The `changes` jsonb\nincludes the before/after values of `session_leader_id`.\n","tags":["Sessions"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["session_leader_id"],"properties":{"session_leader_id":{"type":"string","format":"uuid","description":"User ID of the peer leader to assign. Must be a current member of the session's group."}}}}}},"responses":{"200":{"description":"Leader designated; returns the updated session row."},"401":{"description":"Unauthorized"},"404":{"description":"Either the feature flag is off, the cohort/session was not found,\nor the session was soft-deleted.\n"},"422":{"description":"Validation error. Possible `code` values:\n- `not_peer_led` — session modality is not group_coaching + peer_led\n- `session_not_in_cohort` — sessionId does not belong to cohortId\n- `session_already_started` — now() is at/after scheduled_start\n- `participant_not_in_group` — designated user is not a current group member\n"}}}},"/admin/phases":{"get":{"summary":"Get all task phases","description":"Get all task phases with template counts","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"include_archived","schema":{"type":"boolean"},"description":"Include archived phases"}],"responses":{"200":{"description":"Phases retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskPhaseWithCount"}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new phase","description":"Create a new task phase for organizing templates","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskPhaseRequest"}}}},"responses":{"201":{"description":"Phase created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskPhase"}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"409":{"description":"Phase with this slug already exists"}}}},"/admin/phases/reorder":{"put":{"summary":"Reorder phases","description":"Update sort_order for multiple phases at once","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["phases"],"properties":{"phases":{"type":"array","items":{"type":"object","required":["id","sort_order"],"properties":{"id":{"type":"string","format":"uuid"},"sort_order":{"type":"integer"}}}}}}}}},"responses":{"200":{"description":"Phases reordered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskPhaseWithCount"}}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"404":{"description":"Phase not found"}}}},"/admin/phases/{id}":{"get":{"summary":"Get phase by ID","description":"Get a single task phase by ID with template count","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Phase ID"}],"responses":{"200":{"description":"Phase retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskPhaseWithCount"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Phase not found"}}},"put":{"summary":"Update a phase","description":"Update an existing task phase","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Phase ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaskPhaseRequest"}}}},"responses":{"200":{"description":"Phase updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskPhase"}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"404":{"description":"Phase not found"}}},"delete":{"summary":"Delete a phase","description":"Soft delete a task phase (only if it has no templates)","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Phase ID"}],"responses":{"204":{"description":"Phase deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Phase not found"},"409":{"description":"Cannot delete phase with templates"}}}},"/admin/phases/{id}/archive":{"post":{"summary":"Archive a phase","description":"Archive a task phase (soft hide without deleting)","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Phase ID"}],"responses":{"200":{"description":"Phase archived successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"archived":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Phase not found"}}}},"/admin/phases/{id}/restore":{"post":{"summary":"Restore an archived phase","description":"Restore a previously archived task phase","tags":["Task Phases"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Phase ID"}],"responses":{"200":{"description":"Phase restored successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"restored":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Phase not found"}}}},"/cohorts/{cohortId}/tasks":{"get":{"summary":"Get tasks for a cohort","description":"Get all tasks for a cohort with optional filtering by status, phase, and assignee","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated list of statuses to filter by","example":"not_started,in_progress"},{"in":"query","name":"phase","schema":{"type":"string"},"description":"Comma-separated list of phases to filter by","example":"cohort_setup,cohort_prep"},{"in":"query","name":"assignee_id","schema":{"type":"string","format":"uuid"},"description":"Filter by assignee"},{"in":"query","name":"session_id","schema":{"type":"string","format":"uuid"},"description":"Filter by session"},{"in":"query","name":"include_subtasks","schema":{"type":"boolean"},"description":"Include subtasks in results"}],"responses":{"200":{"description":"Tasks retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskWithDetails"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"post":{"summary":"Create a new task","description":"Create a new task for a cohort","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskRequest"}}}},"responses":{"201":{"description":"Task created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskWithDetails"}}}}}},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/tasks/stats":{"get":{"summary":"Get task statistics for a cohort","description":"Get aggregated task counts by status for a cohort","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Task statistics retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskStats"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/tasks/bulk":{"post":{"summary":"Bulk task operations","description":"Perform bulk operations on multiple tasks (update status, assignee, priority, or delete)","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkTaskRequest"}}}},"responses":{"200":{"description":"Bulk operation completed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"affected_count":{"type":"integer"}}}}}}}},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/tasks/{id}":{"get":{"summary":"Get task by ID","description":"Get a single task with full details including subtasks and comments","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID"}],"responses":{"200":{"description":"Task retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"allOf":[{"$ref":"#/components/schemas/TaskWithDetails"},{"type":"object","properties":{"subtasks":{"type":"array","items":{"$ref":"#/components/schemas/TaskWithDetails"}},"comments":{"type":"array","items":{"$ref":"#/components/schemas/TaskComment"}}}}]}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}},"put":{"summary":"Update a task","description":"Update task properties","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaskRequest"}}}},"responses":{"200":{"description":"Task updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskWithDetails"}}}}}},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}},"delete":{"summary":"Delete a task","description":"Soft delete a task","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID"}],"responses":{"204":{"description":"Task deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}}},"/cohorts/{cohortId}/tasks/{id}/duplicate":{"post":{"summary":"Duplicate a task","description":"Create a copy of an existing task. Copies title (prefixed with \"Copy of \"), description, priority, and phase/sub-group assignment. Does NOT copy status (defaults to not_started), due date, assignee, comments, activity log, or subtasks. Only parent tasks can be duplicated, not subtasks.","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID to duplicate"}],"responses":{"201":{"description":"Task duplicated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskWithDetails"}}}}}},"400":{"description":"Invalid request (e.g., attempting to duplicate a subtask)"},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}}},"/cohorts/{cohortId}/tasks/{id}/status":{"put":{"summary":"Quick status update","description":"Update task status with automatic completion tracking","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["not_started","in_progress","complete","blocked","cancelled"]},"completeSubtasks":{"type":"boolean","description":"When true and status is 'complete', also complete all incomplete subtasks under this parent task"}}}}}},"responses":{"200":{"description":"Status updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskWithDetails"}}}}}},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}}},"/cohorts/{cohortId}/tasks/{id}/activity":{"get":{"summary":"Get task activity log","description":"Get the activity log for a task showing all changes","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Maximum number of activity entries to return"}],"responses":{"200":{"description":"Activity log retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskActivityLog"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}}},"/cohorts/{cohortId}/tasks/{id}/comments":{"get":{"summary":"Get task comments","description":"Get all comments for a task","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID"}],"responses":{"200":{"description":"Comments retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskComment"}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}},"post":{"summary":"Add a comment to a task","description":"Add a new comment to a task","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"},{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["content"],"properties":{"content":{"type":"string","description":"Comment content"}}}}}},"responses":{"201":{"description":"Comment added successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskComment"}}}}}},"400":{"description":"Invalid request"},"401":{"description":"Unauthorized"},"404":{"description":"Task or cohort not found"}}}},"/cohorts/{cohortId}/milestones":{"get":{"summary":"Get milestones for a cohort","description":"Get all milestones for a cohort","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"Milestones retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"display_order":{"type":"integer"},"status":{"type":"string","enum":["not_started","in_progress","complete","blocked"]}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/tasks/group-counts":{"get":{"summary":"Get grouped task counts","description":"Get task counts grouped by a specified field, using the same filters as GET /tasks. Returns group keys, labels, and counts without task data.","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"groupBy","required":true,"schema":{"type":"string","enum":["cohort","phase","status","assignee","partner"]},"description":"Field to group task counts by"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search task title, cohort name, or assignee name"},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated list of statuses to filter by"},{"in":"query","name":"phase","schema":{"type":"string"},"description":"Comma-separated list of phase slugs or names to filter by"},{"in":"query","name":"cohortIds","schema":{"type":"string"},"description":"Comma-separated list of cohort IDs to filter by"},{"in":"query","name":"assigneeIds","schema":{"type":"string"},"description":"Comma-separated list of user IDs to filter by assignee"},{"in":"query","name":"dueDateFrom","schema":{"type":"string","format":"date"},"description":"Filter tasks with due date on or after this date"},{"in":"query","name":"dueDateTo","schema":{"type":"string","format":"date"},"description":"Filter tasks with due date on or before this date"},{"in":"query","name":"isOverdue","schema":{"type":"boolean"},"description":"Filter for overdue tasks only"},{"in":"query","name":"includeOverdue","schema":{"type":"boolean"},"description":"Include overdue tasks in addition to date range filter (OR logic)"}],"responses":{"200":{"description":"Group counts retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"Group identifier"},"label":{"type":"string","description":"Human-readable group label"},"count":{"type":"integer","description":"Number of tasks in this group"}}}}}}}}},"400":{"description":"Bad request - missing or invalid groupBy parameter"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/tasks":{"get":{"summary":"Get all tasks across cohorts","description":"Get all tasks across all cohorts with filtering, sorting, and pagination. Supports search, status, phase, cohort, assignee, and date range filters.","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"description":"Search task title, cohort name, or assignee name"},{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated list of statuses to filter by","example":"not_started,in_progress"},{"in":"query","name":"phase","schema":{"type":"string"},"description":"Comma-separated list of phase slugs or names to filter by","example":"cohort_setup,cohort_prep"},{"in":"query","name":"cohortIds","schema":{"type":"string"},"description":"Comma-separated list of cohort IDs to filter by","example":"uuid-1,uuid-2"},{"in":"query","name":"assigneeIds","schema":{"type":"string"},"description":"Comma-separated list of user IDs to filter by assignee"},{"in":"query","name":"dueDateFrom","schema":{"type":"string","format":"date"},"description":"Filter tasks with due date on or after this date"},{"in":"query","name":"dueDateTo","schema":{"type":"string","format":"date"},"description":"Filter tasks with due date on or before this date"},{"in":"query","name":"isOverdue","schema":{"type":"boolean"},"description":"Filter for overdue tasks only (due_date < today AND status not complete/cancelled)"},{"in":"query","name":"includeOverdue","schema":{"type":"boolean"},"description":"Include overdue tasks in addition to date range filter (OR logic)"},{"in":"query","name":"sortBy","schema":{"type":"string","enum":["due_date","partner","cohort","status"]},"description":"Field to sort by (default due_date)"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["asc","desc"]},"description":"Sort direction (default asc)"},{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number for pagination"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Number of tasks per page"}],"responses":{"200":{"description":"Tasks retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CrossCohortTaskRow"}},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/tasks/assignees":{"get":{"summary":"Get users with task assignments","description":"Get all users who have at least one task assigned. Used for populating the assignee filter avatars.","tags":["Tasks"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Assignees retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskAssignee"}}}}}}},"401":{"description":"Unauthorized"}}}},"/tasks/my-tasks":{"get":{"summary":"Get current user's tasks","description":"Get tasks assigned to the current user across all cohorts","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"status","schema":{"type":"string"},"description":"Comma-separated list of statuses to filter by"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Maximum number of tasks to return"}],"responses":{"200":{"description":"Tasks retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskWithDetails"}}}}}}},"401":{"description":"Unauthorized"}}}},"/tasks/overdue":{"get":{"summary":"Get all overdue tasks","description":"Get all overdue tasks across all cohorts (admin view)","tags":["Tasks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Maximum number of tasks to return"}],"responses":{"200":{"description":"Overdue tasks retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskWithDetails"}}}}}}},"401":{"description":"Unauthorized"}}}},"/admin/sub-groups":{"get":{"summary":"Get all task sub-groups","description":"Get all task sub-groups with details, optionally filtered by phase","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"phase_id","schema":{"type":"string","format":"uuid"},"description":"Filter by phase ID"},{"in":"query","name":"include_archived","schema":{"type":"boolean"},"description":"Include archived sub-groups"}],"responses":{"200":{"description":"Sub-groups retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskSubGroupWithDetails"}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new sub-group","description":"Create a new task sub-group for organizing templates within a phase","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskSubGroupRequest"}}}},"responses":{"201":{"description":"Sub-group created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskSubGroup"}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"404":{"description":"Phase not found"},"409":{"description":"Sub-group with this name already exists in phase"}}}},"/admin/sub-groups/reorder":{"put":{"summary":"Reorder sub-groups","description":"Update sort_order for multiple sub-groups at once","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sub_groups"],"properties":{"sub_groups":{"type":"array","items":{"type":"object","required":["id","sort_order"],"properties":{"id":{"type":"string","format":"uuid"},"sort_order":{"type":"integer"}}}}}}}}},"responses":{"200":{"description":"Sub-groups reordered successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TaskSubGroupWithDetails"}}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"404":{"description":"Sub-group not found"}}}},"/admin/sub-groups/{id}/template-count":{"get":{"summary":"Get template count for a sub-group","description":"Get the number of templates in a sub-group (for archive warning)","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Sub-group ID"}],"responses":{"200":{"description":"Template count retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"template_count":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Sub-group not found"}}}},"/admin/sub-groups/{id}":{"get":{"summary":"Get sub-group by ID","description":"Get a single task sub-group by ID with details","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Sub-group ID"}],"responses":{"200":{"description":"Sub-group retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskSubGroupWithDetails"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Sub-group not found"}}},"put":{"summary":"Update a sub-group","description":"Update an existing task sub-group","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Sub-group ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaskSubGroupRequest"}}}},"responses":{"200":{"description":"Sub-group updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TaskSubGroup"}}}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"404":{"description":"Sub-group not found"},"409":{"description":"Sub-group with this name already exists in phase"}}},"delete":{"summary":"Delete a sub-group","description":"Soft delete a task sub-group (only if it has no templates)","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Sub-group ID"}],"responses":{"204":{"description":"Sub-group deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Sub-group not found"},"409":{"description":"Cannot delete sub-group with templates"}}}},"/admin/sub-groups/{id}/archive":{"post":{"summary":"Archive a sub-group","description":"Archive a task sub-group (soft hide without deleting)","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Sub-group ID"}],"responses":{"200":{"description":"Sub-group archived successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"archived":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Sub-group not found"}}}},"/admin/sub-groups/{id}/restore":{"post":{"summary":"Restore an archived sub-group","description":"Restore a previously archived task sub-group","tags":["Task Sub-Groups"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Sub-group ID"}],"responses":{"200":{"description":"Sub-group restored successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"restored":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Sub-group not found"}}}},"/api/admin/task-templates":{"get":{"summary":"Get all task templates","description":"Retrieve all task templates with optional filtering. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"query","name":"cohort_type","schema":{"type":"string"},"description":"Filter by cohort type"},{"in":"query","name":"phase","schema":{"type":"string","enum":["cohort_setup","cohort_prep","live_delivery","wrap_renewal"]},"description":"Filter by task phase"},{"in":"query","name":"template_type","schema":{"type":"string","enum":["base","group_coaching","one_on_one","workshop"]},"description":"Filter by template type"},{"in":"query","name":"parent_only","schema":{"type":"boolean"},"description":"If true, only return parent templates (exclude subtask templates)"},{"in":"query","name":"is_active","schema":{"type":"boolean"},"description":"Filter by active status"}],"responses":{"200":{"description":"List of task templates","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TaskTemplateWithDetails"}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}},"post":{"summary":"Create a task template","description":"Create a new task template. Admin only.","tags":["Admin - Task Templates"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTaskTemplateRequest"}}}},"responses":{"201":{"description":"Task template created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskTemplate"}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/cohort-types":{"get":{"summary":"Get distinct cohort types","description":"Get all distinct cohort types that have templates defined. Admin only.","tags":["Admin - Task Templates"],"responses":{"200":{"description":"List of cohort types","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/template-types":{"get":{"summary":"Get available template types","description":"Get all available template types with their labels. Admin only.","tags":["Admin - Task Templates"],"responses":{"200":{"description":"List of template types with labels","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TemplateTypeOption"}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/grouped":{"get":{"summary":"Get templates grouped by phase and sub-group","description":"Retrieve templates organized hierarchically by phase and sub-group for the accordion display. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"query","name":"template_type","required":true,"schema":{"type":"string","enum":["base","group_coaching","one_on_one","workshop"]},"description":"Template type to filter by"},{"in":"query","name":"include_archived","schema":{"type":"boolean"},"description":"Include archived templates, phases, and sub-groups"}],"responses":{"200":{"description":"Hierarchical structure of phases with sub-groups and templates","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"template_count":{"type":"integer"},"sub_groups":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"templates":{"type":"array","items":{"$ref":"#/components/schemas/TaskTemplateWithDetails"}}}}}}}}}}}}},"400":{"description":"Missing or invalid template_type parameter"},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/counts":{"get":{"summary":"Get template counts by type","description":"Get the count of non-archived parent templates for each template type. Admin only.","tags":["Admin - Task Templates"],"responses":{"200":{"description":"Template counts by type","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"base":{"type":"integer"},"group_coaching":{"type":"integer"},"one_on_one":{"type":"integer"},"workshop":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/{id}":{"get":{"summary":"Get a task template by ID","description":"Retrieve a single task template with details. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task template ID"}],"responses":{"200":{"description":"Task template details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskTemplateWithDetails"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Task template not found"},"500":{"description":"Internal server error"}}},"put":{"summary":"Update a task template","description":"Update an existing task template. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task template ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaskTemplateRequest"}}}},"responses":{"200":{"description":"Task template updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskTemplate"}}}},"400":{"description":"Invalid request body"},"401":{"description":"Unauthorized"},"404":{"description":"Task template not found"},"500":{"description":"Internal server error"}}},"delete":{"summary":"Delete a task template","description":"Soft delete a task template. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task template ID"}],"responses":{"204":{"description":"Task template deleted"},"401":{"description":"Unauthorized"},"404":{"description":"Task template not found"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/{id}/subtasks":{"get":{"summary":"Get subtask templates for a parent template","description":"Retrieve all subtask templates for a given parent template. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Parent template ID"}],"responses":{"200":{"description":"List of subtask templates","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TaskTemplateWithDetails"}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Parent template not found"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/{id}/duplicate":{"post":{"summary":"Duplicate a task template","description":"Create a copy of an existing task template. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task template ID to duplicate"}],"responses":{"201":{"description":"Task template duplicated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskTemplate"}}}},"401":{"description":"Unauthorized"},"404":{"description":"Task template not found"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/{id}/archive":{"post":{"summary":"Archive a task template","description":"Archive a task template (soft hide without deleting). Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task template ID"}],"responses":{"200":{"description":"Task template archived","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"archived":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Task template not found"},"500":{"description":"Internal server error"}}}},"/api/admin/task-templates/{id}/restore":{"post":{"summary":"Restore an archived task template","description":"Restore a previously archived task template. Admin only.","tags":["Admin - Task Templates"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Task template ID"}],"responses":{"200":{"description":"Task template restored","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"restored":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Task template not found"},"500":{"description":"Internal server error"}}}},"/cohorts/{cohortId}/time-slots":{"get":{"summary":"List all time slots for a cohort","description":"Returns all time slots for a cohort ordered by slot number","tags":["TimeSlots"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"Cohort ID"}],"responses":{"200":{"description":"List of time slots","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"time_slots":{"type":"array","items":{"$ref":"#/components/schemas/TimeSlotResponse"}},"total":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}},"post":{"summary":"Create a new time slot","description":"Creates a new time slot with auto-generated identifier","tags":["TimeSlots"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTimeSlotRequest"}}}},"responses":{"201":{"description":"Time slot created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TimeSlot"},"message":{"type":"string"}}}}}},"400":{"description":"Invalid input or cohort has no code"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/time-slots/{timeSlotId}":{"get":{"summary":"Get a time slot by ID","description":"Returns a single time slot","tags":["TimeSlots"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"timeSlotId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Time slot details","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TimeSlot"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or time slot not found"}}},"put":{"summary":"Update a time slot","description":"Updates day, time, or timezone (identifier and slot_number are immutable)","tags":["TimeSlots"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"timeSlotId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTimeSlotRequest"}}}},"responses":{"200":{"description":"Time slot updated","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/TimeSlot"},"message":{"type":"string"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or time slot not found"}}},"delete":{"summary":"Delete a time slot","description":"Soft deletes a time slot. Cannot delete if assigned to a scheduled group session.","tags":["TimeSlots"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"timeSlotId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Time slot deleted"},"400":{"description":"Cannot delete - time slot is assigned to a scheduled session"},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or time slot not found"}}}},"/cohorts/{cohortId}/time-slots/{timeSlotId}/usage":{"get":{"summary":"Get time slot usage information","description":"Returns usage information for a time slot to inform deletion confirmation dialog","tags":["TimeSlots"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"timeSlotId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Usage information","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"isAssignedToGroup":{"type":"boolean","description":"Whether the time slot is assigned to a scheduled group session"},"availabilityRequestCount":{"type":"integer","description":"Number of availability requests that reference this time slot"},"canDelete":{"type":"boolean","description":"Whether the time slot can be deleted (false if assigned to a group)"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort or time slot not found"}}}},"/cohorts/{cohortId}/time-slots/validate":{"post":{"summary":"Validate time slot identifiers","description":"Checks if an array of time slot identifiers exist for a cohort","tags":["TimeSlots"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["identifiers"],"properties":{"identifiers":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"not_found":{"type":"array","items":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Cohort not found"}}}},"/topics":{"get":{"summary":"Get all topics","description":"Get all active topics, optionally filtered by search term. Results are sorted alphabetically.","tags":["Topics"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"search","schema":{"type":"string"},"required":false,"description":"Search term to filter topics by name (partial match)"}],"responses":{"200":{"description":"Topics retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Topic"}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new topic (or return existing on case-insensitive name match)","description":"Adds a new topic to the global catalog. The topics.name column is case-insensitive\n(citext) and unique — if a topic with the given name already exists (any case),\nthe existing row is returned with HTTP 200 and `meta.created = false` so the\nclient can show a \"Topic already exists — selected\" toast (MED-832 AC7).\n\nNewly created topics return HTTP 201 with `meta.created = true`. The new topic\nis is_active = true and gets display_order = MAX + 1.\n","tags":["Topics"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":200,"description":"Topic name. Whitespace is trimmed. Empty string is rejected.","example":"Group Formation"}}}}}},"responses":{"200":{"description":"Existing topic returned (case-insensitive name match)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Topic"},"meta":{"type":"object","properties":{"created":{"type":"boolean","example":false}}}}}}}},"201":{"description":"New topic created","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Topic"},"meta":{"type":"object","properties":{"created":{"type":"boolean","example":true}}}}}}}},"401":{"description":"Unauthorized"},"422":{"description":"Validation failed (empty name, missing name, name too long)"}}}},"/topics/{id}":{"get":{"summary":"Get topic by ID","description":"Get a single topic by its ID","tags":["Topics"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"Topic ID"}],"responses":{"200":{"description":"Topic retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/Topic"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Topic not found"}}}},"/users":{"get":{"summary":"List all internal users","description":"Get paginated list of internal users (Admins and Cohort Managers) with filtering and search","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1},"description":"Page number"},{"in":"query","name":"limit","schema":{"type":"integer","default":25},"description":"Number of items per page"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by name or email"},{"in":"query","name":"status","schema":{"type":"string","enum":["all","active","inactive"],"default":"all"},"description":"Filter by user status"},{"in":"query","name":"persona","schema":{"type":"string","enum":["internal_admin","internal_user"]},"description":"Filter by persona type"},{"in":"query","name":"sortBy","schema":{"type":"string","enum":["first_name","last_name","email","department","job_title","created_at","last_login_at"],"default":"first_name"},"description":"Field to sort by"},{"in":"query","name":"sortOrder","schema":{"type":"string","enum":["asc","desc"],"default":"asc"},"description":"Sort order"}],"responses":{"200":{"description":"Users retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"$ref":"#/components/schemas/InternalUser"}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"page":{"type":"integer"},"limit":{"type":"integer"},"total":{"type":"integer"},"totalPages":{"type":"integer"}}},"totalActive":{"type":"integer"},"totalInactive":{"type":"integer"}}}}}}}},"401":{"description":"Unauthorized"}}},"post":{"summary":"Create a new user","description":"Create a new internal user (Admin or Cohort Manager). An invitation email will be sent.","tags":["Users"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserInput"}}}},"responses":{"201":{"description":"User created successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/InternalUser"},"message":{"type":"string"}}}}}},"400":{"description":"Bad request - validation error"},"401":{"description":"Unauthorized"},"409":{"description":"Conflict - email already exists"}}}},"/users/check-email":{"get":{"summary":"Check if email exists","description":"Check if an email address is already in use","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"email","required":true,"schema":{"type":"string","format":"email"},"description":"Email address to check"}],"responses":{"200":{"description":"Email check result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"exists":{"type":"boolean"}}}}}}}},"400":{"description":"Bad request - email is required"},"401":{"description":"Unauthorized"}}}},"/users/internal":{"get":{"summary":"Get internal users for dropdowns","description":"Get all internal users formatted for team assignment dropdowns","tags":["Users"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Internal users retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"email":{"type":"string"}}}}}}}}},"401":{"description":"Unauthorized"}}}},"/users/{id}":{"get":{"summary":"Get user by ID","description":"Get a single internal user's details by their ID","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/InternalUser"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}},"put":{"summary":"Update a user","description":"Update an internal user's profile (role, department, persona)","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserInput"}}}},"responses":{"200":{"description":"User updated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/InternalUser"},"message":{"type":"string"}}}}}},"400":{"description":"Bad request - validation error"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/users/{id}/deactivate":{"post":{"summary":"Deactivate a user","description":"Deactivate an internal user. Deactivated users cannot log in. Users cannot deactivate themselves.","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User deactivated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"is_active":{"type":"boolean"}}},"message":{"type":"string"}}}}}},"400":{"description":"Bad request - cannot deactivate yourself or user already inactive"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/users/{id}/activate":{"post":{"summary":"Activate a user","description":"Reactivate a deactivated internal user","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"User activated successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"is_active":{"type":"boolean"}}},"message":{"type":"string"}}}}}},"400":{"description":"Bad request - user already active"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/users/{id}/resend-invitation":{"post":{"summary":"Resend invitation","description":"Resend the invitation email to a user who hasn't accepted yet","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"}],"responses":{"200":{"description":"Invitation resent successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"}}},"message":{"type":"string"}}}}}},"400":{"description":"Bad request - user has already accepted their invitation"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/users/{id}/activity":{"get":{"summary":"Get user activity log","description":"Get the audit trail of all actions performed on or by a user","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"},"description":"User ID"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Maximum number of activity entries to return"}],"responses":{"200":{"description":"Activity log retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"user_id":{"type":"string","format":"uuid"},"performed_by":{"type":"string","format":"uuid","nullable":true},"performed_by_name":{"type":"string"},"action":{"type":"string","enum":["user_created","user_updated","user_deactivated","user_activated","persona_changed","invitation_sent","invitation_resent","invitation_accepted"]},"description":{"type":"string"},"field_name":{"type":"string","nullable":true},"old_value":{"type":"string","nullable":true},"new_value":{"type":"string","nullable":true},"changes":{"type":"object","nullable":true},"created_at":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/users/me/preferences":{"get":{"summary":"Get all user preferences","description":"Get all preferences for the authenticated user as a key-value object","tags":["Users"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User preferences retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","additionalProperties":true,"description":"Key-value pairs of user preferences"}}}}}},"401":{"description":"Unauthorized"}}}},"/users/me/preferences/{key}":{"get":{"summary":"Get a specific preference","description":"Get a specific preference value by key for the authenticated user","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"key","required":true,"schema":{"type":"string"},"description":"The preference key (e.g., \"participants_columns_abc123\")"}],"responses":{"200":{"description":"Preference retrieved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"description":"The preference value (can be any JSON type)"}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Preference not found"}}},"put":{"summary":"Set a preference","description":"Create or update a preference for the authenticated user","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"key","required":true,"schema":{"type":"string"},"description":"The preference key"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["value"],"properties":{"value":{"description":"The preference value (can be any JSON type)"}}},"example":{"value":[{"columnId":"name","visible":true,"order":0},{"columnId":"email","visible":false,"order":1}]}}}},"responses":{"200":{"description":"Preference saved successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"description":"The saved preference value"}}}}}},"400":{"description":"Bad request - value is required"},"401":{"description":"Unauthorized"}}},"delete":{"summary":"Delete a preference","description":"Delete a preference for the authenticated user","tags":["Users"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"key","required":true,"schema":{"type":"string"},"description":"The preference key to delete"}],"responses":{"204":{"description":"Preference deleted successfully"},"401":{"description":"Unauthorized"},"404":{"description":"Preference not found"}}}},"/api/participant/sessions/{sessionId}/waiting-room":{"get":{"summary":"Get the participant waiting room payload","description":"Returns the composite payload needed to render the participant\nwaiting room: session info + leader, AI features flag, attendee\nroster with computed presence states (present / joining /\nnot_yet_here), reflection state for the orientation bullet,\nand a server `now` timestamp clients use for the countdown.\n\nSide effect: upserts the caller's presence heartbeat\n(`session_attendance.last_seen_at`, `joined_at` set on first call).\n\nGated by `live_session_delivery` feature flag. Returns 403 when the\nauthenticated user is not enrolled in the session's cohort.\n","tags":["Waiting Room"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Waiting room payload","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/WaitingRoomPayload"}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Participant not enrolled in this session's cohort"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/api/participant/sessions/{sessionId}/volunteer-start":{"post":{"summary":"Volunteer claim-and-launch for a day-of no-show (MED-1006 AC8)","description":"The single \"I'll lead\" action. Atomically claims leadership\n(sets session_leader_id to the caller) and launches the session\n(scheduled → in_progress) for a peer-led Live Session Delivery session\nin the no-show window. Exactly one starter wins; `became_leader: false`\nmeans another member won the race and the caller should land in the\nparticipant active view (AC9 — not an error).\n\nAuthorization: peer-led + Live Session Delivery enabled, within the\nvolunteer window, the caller has not opted out (did not decline the\nvolunteer call / \"Keep waiting\"), and is a current group member.\n","tags":["Waiting Room"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Claim-and-launch result","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"became_leader":{"type":"boolean","description":"true if this caller won the launch race"},"session_id":{"type":"string","format":"uuid"},"cohort_id":{"type":"string","format":"uuid"}}}}}}}},"401":{"description":"Unauthorized"},"403":{"description":"Not eligible (opted out","not a group member":null,"or not a peer-led live session)":null},"404":{"description":"Session not found OR live_session_delivery flag off"},"409":{"description":"Volunteer window not open","or already launched/missed":null}}}},"/api/participant/sessions/{sessionId}/keep-waiting":{"post":{"summary":"Opt out of leading for this session (MED-1006 AC17)","description":"Records the \"Keep waiting — let someone else step in\" choice. Persisted\nas a volunteer-call decline so the \"Start Session\" CTA is not restored\non leave/re-join. No in-room reversal in MVP.\n","tags":["Waiting Room"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Opt-out recorded","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"opted_out":{"type":"boolean"}}}}}}}},"401":{"description":"Unauthorized"},"404":{"description":"Session not found OR live_session_delivery flag off"}}}},"/webhooks/resend":{"post":{"summary":"Handle Resend email events","description":"Receives webhook events from Resend for email tracking.\nEvents include: sent, delivered, opened, clicked, bounced, complained.\nConfigure this URL in Resend dashboard: https://resend.com/webhooks\n","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","data"],"properties":{"type":{"type":"string","enum":["email.sent","email.delivered","email.delivery_delayed","email.complained","email.bounced","email.opened","email.clicked"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"email_id":{"type":"string"},"from":{"type":"string"},"to":{"type":"array","items":{"type":"string"}},"subject":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Webhook processed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"message":{"type":"string"}}}}}}}},"400":{"description":"Invalid webhook payload"},"401":{"description":"Invalid webhook signature"},"409":{"description":"Duplicate webhook event"}}}},"/webhooks/paperform":{"post":{"summary":"Handle Paperform form submissions","description":"Receives webhook events from Paperform for form submissions (intake, feedback, surveys).\nValidates signature, stores payload, and queues for async processing.\nConfigure this URL in Paperform dashboard with your webhook secret.\n\nURL format: https://api.medley.com/webhooks/paperform?secret={your_secret}\n\nRequired hidden fields in Paperform forms:\n- sgos_participant_id: The cohort_participant.id for participant matching\n- sgos_session_id: (Optional) For session feedback forms\n","tags":["Webhooks"],"parameters":[{"in":"query","name":"secret","schema":{"type":"string"},"description":"Webhook secret for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["submission_id","form_id","data"],"properties":{"event":{"type":"string","example":"form.submission.created"},"submission_id":{"type":"string","description":"Unique Paperform submission ID"},"form_id":{"type":"string","description":"Paperform form ID"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"array","description":"Array of form field responses","items":{"type":"object","properties":{"key":{"type":"string","description":"Field key/ID"},"title":{"type":"string","description":"Field label"},"value":{"description":"Field value (type varies)"},"type":{"type":"string","description":"Field type (text, hidden, rating, etc.)"}}}}}}}}},"responses":{"200":{"description":"Webhook received and queued for processing","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","properties":{"message":{"type":"string","example":"Webhook received"},"event_id":{"type":"string","format":"uuid"}}}}}}}},"400":{"description":"Invalid webhook payload"},"401":{"description":"Invalid webhook signature or secret"},"409":{"description":"Duplicate submission already processed"}}}},"/webhooks/health":{"get":{"summary":"Webhook endpoint health check","description":"Simple health check to verify webhook endpoint is accessible","tags":["Webhooks"],"responses":{"200":{"description":"Webhook endpoint is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"type":"string","example":"healthy"},"timestamp":{"type":"string","format":"date-time"}}}}}}}}}}},"/webhooks/email-sent":{"post":{"summary":"Log a sent email (internal use)","description":"Internal endpoint called by the communications service to log emails\nafter they are sent via Resend. Requires x-api-key header authentication.\n","tags":["Webhooks"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email_type","from_email","to_email","subject"],"properties":{"resend_id":{"type":"string","description":"Resend's email ID (null if send failed)"},"email_type":{"type":"string","description":"Type of email (invitation, welcome, etc.)"},"from_email":{"type":"string"},"to_email":{"type":"string"},"subject":{"type":"string"},"user_id":{"type":"string","format":"uuid"},"metadata":{"type":"object"},"status":{"type":"string","enum":["sent","failed"]},"error_message":{"type":"string"}}}}}},"responses":{"201":{"description":"Email logged successfully"},"400":{"description":"Invalid request - missing required fields"},"401":{"description":"Invalid or missing API key"}}}},"/webhooks/google-calendar":{"post":{"summary":"Handle Google Calendar push notifications","description":"Receives push notifications from Google Calendar when watched events change.\nUsed for RSVP tracking - when attendees accept/decline calendar invites,\nGoogle sends a notification here, and we update session_attendance records.\n\n**Setup Instructions:**\n1. Register your webhook URL with Google (see GOOGLE_CALENDAR_WEBHOOK.md)\n2. Set GOOGLE_CALENDAR_WEBHOOK_TOKEN env var for verification\n3. Webhook URL must be HTTPS in production\n\n**Headers from Google:**\n- X-Goog-Channel-ID: Your channel ID\n- X-Goog-Resource-ID: Google's resource ID\n- X-Goog-Resource-State: \"sync\", \"exists\", or \"not_exists\"\n- X-Goog-Channel-Token: Your verification token\n- X-Goog-Resource-URI: URI of changed resource\n","tags":["Webhooks"],"responses":{"200":{"description":"Webhook processed","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"message":{"type":"string"}}}}}}}},"401":{"description":"Invalid webhook token"}}}},"/webhooks/calendly":{"post":{"summary":"Handle Calendly webhook events","description":"Receives webhook events from Calendly for booking notifications.\nEvents: invitee.created (new booking), invitee.canceled (cancellation).\nWebhook signature verified using Calendly-Webhook-Signature header.\n","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","payload"],"properties":{"event":{"type":"string","enum":["invitee.created","invitee.canceled"]},"created_at":{"type":"string","format":"date-time"},"payload":{"type":"object"}}}}}},"responses":{"200":{"description":"Webhook processed successfully"},"401":{"description":"Invalid or missing webhook signature"}}}},"/webhooks/billcom":{"post":{"summary":"Handle Bill.com payment status webhooks","description":"Receives webhook events from Bill.com for payment status changes.\nEvents: payment.updated, payment.failed.\nSignature verified using HMAC-SHA256 with x-bill-sha-signature header.\nPublic endpoint — no authentication required.\n","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["metadata","payment"],"properties":{"metadata":{"type":"object","properties":{"eventType":{"type":"string","enum":["payment.updated","payment.failed"]},"eventId":{"type":"string"}}},"payment":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Webhook received and processed"},"401":{"description":"Invalid or missing HMAC signature"}}}},"/webhooks/google-calendar/status":{"get":{"summary":"Get Google Calendar webhook status","description":"Returns watch channel status and calendar service configuration","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Webhook status"},"401":{"description":"Authentication required"}}}},"/webhooks/google-calendar/force-renew":{"post":{"summary":"Force re-register Google Calendar watch channel","description":"Stops existing channels and registers a new one","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Channel re-registered"},"401":{"description":"Authentication required"}}}},"/webhooks/google-calendar/sync-session/{sessionId}":{"post":{"summary":"Manually sync RSVPs for a specific session","description":"Fetches attendee responses from Google Calendar and updates session_attendance","tags":["Webhooks"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"RSVP sync result with Google Calendar attendee data"},"401":{"description":"Authentication required"},"404":{"description":"Session not found"}}}},"/api/auth/login":{"post":{"summary":"Authenticate user and get tokens","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"200":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Invalid credentials"},"423":{"description":"Account locked"}}}},"/api/auth/refresh":{"post":{"summary":"Refresh access token using refresh token","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Token refreshed successfully"},"401":{"description":"Invalid or expired refresh token"}}}},"/api/auth/me":{"get":{"summary":"Get current authenticated user","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Current user data"},"401":{"description":"Not authenticated"}}}},"/api/auth/logout":{"post":{"summary":"Logout current user (revokes refresh token)","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string","description":"The refresh token to revoke"}}}}}},"responses":{"200":{"description":"Logout successful"}}}},"/api/auth/logout-all":{"post":{"summary":"Logout from all devices (revokes all refresh tokens)","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"All sessions terminated"},"401":{"description":"Not authenticated"}}}},"/api/auth/sessions":{"get":{"summary":"Get all active sessions for current user","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of active sessions"},"401":{"description":"Not authenticated"}}}},"/api/auth/validate":{"get":{"summary":"Validate current token","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Token is valid"},"401":{"description":"Token is invalid"}}}},"/api/auth/magic-link/verify":{"post":{"summary":"Verify magic link and authenticate user","description":"Validates the magic link token, marks it as used, and returns authentication tokens","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"The magic link token"}}}}}},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"},"tokens":{"type":"object","properties":{"accessToken":{"type":"string"},"refreshToken":{"type":"string"}}},"isFirstLogin":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid or expired token"},"404":{"description":"Token not found"}}}},"/api/auth/magic-link/request":{"post":{"summary":"Request a new magic link","description":"Sends a magic link email to the user for passwordless login","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"User's email address"}}}}}},"responses":{"200":{"description":"If the email exists and has the correct persona, a magic link will be sent"},"429":{"description":"Rate limit exceeded"}}}},"/api/auth/impersonate":{"post":{"summary":"Start impersonating another user","description":"Admin-only endpoint to start an impersonation session. Returns tokens for the target user with impersonation tracking.","tags":["Auth"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["targetUserId"],"properties":{"targetUserId":{"type":"string","format":"uuid","description":"The user ID to impersonate"}}}}}},"responses":{"200":{"description":"Impersonation session started"},"400":{"description":"Cannot impersonate admin users"},"403":{"description":"Not authorized to impersonate"},"404":{"description":"Target user not found"}}}},"/api/auth/end-impersonate":{"post":{"summary":"End impersonation session","description":"End the current impersonation session and return admin's own tokens","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Impersonation session ended"},"400":{"description":"Not currently impersonating"}}}},"/api/auth/users-by-persona/{persona}":{"get":{"summary":"List users by persona for impersonation dropdown","description":"Returns active users with the specified persona. Admin-only endpoint.","tags":["Auth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"persona","required":true,"schema":{"type":"string","enum":["internal_user","coach","participant","partner"]},"description":"The persona type to filter by"}],"responses":{"200":{"description":"List of users with the specified persona"},"400":{"description":"Invalid persona or cannot list admin users"},"403":{"description":"Not authorized"}}}},"/api/auth/impersonation-log":{"get":{"summary":"Get impersonation audit log","description":"Returns all impersonation sessions. Admin-only endpoint.","tags":["Auth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":25}}],"responses":{"200":{"description":"Impersonation log entries"},"403":{"description":"Not authorized"}}}},"/api/auth/impersonation-log/{id}/activities":{"get":{"summary":"Get activities during an impersonation session","description":"Returns all activity log entries that occurred during a specific impersonation session's time window.","tags":["Auth"],"security":[{"bearerAuth":[]}],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Activities during the session"},"404":{"description":"Session not found"}}}},"/api/auth/forgot-password":{"post":{"summary":"Request password reset","description":"Sends a password reset email to the user if the email exists","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"If the email exists, a reset link will be sent"},"429":{"description":"Too many requests"}}}},"/api/auth/reset-password/validate":{"get":{"summary":"Validate password reset token","description":"Check if a password reset token is valid","tags":["Auth"],"parameters":[{"in":"query","name":"token","required":true,"schema":{"type":"string"},"description":"The password reset token"}],"responses":{"200":{"description":"Token is valid","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"valid":{"type":"boolean"},"email":{"type":"string"}}}}}}}},"404":{"description":"Invalid or expired token"}}}},"/api/auth/reset-password":{"post":{"summary":"Reset password with token","description":"Reset the user's password using a valid reset token","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","password"],"properties":{"token":{"type":"string","description":"The password reset token"},"password":{"type":"string","format":"password","description":"The new password (min 8 characters)"}}}}}},"responses":{"200":{"description":"Password reset successfully"},"400":{"description":"Invalid request or password requirements not met"},"404":{"description":"Invalid or expired token"}}}},"/api/auth/admin/reset-password":{"post":{"summary":"Admin-initiated password reset","description":"Send a password reset email to a user (admin only)","tags":["Auth"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["userId"],"properties":{"userId":{"type":"string","format":"uuid","description":"The user's ID"}}}}}},"responses":{"200":{"description":"Password reset email sent"},"401":{"description":"Unauthorized"},"404":{"description":"User not found"}}}},"/api/cohorts/{cohortId}/coaches":{"get":{"summary":"List coaches in a cohort","description":"Returns all coaches in the cohort's potential and assigned pools with their details","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The cohort ID"},{"in":"query","name":"page","schema":{"type":"integer","default":1}},{"in":"query","name":"limit","schema":{"type":"integer","default":25}},{"in":"query","name":"pool_status","schema":{"type":"string","enum":["potential","assigned"]}},{"in":"query","name":"availability_status","schema":{"type":"string","enum":["not_requested","pending_response","available","partially_available","unavailable"]}},{"in":"query","name":"search","schema":{"type":"string"}}],"responses":{"200":{"description":"List of coaches in the cohort"},"404":{"description":"Cohort not found"}}},"post":{"summary":"Add coaches to the potential pool","description":"Adds one or more coaches to the cohort's potential pool","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["coach_user_ids"],"properties":{"coach_user_ids":{"type":"array","items":{"type":"string","format":"uuid"}}}}}}},"responses":{"201":{"description":"Coaches added to pool"},"400":{"description":"Invalid request"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/stats":{"get":{"summary":"Get coach statistics for a cohort","description":"Returns stats for the Coaches tab stat cards","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Coach statistics"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/schedule":{"get":{"summary":"Get coach schedule grid data","description":"Returns session columns and coach rows with cell states for the schedule grid view. Cell states include scheduled, available, available with conditions, unavailable, awaiting response, and not requested.","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The cohort ID"},{"in":"query","name":"modality","schema":{"type":"string","enum":["group_coaching","one_on_one","workshop"]},"description":"Filter coaches by modality"},{"in":"query","name":"availability_filter","schema":{"type":"string","enum":["all","received","not_received"]},"description":"Filter by availability response status"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search coaches by name or email"},{"in":"query","name":"show_other_cohorts","schema":{"type":"boolean"},"description":"Include cross-cohort session data for conflict detection (MED-454)"}],"responses":{"200":{"description":"Schedule grid data with sessions, coaches, and cell states"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/available-pool":{"get":{"summary":"Get coaches available to add to cohort","description":"Returns coaches not already in this cohort who can be added to the potential pool","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"query","name":"page","schema":{"type":"integer"}},{"in":"query","name":"limit","schema":{"type":"integer"}},{"in":"query","name":"status","schema":{"type":"string"},"description":"Filter by coach profile status (e.g., activated, onboarding)"},{"in":"query","name":"search","schema":{"type":"string"},"description":"Search by name or email"},{"in":"query","name":"region","schema":{"type":"string"},"description":"Filter by region (e.g., US - West, US - East, Europe)"},{"in":"query","name":"timezone","schema":{"type":"string"},"description":"Filter by timezone (e.g., America/Los_Angeles)"},{"in":"query","name":"program_id","schema":{"type":"string","format":"uuid"},"description":"Filter by coaches who have worked in cohorts for this program"},{"in":"query","name":"coach_type","schema":{"type":"string","enum":["medley","partner"]},"description":"Filter by coach type (medley or partner)"}],"responses":{"200":{"description":"List of available coaches"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/{coachUserId}":{"delete":{"summary":"Remove a coach from the cohort","description":"Soft-deletes the coach from the cohort's pool","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Coach removed from cohort"},"404":{"description":"Cohort or coach not found"}}}},"/api/cohorts/{cohortId}/coaches/availability-request":{"post":{"summary":"Send availability request to coaches","description":"Sends availability request emails to selected coaches","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["coach_user_ids","modalities","time_slot_ids"],"properties":{"coach_user_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"modalities":{"type":"array","items":{"type":"string"}},"time_slot_ids":{"type":"array","items":{"type":"string"}},"response_deadline":{"type":"string","format":"date"},"cohort_context":{"type":"string"},"email_subject":{"type":"string"},"email_message":{"type":"string"}}}}}},"responses":{"200":{"description":"Availability requests sent"},"400":{"description":"Invalid request"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/availability-request/test-email":{"post":{"summary":"Send test availability request email","description":"Sends a test email to the current user for preview","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email_subject":{"type":"string"},"email_message":{"type":"string"},"response_deadline":{"type":"string","format":"date-time","description":"Optional CM-selected response deadline; formatted long-form so the test email matches a real send"}}}}}},"responses":{"200":{"description":"Test email sent"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/{coachUserId}/detail":{"get":{"summary":"Get coach detail for modal","description":"Returns coach profile, availability response, and performance data for the Coach Detail modal","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The cohort ID"},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The coach's user ID"}],"responses":{"200":{"description":"Coach detail data","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string"},"email":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"phone":{"type":"string"},"avatar_url":{"type":"string"},"coach_profile_id":{"type":"string"},"status":{"type":"string"},"coaching_specializations":{"type":"string"},"location":{"type":"string"},"home_base_timezone":{"type":"string"},"timezone_preference":{"type":"string"},"internal_notes":{"type":"string"},"availability_status":{"type":"string"},"availability_percent":{"type":"number"},"satisfaction_rating":{"type":"number"},"total_reviews":{"type":"number"},"session_counts":{"type":"object"},"group_assignments":{"type":"array"},"availability_response":{"type":"object"}}}}}},"404":{"description":"Cohort or coach not found"}}}},"/api/cohorts/{cohortId}/coaches/{coachUserId}/past-cohorts":{"get":{"summary":"Get coach's past cohorts","description":"Returns list of past cohorts where this coach has participated (excludes current cohort)","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The current cohort ID (will be excluded from results)"},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"},"description":"The coach's user ID"}],"responses":{"200":{"description":"List of past cohorts","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"cohort_id":{"type":"string"},"cohort_name":{"type":"string"},"partner_name":{"type":"string"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date"},"group_count":{"type":"number"},"satisfaction_rating":{"type":"number"},"total_sessions":{"type":"number"}}}}}}},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/announcement-recipients":{"get":{"summary":"Get coaches eligible for announcements","description":"Returns list of coaches with group assignments for the announcement modal","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of coaches eligible for announcements"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/announcements":{"post":{"summary":"Send assignment announcements to coaches","description":"Sends announcement emails to selected coaches and marks them as announced","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["coach_user_ids"],"properties":{"coach_user_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"email_subject":{"type":"string"},"email_body":{"type":"string"}}}}}},"responses":{"200":{"description":"Announcements sent"},"400":{"description":"Invalid request"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/announcements/preview":{"post":{"summary":"Preview coach announcement email with resolved merge tags","description":"Resolves merge tags in the email template using real cohort and coach data. Returns the resolved subject, body, and any unresolvable tags.","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email_subject","email_body","coach_user_id"],"properties":{"email_subject":{"type":"string"},"email_body":{"type":"string"},"coach_user_id":{"type":"string","format":"uuid"}}}}}},"responses":{"200":{"description":"Resolved email preview"},"400":{"description":"Missing required fields"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/announcements/test":{"post":{"summary":"Send a test coach announcement email","description":"Sends a test email to the current user's address with [TEST] prefix. Uses real cohort/coach data for merge tag resolution. No side effects on email history or contract status.","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email_subject","email_body"],"properties":{"email_subject":{"type":"string"},"email_body":{"type":"string"},"coach_user_id":{"type":"string","format":"uuid","description":"Optional specific coach to resolve merge tags against"}}}}}},"responses":{"200":{"description":"Test email sent successfully"},"400":{"description":"Missing required fields or no coaches available"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/{coachUserId}/contract-signed":{"post":{"summary":"Mark coach contract as signed","description":"Updates the coach's contract status to signed","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}},{"in":"path","name":"coachUserId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Contract marked as signed"},"400":{"description":"Coach not in announced status"},"404":{"description":"Cohort or coach not found"}}}},"/api/cohorts/{cohortId}/coaches/callout-data":{"get":{"summary":"Get callout data for Groups view","description":"Returns data to determine which callout to show in Groups view","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Callout data"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/upload/template":{"get":{"summary":"Download partner coach CSV template","description":"Returns a CSV file with the expected column headers for the partner coach upload.","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"CSV template","content":{"text/csv":{"schema":{"type":"string"}}}}}}},"/api/cohorts/{cohortId}/coaches/upload/preview":{"post":{"summary":"Preview partner coach CSV upload","description":"Validates CSV rows, classifies duplicates, and returns counts + per-row results without persisting changes.","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mappings","csvData"],"properties":{"mappings":{"type":"array","items":{"type":"object","properties":{"csvColumn":{"type":"string"},"targetField":{"type":"string","nullable":true,"enum":["first_name","last_name","email","phone","city","state_region","country","timezone"]}}}},"csvData":{"type":"array","items":{"type":"array","items":{"type":"string"}}}}}}}},"responses":{"200":{"description":"Preview results"},"400":{"description":"Missing required mappings or cohort has no partner"},"404":{"description":"Cohort not found"}}}},"/api/cohorts/{cohortId}/coaches/upload":{"post":{"summary":"Execute partner coach CSV upload","description":"Creates new partner coaches, associates existing Medley coaches, and adds all processed coaches to the cohort's potential coach list.","tags":["Cohort Coaches"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["mappings","csvData"],"properties":{"mappings":{"type":"array"},"csvData":{"type":"array"}}}}}},"responses":{"200":{"description":"Import result"},"400":{"description":"Validation error"},"404":{"description":"Cohort not found"}}}},"/api/cohorts":{"post":{"summary":"Create a new cohort","description":"Creates a cohort with program, modalities, sessions, and configuration","tags":["Cohorts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["partner_id","name","modalities"],"properties":{"partner_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"disabled_email_recipients":{"type":"array","items":{"type":"string","enum":["participants","coaches","partners"]},"description":"Recipient types with disabled automated emails"}}}}}},"responses":{"201":{"description":"Cohort created successfully"},"400":{"description":"Invalid request"},"409":{"description":"Cohort code or name already exists"}}}},"/api/cohorts/{id}":{"put":{"summary":"Update a cohort","description":"Updates cohort fields including email disable settings","tags":["Cohorts"],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"disabled_email_recipients":{"type":"array","items":{"type":"string","enum":["participants","coaches","partners"]},"description":"Recipient types with disabled automated emails"}}}}}},"responses":{"200":{"description":"Cohort updated successfully"},"400":{"description":"Invalid request"},"404":{"description":"Cohort not found"},"409":{"description":"Cohort code or name already exists"}}}},"/cohorts/{cohortId}/sessions/one-on-one/eligible-coaches":{"get":{"summary":"Get coaches eligible for 1:1 assignment","description":"Returns coaches with confirmed 1:1 availability for this cohort (one_on_one in confirmed_modalities). E18-2.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of eligible coaches"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/one-on-one/unenrolled-participants":{"get":{"summary":"Get participants not yet enrolled in 1:1 coaching","description":"Returns cohort participants who have no one_on_one_session records. Used by the \"+ Add 1:1\" modal. E18-2.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"List of unenrolled participants"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/one-on-one/participants":{"post":{"summary":"Add participants to 1:1 coaching","description":"Creates the configured number of unscheduled session slots for each selected participant. Auto-assigns group coach if applicable. E18-2.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["participant_user_ids"],"properties":{"participant_user_ids":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Sessions created"},"400":{"description":"Missing modality or invalid input"},"404":{"description":"Cohort not found"}}}},"/cohorts/{cohortId}/sessions/one-on-one/participants/{participantUserId}/coach":{"put":{"summary":"Assign or change coach for a participant's 1:1 sessions","description":"Updates coach_id on all unscheduled 1:1 sessions for the participant. Scheduled/completed sessions are not affected. E18-2.","tags":["Sessions"],"parameters":[{"in":"path","name":"cohortId","required":true,"schema":{"type":"string"}},{"in":"path","name":"participantUserId","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["coach_user_id"],"properties":{"coach_user_id":{"type":"string","nullable":true,"description":"Coach user ID to assign, or null to unassign"}}}}}},"responses":{"200":{"description":"Coach assignment updated"},"400":{"description":"Invalid input"},"404":{"description":"Cohort not found"}}}},"/api/webhooks/resend":{"post":{"summary":"Handle Resend email webhooks","description":"Receives and processes webhook events from Resend for email tracking","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResendWebhookPayload"}}}},"responses":{"200":{"description":"Webhook processed successfully"},"400":{"description":"Invalid webhook payload"},"401":{"description":"Invalid webhook signature"},"409":{"description":"Webhook event already processed"}}}},"/api/webhooks/paperform":{"post":{"summary":"Handle Paperform form submissions","description":"Receives webhook events from Paperform for form submissions.\nValidates signature, stores payload, and queues for async processing.\nConfigure this URL in Paperform: https://paperform.co/help/articles/how-to-use-webhooks/\n","tags":["Webhooks"],"parameters":[{"in":"query","name":"secret","schema":{"type":"string"},"description":"Optional webhook secret for authentication"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["event","submission_id","form_id","data"],"properties":{"event":{"type":"string","example":"form.submission.created"},"submission_id":{"type":"string","example":"sub_abc123xyz"},"form_id":{"type":"string","example":"frm_xyz789"},"created_at":{"type":"string","format":"date-time"},"data":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"title":{"type":"string"},"value":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]},"type":{"type":"string"}}}}}}}}},"responses":{"200":{"description":"Webhook received and queued for processing","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"message":{"type":"string"},"event_id":{"type":"string","format":"uuid"}}}}}}}},"400":{"description":"Invalid webhook payload"},"401":{"description":"Invalid webhook signature or secret"},"409":{"description":"Duplicate submission (already processed)"}}}},"/api/webhooks/health":{"get":{"summary":"Webhook endpoint health check","description":"Simple health check for webhook endpoint availability","tags":["Webhooks"],"responses":{"200":{"description":"Webhook endpoint is healthy"}}}},"/api/webhooks/email-sent":{"post":{"summary":"Log a sent email (internal use)","description":"Called by communications service to log emails after sending via Resend","tags":["Webhooks"],"security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["resend_id","email_type","from_email","to_email","subject"],"properties":{"resend_id":{"type":"string"},"email_type":{"type":"string"},"from_email":{"type":"string"},"to_email":{"type":"string"},"subject":{"type":"string"},"user_id":{"type":"string","format":"uuid"},"metadata":{"type":"object"}}}}}},"responses":{"201":{"description":"Email logged successfully"},"400":{"description":"Invalid request"},"401":{"description":"Invalid API key"}}}},"/api/webhooks/google-calendar":{"post":{"summary":"Handle Google Calendar push notifications","description":"Receives push notifications from Google Calendar when watched events change.\nUsed for RSVP tracking - when attendees accept/decline invites, Google sends\na notification here, and we fetch updated attendee responses.\n\nNote: Google sends headers, not body data. The headers include:\n- X-Goog-Channel-ID: Your channel ID\n- X-Goog-Resource-ID: Google's resource ID\n- X-Goog-Resource-State: \"sync\" (initial), \"exists\" (change), or \"not_exists\" (deleted)\n- X-Goog-Channel-Token: Your verification token (if set)\n","tags":["Webhooks"],"responses":{"200":{"description":"Webhook processed successfully"},"401":{"description":"Invalid webhook token"}}}},"/api/webhooks/google-calendar/status":{"get":{"summary":"Get Google Calendar webhook status (admin diagnostic)","description":"Returns the current watch channel status and calendar service configuration","tags":["Webhooks"],"responses":{"200":{"description":"Webhook status"}}}},"/api/webhooks/google-calendar/force-renew":{"post":{"summary":"Force re-register Google Calendar watch channel (admin diagnostic)","description":"Stops all existing channels and registers a new one","tags":["Webhooks"],"responses":{"200":{"description":"Channel re-registered"}}}},"/api/webhooks/google-calendar/sync-session/{sessionId}":{"post":{"summary":"Manually sync RSVPs for a specific session (admin diagnostic)","description":"Fetches attendee responses from Google Calendar and updates session_attendance","tags":["Webhooks"],"parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"RSVP sync completed"},"404":{"description":"Session not found or no calendar event ID"}}}}},"tags":[{"name":"Auth","description":"Authentication endpoints"},{"name":"Bill.com Integration","description":"Bill.com connection management, vendor mapping, and payment scheduling"},{"name":"Calendly","description":"Calendly OAuth integration and scheduling endpoints"},{"name":"Coach Active Session","description":"Coach Active Session screen endpoints (MED-792 / Story 2.3a). Backs the\nActive Session screen that loads when a coach clicks \"Start Session — Group N\"\non the Session Prep page (MED-793). Endpoints are coach-only and\ngated by the `live_session_delivery` feature flag (MED-886).\n\n**Decision A2 (auto-story 2026-05-14):** AC3 push-to-participants is\ndeferred. Coach state changes persist immediately and emit a Node\ndomain event on `sessionDomainEvents`; participant push receivers\nwire in with MED-804.\n\n**Decision B1 (auto-story 2026-05-14):** AC7 AI disclosure gating uses\n`modality.ai_features_enabled` alone. The group-level toggle from\nMED-876 is treated as implicitly true until that story lands.\n"},{"name":"Coaches","description":"Coach management endpoints"},{"name":"Coach Portal","description":"Coach-facing endpoints for viewing and responding to availability requests"},{"name":"Coach Post-Session","description":"Coach Post-Session screen endpoints (MED-794 / Story 2.6a). Backs the\nfull-page screen that loads when a coach clicks End Session on the\nActive Session screen (MED-792 AC6). Coach-only, gated by the\n`live_session_delivery` feature flag.\n\nComposite bundle endpoint plus a single mutation for the new Log\nCommitments storage. Attendance, engagement, and coach feedback\nflow through the existing `Coach Portal` endpoints.\n"},{"name":"Coach Session Prep","description":"Coach Session Prep flow (MED-793 / Story 2.5a). Coach-only endpoints\nbacking the prep page accessible from the SGOS dashboard and the\nCoach Cohort Detail Sessions Tab. Extends the shared\nSessionAgendaAccordion (MED-784) with coach-mode features:\nLeader Notes (WYSIWYG, scoped All Groups OR per-group), Mark As\nReviewed (toggle), RSVP confirmation, \"Before you start\" checklist,\nand a print-ready Prep Sheet PDF.\n\nAI features (suggestions per step, AI Listening) are deferred to\nMED-876 / Story 2.5b and intentionally absent from this endpoint set.\n"},{"name":"Cohort Payments","description":"Per-cohort Bill.com payment scheduling and management"},{"name":"Cohort Coaches","description":"Manage coaches within a cohort's potential and assigned pools"},{"name":"Cohort Compensation","description":"Per-cohort compensation configuration"},{"name":"Cohort Events","description":"Cohort-wide event management (immersion, orientation, huddles, wrap-up)"},{"name":"MatchingSessions","description":"SGOS ↔ matching-server integration endpoints (E23)"},{"name":"Cohorts","description":"Cohort management endpoints"},{"name":"DataLibrary","description":"Data Library endpoints for multi-cohort participant views"},{"name":"Exports","description":"Data export endpoints for downloading table data as CSV"},{"name":"Feature Flags","description":"Feature flag endpoints for runtime feature toggling"},{"name":"Feedback","description":"In-app feedback submission endpoints"},{"name":"Feedback Surveys","description":"Configure feedback surveys (post-session, midpoint, endpoint) for cohorts"},{"name":"Frameworks","description":"Global AI frameworks glossary management (internal admin only)"},{"name":"Groups","description":"Cohort group management endpoints"},{"name":"Health","description":"Health check endpoints"},{"name":"Intake","description":"Intake form management endpoints"},{"name":"Job Titles","description":"Job title management for task assignments"},{"name":"PaperformDeletions","description":"Surfacing of Paperform submission deletions detected by the daily sweep (MED-902/MED-903)"},{"name":"Participant Active Session","description":"Participant Active Session screen endpoints (MED-805 / Story E3.3).\nPowers the immersive, read-only participant view of a live session:\nleader pill, AI listening indicator, slide content, private WYSIWYG\nnotes (AC #8-#10, private per IV3), and the conditional commitment\ncapture area (AC #12-#14, IV4). Endpoints are gated by the\n`live_session_delivery` feature flag (MED-886).\n\n**Decision #1 (auto-story 2026-05-28):** Leader resolution mirrors\n`WaitingRoomService.buildLeader` — the cohort coach is the named\nleader regardless of modality. For `peer_led` modalities the\n`role_label` flips to `PEER LEADER` but the `user_id` stays on the\ncoach (no peer-leader designation infrastructure exists yet).\n\n**Decision #2 (auto-story 2026-05-28):** `commitment_section_active`\nis derived server-side by case-insensitive substring match\n(`/commitment/i`) on the current section's title. When no section is\nactive or the title doesn't match, the flag is `false`.\n\n**Decision #3 (auto-story 2026-05-28):** Flyway v200 introduces\n`session_participant_notes` (per-participant private notes) and\nadds `authored_by_user_id` to `session_participant_commitments` so\nparticipant-self-authored rows pass the new CHECK constraint.\n"},{"name":"Participant Portal","description":"Participant-facing endpoints for viewing program progress and sessions"},{"name":"Participant Post-Session","description":"Participant Post-Session screen endpoints (MED-806 / Story E3.4).\nBacks the structured post-session flow (Leave Feedback → Review Notes →\nFinalize Commitment → Done) for a participant after a group session\nends. Endpoints are gated by the `live_session_delivery` feature flag\n(MED-886).\n\n**Decision #1 (auto-story 2026-05-28):** \"Reflection\" → \"Feedback\"\nrename is page-scoped only — the existing pre-session reflection page\n(MED-803) and its `/reflection` routes are untouched.\n\n**Decision #2 (auto-story 2026-05-28):** New cohort column\n`cohorts.commitment_tracking_enabled` defaults to `false`. Step 3\n(Finalize Commitment) is hidden until a CM (or QA) flips it to true\nfor a given cohort. CM-side toggle UI is a future story.\n\n**Decision #3 (auto-story 2026-05-28):** Feedback submission is\none-way in Phase 1 (AC5). Subsequent draft writes on a submitted\nrow return 409 FEEDBACK_LOCKED.\n"},{"name":"Participants","description":"Cohort participant management"},{"name":"Partners","description":"Partner management endpoints"},{"name":"Partner Contacts","description":"Partner contact management endpoints"},{"name":"Peer Leader Post-Session","description":"Peer Leader Post-Session screen endpoints (MED-909 / E4.6, MED-817\nfoundation). Backs the full-page screen that loads when a peer\nleader clicks End Session on the Active Session screen. Peer-leader-\nonly, gated by the `live_session_delivery` feature flag (MED-886).\n\nMirrors the coach post-session bundle pattern (MED-794) but writes\nto `peer_leader_session_feedback` and `peer_leader_engagement_ratings`\n(migration v218) to keep coach paths undisturbed.\n"},{"name":"Peer Leader RSVP","description":"Endpoints used by the peer leader prep flow (E4.2 / MED-814) to record\nthe assigned leader's RSVP decision for an upcoming peer-led session.\nGated by the `live_session_delivery` feature flag.\n"},{"name":"Peer Leader Session Prep","description":"Peer Leader Pre-Session Prep flow (MED-814 / Story E4.2). Mirrors the\nthree-step coach prep pattern (MED-793) but adapted for peer leaders\nfacilitating from the participant portal. Step 1 is the participant\npre-session reflection (MED-803), Step 2 renders authored session\ncontent via the shared MED-976 read-side service, Step 3 is the\npreview / sandbox from MED-873. Gated by `live_session_delivery`\n(MED-886) and restricted to `coaching_sub_type='peer_led'` cohorts.\n"},{"name":"Pre-Session Reflection","description":"Participant pre-session reflection endpoints (MED-803 / Story E3.1). Backs\nthe participant-facing page that collects responses to CM-configured\nreflection questions before a group session. Participant-only,\ngated by the `live_session_delivery` feature flag.\n"},{"name":"Programs","description":"Program management endpoints"},{"name":"Rate Card","description":"Global compensation rate card management (admin only)"},{"name":"Resources","description":"Resource management endpoints"},{"name":"Resource Categories","description":"Resource category endpoints"},{"name":"Session Agenda Authoring","description":"Structured authoring of session content (MED-975 / E1.3d). The CM\nbuilds the session's agenda directly in the editor — naming sections,\nsetting durations, associating slides, and writing facilitator\nprompts — instead of importing a Google Doc as in MED-784. Content\nis keyed at the (cohort, modality='group_coaching', session_number)\nlevel so one authoring covers all groups for that session number.\n\nCoach prep / live session rendering of this content is e1.3h\n(MED-976) and is NOT in scope here.\n"},{"name":"SessionNumberConfig","description":"Per-cohort session-number level configuration — drives the \"View by Session\" toggle (MED-702). Group Coaching topics are free text; 1:1 Coaching uses session_type propagation (see PUT .../one-on-one-type)."},{"name":"Sessions","description":"Cohort session management for coaching events"},{"name":"Task Phases","description":"Task phase administration for organizing templates"},{"name":"Tasks","description":"Task management for cohort operations"},{"name":"Task Sub-Groups","description":"Task sub-group administration for organizing templates within phases"},{"name":"TimeSlots","description":"Cohort time slot management for group coaching sessions"},{"name":"Topics","description":"Session topic management for coaching themes"},{"name":"Users","description":"User management endpoints"},{"name":"Waiting Room","description":"Participant waiting-room endpoint (MED-804 / Story E3.2). Returns the\ncomposite payload for the participant view of the waiting room:\nsession header + leader, AI features flag, group roster with computed\npresence states, and the participant's reflection status (so the\norientation bullet can render contextually). Polled at ~4s cadence by\nthe frontend; each call also upserts the caller's presence heartbeat.\n\nParticipant-only, gated by the `live_session_delivery` feature flag.\n"},{"name":"Webhooks","description":"External webhook endpoints for third-party integrations"}]}