openapi: 3.0.3 info: title: Running Dashboard API description: REST API for the Sub-80 CPH Half Marathon training dashboard. Provides access to Garmin sync, workout management, AI coaching, social features, and more. version: 1.0.0 contact: name: Running Dashboard Support license: name: MIT servers: - url: http://localhost:8765 description: Development server variables: protocol: default: http tags: - name: Authentication description: Login, logout, registration, and session management - name: Profile description: User profile retrieval and updates - name: Data description: Bulk data export, import, and persistence - name: Routes & Laps description: GPS routes and lap splits for activities - name: Garmin Sync description: Trigger and monitor Garmin Connect synchronization - name: Garmin Auth description: Garmin Connect authentication and session management - name: Claude AI description: AI coaching, run analysis, and API key management - name: Social description: Friends, activity feed, and comments - name: Challenges description: Group challenges, leaderboards, and invitations - name: HRV description: Heart Rate Variability history and backfill - name: Admin description: User management and SSL certificate administration - name: Public description: Unauthenticated public endpoints components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: Bearer token authentication required for most endpoints schemas: # Response Wrappers SuccessResponse: type: object properties: ok: type: boolean enum: [true] required: - ok ErrorResponse: type: object properties: error: type: string required: - error # Auth Schemas AuthStatus: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: auth_required: type: boolean multi_user: type: boolean authenticated: type: boolean user_id: type: string nullable: true username: type: string nullable: true display_name: type: string nullable: true tagline: type: string nullable: true email: type: string nullable: true is_admin: type: boolean nullable: true dashboard_title: type: string nullable: true allow_registration: type: boolean LoginRequest: type: object properties: username: type: string password: type: string required: - username - password LoginResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: token: type: string user_id: type: string LogoutResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: logged_out: type: boolean RegisterRequest: type: object properties: username: type: string password: type: string email: type: string format: email nullable: true required: - username - password KeepAliveResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: ts: type: integer description: Unix timestamp # Profile Schemas UserProfile: type: object properties: user_id: type: string username: type: string display_name: type: string email: type: string format: email tagline: type: string is_admin: type: boolean required: - user_id - username UpdateProfileRequest: type: object properties: display_name: type: string nullable: true email: type: string format: email nullable: true tagline: type: string nullable: true age: type: integer nullable: true # Data/DB Schemas Workout: type: object properties: id: type: string name: type: string description: type: string nullable: true date: type: string format: date-time duration: type: number description: Duration in seconds distance: type: number description: Distance in kilometers avg_hr: type: number description: Average heart rate max_hr: type: number description: Maximum heart rate garmin_id: type: string nullable: true type: type: string enum: [run, strength, cross_training, recovery] status: type: string enum: [planned, completed, skipped] BodyComposition: type: object properties: date: type: string format: date weight: type: number body_fat: type: number nullable: true body_water: type: number nullable: true muscle_mass: type: number nullable: true Metrics: type: object properties: date: type: string format: date vo2_max: type: number nullable: true training_load: type: number nullable: true recovery_time: type: number nullable: true Goal: type: object properties: id: type: string name: type: string description: type: string target_value: type: number target_date: type: string format: date category: type: string progress: type: number nullable: true Analysis: type: object properties: garmin_id: type: string date: type: string format: date-time insights: type: array items: type: string recommendations: type: array items: type: string performance_metrics: type: object HRVReading: type: object properties: date: type: string format: date hrv_value: type: number resting_hr: type: number nullable: true recovery_index: type: number nullable: true PendingSyncItem: type: object properties: id: type: string type: type: string data: type: object created_at: type: string format: date-time FullDataExport: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: activities: type: array items: $ref: '#/components/schemas/Workout' upcoming_workouts: type: array items: $ref: '#/components/schemas/Workout' body_composition: type: array items: $ref: '#/components/schemas/BodyComposition' metrics: type: array items: $ref: '#/components/schemas/Metrics' goals: type: array items: $ref: '#/components/schemas/Goal' analyses: type: array items: $ref: '#/components/schemas/Analysis' hrv_readings: type: array items: $ref: '#/components/schemas/HRVReading' pending_sync: type: array items: $ref: '#/components/schemas/PendingSyncItem' SaveWorkoutsRequest: oneOf: - type: array items: $ref: '#/components/schemas/Workout' - type: object properties: workouts: type: array items: $ref: '#/components/schemas/Workout' required: - workouts SaveWorkoutsResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: saved: type: integer SavePendingRequest: oneOf: - type: array items: $ref: '#/components/schemas/PendingSyncItem' - type: object properties: items: type: array items: $ref: '#/components/schemas/PendingSyncItem' required: - items SavePendingResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: saved: type: integer ImportRequest: type: object properties: activities: type: array items: type: object upcoming_workouts: type: array items: type: object body_composition: type: array items: type: object metrics: type: array items: type: object goals: type: array items: type: object RouteRequest: type: object properties: garmin_id: type: string points: type: array items: type: array items: type: number minItems: 2 maxItems: 2 description: "[latitude, longitude]" required: - garmin_id - points RouteResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: cached: type: boolean RouteData: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: route: type: array items: type: array items: type: number minItems: 2 maxItems: 2 SaveGoalsRequest: oneOf: - type: array items: $ref: '#/components/schemas/Goal' - type: object properties: goals: type: array items: $ref: '#/components/schemas/Goal' required: - goals SaveGoalsResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: saved: type: integer SaveAnalysisRequest: allOf: - type: object properties: garmin_id: type: string insights: type: array items: type: string nullable: true recommendations: type: array items: type: string nullable: true performance_metrics: type: object nullable: true required: - garmin_id SaveAnalysisResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: saved: type: string # Routes & Laps Lap: type: object properties: lap_index: type: integer start_time: type: string format: date-time total_time: type: number distance: type: number avg_hr: type: number nullable: true max_hr: type: number nullable: true avg_pace: type: string nullable: true LapsResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: laps: type: array items: $ref: '#/components/schemas/Lap' # Garmin Sync Schemas SyncResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: status: type: string enum: [idle, syncing, done, error] triggered: type: boolean message: type: string nullable: true SyncStatusResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: status: type: string enum: [idle, syncing, done, error] progress: type: number nullable: true description: Percentage (0-100) message: type: string nullable: true data: type: object nullable: true SyncImportRequest: type: object properties: days_back: type: integer minimum: 1 description: Number of days to look back for historical data required: - days_back SyncImportResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: triggered: type: boolean days_back: type: integer message: type: string nullable: true InvalidateResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: invalidated: type: boolean # Garmin Auth Schemas GarminAuthStatus: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: state: type: string enum: [idle, waiting_2fa, authenticating, authenticated, error] message: type: string nullable: true has_tokens: type: boolean GarminStartAuthRequest: type: object properties: email: type: string format: email password: type: string required: - email - password GarminAuthMessage: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: msg: type: string Submit2FARequest: type: object properties: code: type: string description: Two-factor authentication code required: - code GarminProbeResult: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: test_date: type: string format: date-time results: type: object additionalProperties: true PushRequest: type: array items: type: object properties: id: type: string type: type: string data: type: object # Claude AI Schemas ClaudeMessage: type: object properties: role: type: string enum: [user, assistant] content: oneOf: - type: string - type: array items: type: object AnalyseRunsRequest: type: object properties: garmin_ids: type: array items: type: string required: - garmin_ids AnalysisMap: type: object additionalProperties: $ref: '#/components/schemas/Analysis' AnalyseRunsResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: analyses: $ref: '#/components/schemas/AnalysisMap' SaveClaudeKeyRequest: type: object properties: key: type: string required: - key SaveClaudeKeyResponse: $ref: '#/components/schemas/SuccessResponse' # Social Schemas Friend: type: object properties: user_id: type: string username: type: string FriendsResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: friends: type: array items: $ref: '#/components/schemas/Friend' UserSearchResult: allOf: - $ref: '#/components/schemas/Friend' - type: object properties: following: type: boolean UsersSearchResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: users: type: array items: $ref: '#/components/schemas/UserSearchResult' Activity: allOf: - $ref: '#/components/schemas/Workout' - type: object properties: user_id: type: string username: type: string FeedResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: activities: type: array items: $ref: '#/components/schemas/Activity' Comment: type: object properties: id: type: string garmin_id: type: string owner_id: type: string user_id: type: string username: type: string body: type: string created_at: type: string format: date-time CommentsResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: comments: type: array items: $ref: '#/components/schemas/Comment' AddFriendRequest: type: object properties: friend_id: type: string required: - friend_id FriendActionResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: added: type: boolean removed: type: boolean friend_id: type: string AddCommentRequest: type: object properties: garmin_id: type: string owner_id: type: string body: type: string required: - garmin_id - owner_id - body AddCommentResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: comment: $ref: '#/components/schemas/Comment' DeleteCommentResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: deleted: type: boolean # Challenges Schemas Challenge: type: object properties: id: type: string name: type: string type: type: string members: type: integer top3: type: array items: type: object nullable: true ends_at: type: string format: date-time nullable: true ChallengesResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: challenges: type: array items: $ref: '#/components/schemas/Challenge' ChallengeType: type: object properties: key: type: string label: type: string ChallengeTypesResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: types: type: array items: $ref: '#/components/schemas/ChallengeType' LeaderboardEntry: type: object properties: user_id: type: string username: type: string score: type: number LeaderboardResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: leaderboard: type: array items: $ref: '#/components/schemas/LeaderboardEntry' CreateChallengeRequest: type: object properties: name: type: string challenge_type: type: string ends_at: type: string format: date-time nullable: true required: - name - challenge_type CreateChallengeResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: challenge_id: type: string InviteChallengeRequest: type: object properties: challenge_id: type: string invitee_id: type: string required: - challenge_id - invitee_id InviteChallengeResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: invited: type: boolean invitee_id: type: string RespondChallengeRequest: type: object properties: challenge_id: type: string accept: type: boolean required: - challenge_id - accept RespondChallengeResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: updated: type: boolean status: type: string # HRV Schemas HRVHistoryResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: hrv_history: type: array items: $ref: '#/components/schemas/HRVReading' HRVReadingRequest: type: object properties: date: type: string format: date hrv_value: type: number resting_hr: type: number nullable: true recovery_index: type: number nullable: true required: - date - hrv_value HRVReadingResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' HRVBackfillRequest: type: object properties: days_back: type: integer nullable: true description: Number of days to backfill (optional) HRVBackfillResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: started: type: boolean message: type: string HRVBackfillStatusResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: status: type: string progress: type: number nullable: true message: type: string nullable: true # Admin Schemas AdminUser: allOf: - $ref: '#/components/schemas/UserProfile' - type: object properties: created_at: type: string format: date-time AdminUsersResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: users: type: array items: $ref: '#/components/schemas/AdminUser' CreateAdminUserRequest: type: object properties: username: type: string password: type: string is_admin: type: boolean nullable: true required: - username - password CreateAdminUserResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: user_id: type: string username: type: string DeleteAdminUserResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: deleted: type: string SSLCertInfo: type: object properties: issuer: type: string subject: type: string valid_from: type: string format: date-time valid_to: type: string format: date-time thumbprint: type: string SSLStatusResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: ssl_active: type: boolean cert_info: $ref: '#/components/schemas/SSLCertInfo' GenerateSSLRequest: type: object properties: days: type: integer nullable: true description: Certificate validity in days (default varies) GenerateSSLResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: cert_info: $ref: '#/components/schemas/SSLCertInfo' message: type: string UploadSSLRequest: type: object properties: cert_pem: type: string description: PEM-encoded certificate key_pem: type: string description: PEM-encoded private key required: - cert_pem - key_pem UploadSSLResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: cert_info: $ref: '#/components/schemas/SSLCertInfo' message: type: string # Public Stats Schemas PublicStatsResponse: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: km_tracked: type: number users: type: integer workouts_planned: type: integer analyses: type: integer paths: # Authentication Endpoints /api/auth/status: get: tags: - Authentication summary: Get authentication status description: Returns the current authentication status and dashboard configuration. No authentication required. responses: '200': description: Auth status retrieved content: application/json: schema: $ref: '#/components/schemas/AuthStatus' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/auth/login: post: tags: - Authentication summary: Login user description: Authenticates a user with username and password. Sets session cookie and returns JWT token. 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 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/auth/logout: post: tags: - Authentication summary: Logout user description: Revokes the current token and clears the session cookie. security: - bearerAuth: [] responses: '200': description: Logout successful content: application/json: schema: $ref: '#/components/schemas/LogoutResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/auth/register: post: tags: - Authentication summary: Register new user description: Creates a new user account. Only works if ALLOW_REGISTRATION environment variable is true. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RegisterRequest' responses: '200': description: Registration successful content: application/json: schema: $ref: '#/components/schemas/LoginResponse' '400': description: Invalid input or registration disabled content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: User already exists content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/keepalive: get: tags: - Authentication summary: Keep-alive ping description: Returns current server timestamp. Used to maintain session alive. No authentication required. responses: '200': description: Keep-alive successful content: application/json: schema: $ref: '#/components/schemas/KeepAliveResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Profile Endpoints /api/profile: get: tags: - Profile summary: Get user profile description: Returns the current authenticated user's profile information. security: - bearerAuth: [] responses: '200': description: Profile retrieved content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - $ref: '#/components/schemas/UserProfile' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' patch: tags: - Profile summary: Update user profile description: Updates one or more fields of the user's profile. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateProfileRequest' responses: '200': description: Profile updated content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - $ref: '#/components/schemas/UserProfile' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Data/DB Endpoints /api/db/data: get: tags: - Data summary: Export full user data description: Returns complete user data export including activities, workouts, body composition, metrics, goals, analyses, HRV readings, and pending sync queue. security: - bearerAuth: [] responses: '200': description: Data exported content: application/json: schema: $ref: '#/components/schemas/FullDataExport' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/db/workouts: post: tags: - Data summary: Save workouts description: Saves upcoming workouts to the database. Accepts either an array of workouts or an object with a "workouts" key. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SaveWorkoutsRequest' responses: '200': description: Workouts saved content: application/json: schema: $ref: '#/components/schemas/SaveWorkoutsResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/db/pending: post: tags: - Data summary: Save pending sync items description: Saves pending sync queue items to the database. Accepts either an array of items or an object with an "items" key. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SavePendingRequest' responses: '200': description: Pending items saved content: application/json: schema: $ref: '#/components/schemas/SavePendingResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/db/import: post: tags: - Data summary: Bulk import data description: Performs bulk import of data from localStorage snapshot into SQLite database. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ImportRequest' responses: '200': description: Data imported content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: imported: type: integer errors: type: array items: type: string nullable: true '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/db/route: post: tags: - Data summary: Cache GPS route description: Caches GPS route points for an activity. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RouteRequest' responses: '200': description: Route cached content: application/json: schema: $ref: '#/components/schemas/RouteResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/db/goals: post: tags: - Data summary: Save goals description: Saves training goals to the database. Accepts either an array of goals or an object with a "goals" key. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SaveGoalsRequest' responses: '200': description: Goals saved content: application/json: schema: $ref: '#/components/schemas/SaveGoalsResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/db/analyses: post: tags: - Data summary: Save run analysis description: Saves analysis results for a completed run activity. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SaveAnalysisRequest' responses: '200': description: Analysis saved content: application/json: schema: $ref: '#/components/schemas/SaveAnalysisResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Routes & Laps Endpoints /api/route/{activity_id}: get: tags: - Routes & Laps summary: Get activity route description: Returns the GPS route points for a specific activity. security: - bearerAuth: [] parameters: - name: activity_id in: path required: true schema: type: string description: The Garmin activity ID responses: '200': description: Route retrieved content: application/json: schema: $ref: '#/components/schemas/RouteData' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Route not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/laps/{activity_id}: get: tags: - Routes & Laps summary: Get activity laps description: Returns lap data for a specific activity. security: - bearerAuth: [] parameters: - name: activity_id in: path required: true schema: type: string description: The Garmin activity ID responses: '200': description: Laps retrieved content: application/json: schema: $ref: '#/components/schemas/LapsResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Laps not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Garmin Sync Endpoints /api/sync: get: tags: - Garmin Sync summary: Trigger sync (GET) description: Triggers a background synchronization with Garmin. Can also use POST. security: - bearerAuth: [] responses: '200': description: Sync triggered content: application/json: schema: $ref: '#/components/schemas/SyncResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' post: tags: - Garmin Sync summary: Trigger sync (POST) description: Triggers a background synchronization with Garmin. Can also use GET. security: - bearerAuth: [] responses: '200': description: Sync triggered content: application/json: schema: $ref: '#/components/schemas/SyncResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/sync/trigger: get: tags: - Garmin Sync summary: Trigger sync (GET alternative) description: Alternative endpoint to trigger a background synchronization with Garmin. security: - bearerAuth: [] responses: '200': description: Sync triggered content: application/json: schema: $ref: '#/components/schemas/SyncResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' post: tags: - Garmin Sync summary: Trigger sync (POST alternative) description: Alternative endpoint to trigger a background synchronization with Garmin. security: - bearerAuth: [] responses: '200': description: Sync triggered content: application/json: schema: $ref: '#/components/schemas/SyncResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/sync/status: get: tags: - Garmin Sync summary: Poll sync status description: Returns the current status of an ongoing or completed sync operation. When syncing is complete, includes fresh database data. security: - bearerAuth: [] responses: '200': description: Sync status retrieved content: application/json: schema: $ref: '#/components/schemas/SyncStatusResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/sync/import: post: tags: - Garmin Sync summary: Trigger historical import description: Triggers a historical import of activities from Garmin going back a specified number of days. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SyncImportRequest' responses: '200': description: Historical import triggered content: application/json: schema: $ref: '#/components/schemas/SyncImportResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/sync/invalidate: get: tags: - Garmin Sync summary: Invalidate cached browser description: Force-closes the cached browser instance, useful for resetting Garmin authentication state. security: - bearerAuth: [] responses: '200': description: Cache invalidated content: application/json: schema: $ref: '#/components/schemas/InvalidateResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Garmin Auth Endpoints /api/garmin/status: get: tags: - Garmin Auth summary: Get Garmin authentication status description: Returns the current state of Garmin authentication. No authentication required. responses: '200': description: Garmin auth status retrieved content: application/json: schema: $ref: '#/components/schemas/GarminAuthStatus' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/garmin/start-auth: post: tags: - Garmin Auth summary: Start Garmin authentication description: Initiates the Garmin login process with email and password. Will proceed to 2FA if required. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GarminStartAuthRequest' responses: '200': description: Garmin auth started content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: msg: type: string '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/garmin/submit-2fa: post: tags: - Garmin Auth summary: Submit 2FA code description: Submits the two-factor authentication code for Garmin login. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Submit2FARequest' responses: '200': description: 2FA code accepted content: application/json: schema: $ref: '#/components/schemas/GarminAuthMessage' '400': description: Invalid code content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/garmin/logout: post: tags: - Garmin Auth summary: Logout from Garmin description: Clears Garmin authentication tokens and ends the Garmin session. security: - bearerAuth: [] responses: '200': description: Garmin logout successful content: application/json: schema: $ref: '#/components/schemas/GarminAuthMessage' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/garmin/reset: post: tags: - Garmin Auth summary: Reset Garmin authentication description: Completely resets Garmin authentication state, cookies, and tokens. security: - bearerAuth: [] responses: '200': description: Garmin auth reset successful content: application/json: schema: $ref: '#/components/schemas/GarminAuthMessage' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/probe_garmin: get: tags: - Garmin Auth summary: Test Garmin API endpoints description: Tests connectivity and functionality of Garmin API endpoints. security: - bearerAuth: [] responses: '200': description: Probe results content: application/json: schema: $ref: '#/components/schemas/GarminProbeResult' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/push: post: tags: - Garmin Auth summary: Push workout changes to Garmin description: Pushes pending workout changes and modifications to the Garmin calendar/API. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PushRequest' responses: '200': description: Push successful content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: pushed: type: integer failed: type: integer nullable: true errors: type: array items: type: string nullable: true '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/login: post: tags: - Garmin Auth summary: Launch visible Garmin login description: Launches a visible Chrome browser window for interactive Garmin login. Used as an alternative to programmatic auth. security: - bearerAuth: [] responses: '200': description: Browser launched content: application/json: schema: $ref: '#/components/schemas/GarminAuthMessage' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Claude AI Endpoints /api/claude: post: tags: - Claude AI summary: Claude API proxy description: Proxies requests to the Anthropic Claude API. Authentication is not explicitly checked but may be validated by the backend. requestBody: required: true content: application/json: schema: type: object properties: model: type: string messages: type: array items: $ref: '#/components/schemas/ClaudeMessage' max_tokens: type: integer nullable: true responses: '200': description: Claude response content: application/json: schema: type: object additionalProperties: true '400': description: Invalid request content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/analyse/runs: post: tags: - Claude AI summary: Analyse runs with Claude description: Analyzes one or more completed runs using Claude AI to provide insights and recommendations. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AnalyseRunsRequest' responses: '200': description: Analysis complete content: application/json: schema: $ref: '#/components/schemas/AnalyseRunsResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/save_claude_key: post: tags: - Claude AI summary: Save Claude API key description: Saves the Anthropic Claude API key to the backend. No explicit authentication check. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SaveClaudeKeyRequest' responses: '200': description: API key saved content: application/json: schema: $ref: '#/components/schemas/SaveClaudeKeyResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Social Endpoints /api/social/friends: get: tags: - Social summary: Get friends list description: Returns the current user's list of friends. security: - bearerAuth: [] responses: '200': description: Friends list retrieved content: application/json: schema: $ref: '#/components/schemas/FriendsResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/social/users: get: tags: - Social summary: Search users description: Searches for users by username or display name. security: - bearerAuth: [] parameters: - name: q in: query required: true schema: type: string description: Search query responses: '200': description: Users found content: application/json: schema: $ref: '#/components/schemas/UsersSearchResponse' '400': description: Invalid query content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/social/feed: get: tags: - Social summary: Get friend activity feed description: Returns a feed of activities from the user's friends, with pagination. security: - bearerAuth: [] parameters: - name: limit in: query schema: type: integer default: 40 description: Maximum number of activities to return - name: offset in: query schema: type: integer default: 0 description: Number of activities to skip responses: '200': description: Feed retrieved content: application/json: schema: $ref: '#/components/schemas/FeedResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/social/comments/{garmin_id}: get: tags: - Social summary: Get activity comments description: Returns all comments on a specific activity. security: - bearerAuth: [] parameters: - name: garmin_id in: path required: true schema: type: string description: The activity's Garmin ID - name: owner in: query required: true schema: type: string description: The activity owner's user ID responses: '200': description: Comments retrieved content: application/json: schema: $ref: '#/components/schemas/CommentsResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Activity not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/social/friends/add: post: tags: - Social summary: Add friend description: Sends or adds a friend to the current user's friends list. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddFriendRequest' responses: '200': description: Friend added content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: added: type: boolean friend_id: type: string '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/social/friends/remove: post: tags: - Social summary: Remove friend description: Removes a friend from the current user's friends list. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddFriendRequest' responses: '200': description: Friend removed content: application/json: schema: allOf: - $ref: '#/components/schemas/SuccessResponse' - type: object properties: removed: type: boolean friend_id: type: string '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/social/comments/add: post: tags: - Social summary: Add comment to activity description: Adds a comment to an activity. The owner_id refers to the activity owner, not the commenter. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AddCommentRequest' responses: '200': description: Comment added content: application/json: schema: $ref: '#/components/schemas/AddCommentResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/social/comments/delete/{id}: post: tags: - Social summary: Delete comment description: Deletes a comment by its ID. Only the comment author can delete it. security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string description: Comment ID responses: '200': description: Comment deleted content: application/json: schema: $ref: '#/components/schemas/DeleteCommentResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Not authorized to delete comment content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Comment not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Challenges Endpoints /api/challenges: get: tags: - Challenges summary: Get all challenges description: Returns a list of all active challenges the user is member of or can join. security: - bearerAuth: [] responses: '200': description: Challenges retrieved content: application/json: schema: $ref: '#/components/schemas/ChallengesResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/challenges/types: get: tags: - Challenges summary: Get challenge types description: Returns available challenge types that can be created. No explicit authentication required. responses: '200': description: Challenge types retrieved content: application/json: schema: $ref: '#/components/schemas/ChallengeTypesResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/challenges/{id}/leaderboard: get: tags: - Challenges summary: Get challenge leaderboard description: Returns the leaderboard for a specific challenge. security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string description: Challenge ID responses: '200': description: Leaderboard retrieved content: application/json: schema: $ref: '#/components/schemas/LeaderboardResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Challenge not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/challenges/create: post: tags: - Challenges summary: Create new challenge description: Creates a new challenge that the user will be the organizer of. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateChallengeRequest' responses: '200': description: Challenge created content: application/json: schema: $ref: '#/components/schemas/CreateChallengeResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/challenges/invite: post: tags: - Challenges summary: Invite user to challenge description: Invites another user to join a challenge. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/InviteChallengeRequest' responses: '200': description: Invitation sent content: application/json: schema: $ref: '#/components/schemas/InviteChallengeResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Challenge or user not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/challenges/respond: post: tags: - Challenges summary: Respond to challenge invitation description: Accepts or declines an invitation to join a challenge. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RespondChallengeRequest' responses: '200': description: Invitation responded content: application/json: schema: $ref: '#/components/schemas/RespondChallengeResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: Challenge not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # HRV Endpoints /api/hrv/history: get: tags: - HRV summary: Get HRV history description: Returns historical HRV readings for the specified number of days. security: - bearerAuth: [] parameters: - name: days in: query schema: type: integer default: 30 description: Number of days to retrieve (default 30) responses: '200': description: HRV history retrieved content: application/json: schema: $ref: '#/components/schemas/HRVHistoryResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/hrv/reading: post: tags: - HRV summary: Record HRV reading description: Manually records a new HRV reading. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/HRVReadingRequest' responses: '200': description: HRV reading recorded content: application/json: schema: $ref: '#/components/schemas/HRVReadingResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/hrv/backfill: post: tags: - HRV summary: Backfill HRV from Garmin description: Triggers a backfill of historical HRV data from the Garmin account. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/HRVBackfillRequest' responses: '200': description: Backfill started content: application/json: schema: $ref: '#/components/schemas/HRVBackfillResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/hrv/backfill/status: get: tags: - HRV summary: Get HRV backfill status description: Returns the status of an ongoing HRV backfill operation. security: - bearerAuth: [] responses: '200': description: Backfill status retrieved content: application/json: schema: $ref: '#/components/schemas/HRVBackfillStatusResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Admin Endpoints /api/admin/users: get: tags: - Admin summary: List all users description: Returns a list of all users in the system. Admin only. security: - bearerAuth: [] responses: '200': description: Users list retrieved content: application/json: schema: $ref: '#/components/schemas/AdminUsersResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Not authorized (not admin) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' post: tags: - Admin summary: Create new user description: Creates a new user account. Admin only. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateAdminUserRequest' responses: '200': description: User created content: application/json: schema: $ref: '#/components/schemas/CreateAdminUserResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Not authorized (not admin) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: User already exists content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/admin/users/{user_id}: delete: tags: - Admin summary: Delete user description: Deletes a user account and all associated data. Admin only. security: - bearerAuth: [] parameters: - name: user_id in: path required: true schema: type: string description: User ID to delete responses: '200': description: User deleted content: application/json: schema: $ref: '#/components/schemas/DeleteAdminUserResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Not authorized (not admin) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: User not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/admin/ssl/status: get: tags: - Admin summary: Get SSL certificate status description: Returns information about the current SSL certificate and whether SSL is active. Admin only. security: - bearerAuth: [] responses: '200': description: SSL status retrieved content: application/json: schema: $ref: '#/components/schemas/SSLStatusResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Not authorized (not admin) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/admin/ssl/generate: post: tags: - Admin summary: Generate self-signed SSL certificate description: Generates a new self-signed SSL certificate. Admin only. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/GenerateSSLRequest' responses: '200': description: Certificate generated content: application/json: schema: $ref: '#/components/schemas/GenerateSSLResponse' '400': description: Invalid input content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Not authorized (not admin) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/admin/ssl/upload: post: tags: - Admin summary: Upload custom SSL certificate description: Uploads a custom SSL certificate and private key in PEM format. Admin only. security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UploadSSLRequest' responses: '200': description: Certificate uploaded content: application/json: schema: $ref: '#/components/schemas/UploadSSLResponse' '400': description: Invalid certificate or key content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Not authenticated content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '403': description: Not authorized (not admin) content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' # Public Endpoints /api/stats/public: get: tags: - Public summary: Get public statistics description: Returns aggregate statistics about the platform. No authentication required. responses: '200': description: Public stats retrieved content: application/json: schema: $ref: '#/components/schemas/PublicStatsResponse' '500': description: Server error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse'