{"openapi":"3.1.0","info":{"title":"Jas Run Coach API","version":"0.3.0","description":"Private read-only running and training-plan API for Jas. Bearer-token authentication resolves each request to exactly one manually managed profile."},"servers":[{"url":"https://jas.til3s.co.uk"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"AthleteMetadata":{"type":"object","properties":{"id":{"type":"integer"},"profile_slug":{"type":"string"},"display_name":{"type":["string","null"]},"is_primary":{"type":"boolean"},"strava_athlete_id_available":{"type":"boolean"}}},"TrainingPlan":{"type":"object","properties":{"id":{"type":"string"},"planSlug":{"type":"string"},"planName":{"type":"string"},"versionNumber":{"type":"integer"},"source":{"type":["string","null"]},"notes":{"type":["string","null"]},"startsOn":{"type":"string","format":"date"},"endsOn":{"type":["string","null"],"format":"date"},"isActive":{"type":"boolean"},"importedAt":{"type":"string","format":"date-time"}}},"PlannedWorkout":{"type":"object","properties":{"id":{"type":"string"},"planVersionId":{"type":"string"},"date":{"type":"string","format":"date"},"planned_date":{"type":"string","format":"date"},"sport":{"type":"string"},"workoutType":{"type":["string","null"]},"workout_type":{"type":["string","null"]},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"purpose":{"type":["string","null"]},"plannedDistanceKm":{"type":["number","null"]},"planned_distance_km":{"type":["number","null"]},"plannedDurationMinutes":{"type":["number","null"]},"planned_duration_minutes":{"type":["number","null"]},"intensity":{"type":["string","null"]},"target_intensity":{"type":["string","null"]},"weekCommencing":{"type":["string","null"],"format":"date"},"week_commencing":{"type":["string","null"],"format":"date"},"phase":{"type":["string","null"]},"runDay":{"type":["string","null"]},"run_day":{"type":["string","null"]},"terrain":{"type":["string","null"]},"notes":{"type":["string","null"]},"spreadsheetNotes":{"type":["string","null"]},"spreadsheet_notes":{"type":["string","null"]},"spreadsheetDone":{"type":["string","null"]},"spreadsheet_done":{"type":["string","null"]},"status":{"type":["string","null"]},"linkedRunId":{"type":["integer","null"]},"linked_run_id":{"type":["integer","null"]}}},"Run":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":["string","null"]},"date":{"type":["string","null"],"format":"date-time"},"localDate":{"type":["string","null"]},"distanceKm":{"type":["number","null"]},"movingTimeMinutes":{"type":["number","null"]},"elapsedTimeMinutes":{"type":["number","null"]},"paceMinPerKm":{"type":["number","null"]},"elevationGainMeters":{"type":["number","null"]},"averageHeartRate":{"type":["number","null"]},"maxHeartRate":{"type":["number","null"]},"has_hr_data":{"type":"boolean"},"averageCadence":{"type":["number","null"]},"averageSpeedMetersPerSecond":{"type":["number","null"]},"sufferScore":{"type":["number","null"]}}},"Summary":{"type":"object","properties":{"days":{"type":"integer"},"runCount":{"type":"integer"},"totalDistanceKm":{"type":"number"},"totalMovingTimeMinutes":{"type":"number"},"totalElevationGainMeters":{"type":"number"},"averageDistanceKm":{"type":"number"},"averagePaceMinPerKm":{"type":["number","null"]},"averageHeartRate":{"type":["number","null"]}}},"PlanRealityItem":{"type":"object","properties":{"status":{"type":"string","enum":["completed","moved","modified","missed","upcoming","ambiguous","unmatched_run"]},"plannedWorkout":{"anyOf":[{"$ref":"#/components/schemas/PlannedWorkout"},{"type":"null"}]},"run":{"anyOf":[{"$ref":"#/components/schemas/Run"},{"type":"null"}]},"dateDeltaDays":{"type":["integer","null"]},"distanceDeltaPercent":{"type":["number","null"]},"tracking":{"anyOf":[{"$ref":"#/components/schemas/SpreadsheetTrackingContext"},{"type":"null"}]},"notes":{"type":"array","items":{"type":"string"}}}},"SpreadsheetTrackingContext":{"type":"object","properties":{"spreadsheetDone":{"type":["string","null"]},"spreadsheet_done":{"type":["string","null"]},"doneHint":{"type":"string","enum":["done","not_done","blank","unknown"]},"done_hint":{"type":"string","enum":["done","not_done","blank","unknown"]},"hintedDistanceKm":{"type":["number","null"]},"hinted_distance_km":{"type":["number","null"]},"mismatchFlags":{"type":"array","items":{"type":"string"}},"mismatch_flags":{"type":"array","items":{"type":"string"}}}},"PlanRealitySummary":{"type":"object","properties":{"completed":{"type":"integer"},"moved":{"type":"integer"},"modified":{"type":"integer"},"missed":{"type":"integer"},"upcoming":{"type":"integer"},"ambiguous":{"type":"integer"},"unmatched_run":{"type":"integer"},"plannedPast":{"type":"integer"},"adherenceRate":{"type":["number","null"]},"coachingFrame":{"type":"string"}}},"PlanAdherence":{"type":"object","properties":{"days":{"type":"integer"},"plan":{"anyOf":[{"$ref":"#/components/schemas/TrainingPlan"},{"type":"null"}]},"summary":{"$ref":"#/components/schemas/PlanRealitySummary"},"plan_vs_actual":{"type":"array","items":{"$ref":"#/components/schemas/PlanRealityItem"}}}},"CoachWeeklySummary":{"type":"object","properties":{"week_start":{"type":"string","format":"date"},"week_end":{"type":"string","format":"date"},"phase":{"type":["string","null"]},"planned_run_count":{"type":["integer","null"]},"completed_run_count":{"type":["integer","null"]},"moved_run_count":{"type":["integer","null"]},"modified_run_count":{"type":["integer","null"]},"missed_run_count":{"type":["integer","null"]},"ambiguous_run_count":{"type":["integer","null"]},"unmatched_run_count":{"type":["integer","null"]},"planned_distance_km":{"type":["number","null"]},"actual_distance_km":{"type":["number","null"]},"long_run_planned_km":{"type":["number","null"]},"long_run_actual_km":{"type":["number","null"]},"adherence_status":{"type":"string","enum":["good","mixed","poor","unknown"]},"load_risk":{"type":"string","enum":["green","amber","red","unknown"]},"coaching_verdict":{"type":["string","null"]},"recommendation":{"type":["string","null"]},"data_limitations":{"type":"array","items":{"type":"string"}},"generated_at":{"type":"string","format":"date-time"},"is_stale":{"type":"boolean"},"source_latest_updated_at":{"type":["string","null"],"format":"date-time"}}},"SyncStatus":{"type":"object","properties":{"ok":{"type":"boolean"},"athlete":{"anyOf":[{"$ref":"#/components/schemas/AthleteMetadata"},{"type":"null"}]},"latest_run":{"anyOf":[{"type":"object","properties":{"id":{"type":"integer"},"start_date":{"type":["string","null"],"format":"date-time"},"distance_km":{"type":["number","null"]},"name":{"type":["string","null"]}}},{"type":"null"}]},"latest_sync":{"type":"object","properties":{"synced_at":{"type":["string","null"],"format":"date-time"},"hours_since_sync":{"type":["number","null"]},"source":{"type":["string","null"],"enum":["athlete_sync_status","run_updated_at","strava_token_updated_at",null]},"status":{"type":["string","null"]}}},"coverage":{"type":"object","properties":{"runs_synced_7d":{"type":"integer"},"runs_synced_30d":{"type":"integer"}}},"staleness":{"type":"string","enum":["fresh","aging","stale","unknown"]},"warnings":{"type":"array","items":{"type":"string"}}}},"SeasonContext":{"type":"object","properties":{"current_goal":{"type":["string","null"]},"current_goal_date":{"type":["string","null"],"format":"date"},"next_goal":{"type":["string","null"]},"next_goal_date":{"type":["string","null"],"format":"date"},"phase":{"type":"string","enum":["marathon_taper","marathon_recovery","ultra_base_or_build","ultra_taper","post_ultra_recovery","unknown"]},"days_to_current_goal":{"type":["integer","null"]},"days_to_next_goal":{"type":["integer","null"]}}},"CompactCoachWeeklySummary":{"type":"object","properties":{"week_start":{"type":"string","format":"date"},"week_end":{"type":"string","format":"date"},"phase":{"type":["string","null"]},"runs":{"type":"object","properties":{"planned":{"type":["integer","null"]},"completed":{"type":["integer","null"]},"moved":{"type":["integer","null"]},"modified":{"type":["integer","null"]},"missed":{"type":["integer","null"]},"ambiguous":{"type":["integer","null"]},"unmatched":{"type":["integer","null"]}}},"distance_km":{"type":"object","properties":{"planned":{"type":["number","null"]},"actual":{"type":["number","null"]},"long_run_planned":{"type":["number","null"]},"long_run_actual":{"type":["number","null"]}}},"adherence_status":{"type":"string","enum":["good","mixed","poor","unknown"]},"load_risk":{"type":"string","enum":["green","amber","red","unknown"]},"coaching_verdict":{"type":["string","null"]},"recommendation":{"type":["string","null"]},"data_limitations":{"type":"array","items":{"type":"string"}},"generated_at":{"type":"string","format":"date-time"},"is_stale":{"type":"boolean"},"source_latest_updated_at":{"type":["string","null"],"format":"date-time"}}},"CompactPlannedWorkout":{"type":"object","properties":{"date":{"type":"string","format":"date"},"sport":{"type":"string"},"type":{"type":["string","null"]},"title":{"type":["string","null"]},"purpose":{"type":["string","null"]},"distance_km":{"type":["number","null"]},"duration_minutes":{"type":["number","null"]},"intensity":{"type":["string","null"]},"phase":{"type":["string","null"]},"terrain":{"type":["string","null"]}}},"CoachContextLite":{"type":"object","properties":{"athlete":{"anyOf":[{"$ref":"#/components/schemas/AthleteMetadata"},{"type":"null"}]},"coaching_profile":{"type":"object"},"weekly_summaries":{"type":"object","properties":{"current":{"anyOf":[{"$ref":"#/components/schemas/CompactCoachWeeklySummary"},{"type":"null"}]},"previous":{"anyOf":[{"$ref":"#/components/schemas/CompactCoachWeeklySummary"},{"type":"null"}]}}},"latest_run":{"anyOf":[{"type":"object","properties":{"id":{"type":"integer"},"date":{"type":["string","null"],"format":"date-time"},"name":{"type":["string","null"]},"distance_km":{"type":["number","null"]},"moving_time_minutes":{"type":["number","null"]},"pace_min_per_km":{"type":["number","null"]},"elevation_gain_meters":{"type":["number","null"]},"average_heart_rate":{"type":["number","null"]},"has_heart_rate":{"type":"boolean"}}},{"type":"null"}]},"upcoming_workouts":{"type":"object","properties":{"days":{"type":"integer","enum":[14]},"items":{"type":"array","items":{"$ref":"#/components/schemas/CompactPlannedWorkout"}}}},"current_week_status":{"type":"object","properties":{"week_start":{"type":"string","format":"date"},"week_end":{"type":"string","format":"date"},"completed_runs":{"type":"integer"},"completed_distance_km":{"type":"number"},"planned_runs":{"type":"integer"},"planned_distance_km":{"type":"number"},"remaining_runs":{"type":"integer"},"next_run_date":{"type":["string","null"],"format":"date"}}},"freshness":{"type":"object"},"data_limitations":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","properties":{"error":{"type":"string"}}}}},"security":[{"bearerAuth":[]}],"paths":{"/api/plan/active":{"get":{"operationId":"getActivePlan","summary":"Get the active training plan and planned workouts.","description":"Detailed drill-down only. Do not call for routine coaching context; getCoachContextLite already includes the relevant upcoming workouts.","responses":{"200":{"description":"Active plan and workouts.","content":{"application/json":{"schema":{"type":"object","properties":{"plan":{"anyOf":[{"$ref":"#/components/schemas/TrainingPlan"},{"type":"null"}]},"workouts":{"type":"array","items":{"$ref":"#/components/schemas/PlannedWorkout"}}}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/plan/upcoming":{"get":{"operationId":"getUpcomingPlan","summary":"Get upcoming planned workouts.","description":"Optional drill-down. Do not call alongside getCoachContextLite unless the user explicitly requests a separate plan view.","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":90,"default":14}}],"responses":{"200":{"description":"Upcoming planned workouts.","content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer"},"plan":{"anyOf":[{"$ref":"#/components/schemas/TrainingPlan"},{"type":"null"}]},"workouts":{"type":"array","items":{"$ref":"#/components/schemas/PlannedWorkout"}}}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/plan/week":{"get":{"operationId":"getPlanWeek","summary":"Get this week's planned workouts.","description":"Optional drill-down. The normal current-week status is already included in getCoachContextLite.","responses":{"200":{"description":"Current week of planned workouts.","content":{"application/json":{"schema":{"type":"object","properties":{"weekStart":{"type":"string","format":"date-time"},"weekEnd":{"type":"string","format":"date-time"},"plan":{"anyOf":[{"$ref":"#/components/schemas/TrainingPlan"},{"type":"null"}]},"workouts":{"type":"array","items":{"$ref":"#/components/schemas/PlannedWorkout"}}}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/plan/adherence":{"get":{"operationId":"getPlanAdherence","summary":"Get plan-vs-actual workout matching.","description":"Detailed drill-down for an explicit adherence question. Never call by default or in parallel with getCoachContextLite.","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":365,"default":28}}],"responses":{"200":{"description":"Plan adherence context.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlanAdherence"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/sync/status":{"get":{"operationId":"getSyncStatus","summary":"Get private Strava sync freshness and latest-run status.","description":"Optional freshness drill-down. getCoachContextLite already includes the freshness indicators needed for routine coaching.","responses":{"200":{"description":"Sync freshness status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncStatus"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/coach/context-lite":{"get":{"operationId":"getCoachContextLite","summary":"Get the compact baseline context for a coaching conversation.","description":"Use once at the start of a coaching conversation. Includes recent summaries, latest run, upcoming workouts, current-week status, and freshness. Use other actions only when the user explicitly requests a detailed drill-down.","responses":{"200":{"description":"GPT-safe coaching context briefing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoachContextLite"}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/coach/weekly-summary":{"get":{"operationId":"getCoachWeeklySummary","summary":"Get a persisted coach weekly summary.","description":"Historical-week drill-down only. Current and previous summaries are already included in getCoachContextLite.","parameters":[{"name":"week_start","in":"query","required":false,"schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Persisted coach weekly summary.","content":{"application/json":{"schema":{"type":"object","properties":{"week_start":{"type":"string","format":"date"},"week_end":{"type":"string","format":"date"},"summary":{"anyOf":[{"$ref":"#/components/schemas/CoachWeeklySummary"},{"type":"null"}]}}}}}},"401":{"description":"Missing or invalid bearer token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}