openapi: 3.1.0
info:
  title: Endure API
  version: 1.0.0
  description: REST API for endurance athlete training data. Readiness scores, activities, performance metrics, training plans, and workouts.
  license:
    name: Proprietary
  contact:
    name: Endure Labs
    url: https://endurelabs.app

servers:
  - url: https://endurelabs.app/api/v1
    description: Production

security:
  - bearerAuth: []

tags:
  - name: Status
    description: Health check endpoints
  - name: Athlete
    description: Athlete profile and settings
  - name: Activities
    description: Training activities and streams
  - name: Readiness
    description: Daily readiness scores and recommendations
  - name: PMC
    description: Performance Management Chart (CTL/ATL/TSB)
  - name: Zones
    description: Training zone configuration
  - name: Plans
    description: Training plans
  - name: Workouts
    description: Workout library
  - name: Integrations
    description: Connected services
  - name: Export
    description: Data export

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "API key authentication. Keys are prefixed with `endure_live_`. Pass via Authorization header: `Bearer endure_live_...`"

  headers:
    X-RateLimit-Limit:
      description: Maximum requests allowed in the current window
      schema:
        type: integer
        example: 100
    X-RateLimit-Remaining:
      description: Requests remaining in the current window
      schema:
        type: integer
        example: 97
    X-RateLimit-Reset:
      description: Unix timestamp in milliseconds when the rate limit window resets
      schema:
        type: integer
        format: int64
        example: 1709251200000
    Retry-After:
      description: Seconds until the rate limit resets (only on 429 responses)
      schema:
        type: integer
        example: 30

  schemas:
    ApiMeta:
      type: object
      required:
        - request_id
        - timestamp
      properties:
        request_id:
          type: string
          format: uuid
          example: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
        timestamp:
          type: string
          format: date-time
          example: "2026-03-11T12:00:00.000Z"

    ApiPagination:
      type: object
      required:
        - has_more
        - cursor
        - limit
      properties:
        has_more:
          type: boolean
          example: true
        cursor:
          type:
            - string
            - "null"
          example: "eyJkYXRlIjoiMjAyNi0wMy0wMSIsImlkIjoiYWJjMTIzIn0="
        limit:
          type: integer
          example: 50

    ApiErrorDetail:
      type: object
      required:
        - field
        - message
      properties:
        field:
          type: string
          example: "ftp"
        message:
          type: string
          example: "Must be between 50 and 600"

    ApiError:
      type: object
      required:
        - error
        - meta
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - UNAUTHORIZED
                - FORBIDDEN
                - NOT_FOUND
                - VALIDATION_ERROR
                - RATE_LIMITED
                - SCOPE_INSUFFICIENT
                - INTERNAL_ERROR
              example: "VALIDATION_ERROR"
            message:
              type: string
              example: "Invalid request body"
            details:
              type: array
              items:
                $ref: "#/components/schemas/ApiErrorDetail"
        meta:
          $ref: "#/components/schemas/ApiMeta"

    StatusResponse:
      type: object
      required:
        - status
        - version
        - database
      properties:
        status:
          type: string
          enum:
            - operational
            - degraded
          example: "operational"
        version:
          type: string
          example: "1.0.0"
        database:
          type: string
          enum:
            - healthy
            - unhealthy
          example: "healthy"

    PublicAthlete:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          format: uuid
          example: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
        name:
          type:
            - string
            - "null"
          example: "Jane Doe"
        ftp:
          type:
            - integer
            - "null"
          example: 250
        lthr:
          type:
            - integer
            - "null"
          example: 165
        max_hr:
          type:
            - integer
            - "null"
          example: 190
        resting_hr:
          type:
            - integer
            - "null"
          example: 48
        weight_kg:
          type:
            - number
            - "null"
          example: 72.5
        height_cm:
          type:
            - number
            - "null"
          example: 178.0
        primary_sport:
          type:
            - string
            - "null"
          example: "cycling"
        primary_goal:
          type:
            - string
            - "null"
          example: "Build endurance for a century ride"
        zone_model:
          type:
            - string
            - "null"
          example: "coggan"
        weekly_hours_available:
          type:
            - number
            - "null"
          example: 12
        timezone:
          type:
            - string
            - "null"
          example: "America/New_York"
        created_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2025-06-01T10:00:00.000Z"
        updated_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2026-03-10T15:30:00.000Z"

    UpdateAthleteRequest:
      type: object
      minProperties: 1
      properties:
        ftp:
          type: integer
          minimum: 50
          maximum: 600
          example: 260
        lthr:
          type: integer
          minimum: 80
          maximum: 220
          example: 168
        max_hr:
          type: integer
          minimum: 100
          maximum: 250
          example: 192
        resting_hr:
          type: integer
          minimum: 25
          maximum: 120
          example: 46
        weight_kg:
          type: number
          minimum: 30
          maximum: 200
          example: 71.0
        primary_goal:
          type: string
          maxLength: 200
          example: "Complete an Ironman 70.3"
        zone_model:
          type: string
          maxLength: 50
          example: "coggan"
        weekly_hours_available:
          type: number
          minimum: 1
          maximum: 40
          example: 14

    PublicActivitySummary:
      type: object
      required:
        - id
        - date
        - source
      properties:
        id:
          type: string
          format: uuid
          example: "c3d4e5f6-a1b2-7890-cdef-1234567890ab"
        date:
          type: string
          format: date
          example: "2026-03-10"
        name:
          type:
            - string
            - "null"
          example: "Morning Ride"
        type:
          type:
            - string
            - "null"
          example: "ride"
        sport_type:
          type:
            - string
            - "null"
          example: "cycling"
        source:
          type: string
          example: "strava"
        duration_seconds:
          type:
            - integer
            - "null"
          example: 5400
        distance_meters:
          type:
            - number
            - "null"
          example: 48200.5
        tss:
          type:
            - number
            - "null"
          example: 85.3
        avg_power:
          type:
            - number
            - "null"
          example: 195.0
        normalized_power:
          type:
            - number
            - "null"
          example: 210.0
        max_power:
          type:
            - number
            - "null"
          example: 620.0
        intensity_factor:
          type:
            - number
            - "null"
          example: 0.84
        avg_hr:
          type:
            - integer
            - "null"
          example: 145
        max_hr:
          type:
            - integer
            - "null"
          example: 172
        avg_cadence:
          type:
            - number
            - "null"
          example: 88.0
        total_elevation_gain_m:
          type:
            - number
            - "null"
          example: 850.0
        rpe:
          type:
            - number
            - "null"
          example: 6.5
        feeling:
          type:
            - string
            - "null"
          example: "strong"
        was_planned:
          type:
            - boolean
            - "null"
          example: true
        compliance_pct:
          type:
            - number
            - "null"
          example: 92.0
        compliance_grade:
          type:
            - string
            - "null"
          example: "A"
        created_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2026-03-10T14:00:00.000Z"

    PublicActivityDetail:
      allOf:
        - $ref: "#/components/schemas/PublicActivitySummary"
        - type: object
          properties:
            start_time:
              type:
                - string
                - "null"
              format: date-time
              example: "2026-03-10T07:30:00.000Z"
            start_lat:
              type:
                - number
                - "null"
              example: 39.9612
            start_lng:
              type:
                - number
                - "null"
              example: -82.9988
            end_lat:
              type:
                - number
                - "null"
              example: 39.9701
            end_lng:
              type:
                - number
                - "null"
              example: -82.9875
            polyline:
              type:
                - string
                - "null"
              example: "a~l~Fjk~uOwHfE"
            planned_tss:
              type:
                - number
                - "null"
              example: 80.0
            planned_steps:
              type:
                - string
                - "null"
              example: "Warm up 15 min, 3x10 at tempo, cool down"
            workout_intent:
              type:
                - string
                - "null"
              example: "tempo"
            compliance_detail:
              type:
                - string
                - "null"
              example: "Hit all intervals within target power"
            zone_snapshot:
              type:
                - string
                - "null"
              example: "{\"z1\":120,\"z2\":155,\"z3\":175}"
            zone1_time:
              type:
                - number
                - "null"
              example: 1200
            zone2_time:
              type:
                - number
                - "null"
              example: 1800
            zone3_time:
              type:
                - number
                - "null"
              example: 900
            zone4_time:
              type:
                - number
                - "null"
              example: 600
            zone5_time:
              type:
                - number
                - "null"
              example: 300
            zone6_time:
              type:
                - number
                - "null"
              example: 60
            power_1s:
              type:
                - number
                - "null"
              example: 850.0
            power_5s:
              type:
                - number
                - "null"
              example: 720.0
            power_1m:
              type:
                - number
                - "null"
              example: 420.0
            power_5m:
              type:
                - number
                - "null"
              example: 310.0
            power_20m:
              type:
                - number
                - "null"
              example: 255.0
            power_60m:
              type:
                - number
                - "null"
              example: 230.0

    CreateActivityRequest:
      type: object
      required:
        - date
        - name
        - type
      properties:
        date:
          type: string
          format: date
          example: "2026-03-11"
        name:
          type: string
          minLength: 1
          maxLength: 200
          example: "Lunch Run"
        type:
          type: string
          minLength: 1
          maxLength: 50
          example: "run"
        duration_seconds:
          type: integer
          minimum: 0
          example: 3600
        distance_meters:
          type: number
          minimum: 0
          example: 10000.0
        tss:
          type: number
          minimum: 0
          example: 65.0
        avg_power:
          type: number
          minimum: 0
          example: 180.0
        avg_hr:
          type: integer
          minimum: 0
          example: 148
        rpe:
          type: number
          minimum: 1
          maximum: 10
          example: 7.0
        feeling:
          type: string
          maxLength: 50
          example: "good"

    UpdateActivityRequest:
      type: object
      minProperties: 1
      properties:
        rpe:
          type: number
          minimum: 1
          maximum: 10
          example: 7.5
        feeling:
          type: string
          maxLength: 50
          example: "tired"
        notes:
          type: string
          maxLength: 2000
          example: "Legs felt heavy after yesterday's intervals"

    ActivityStreams:
      type: object
      required:
        - activity_id
        - channels
      properties:
        activity_id:
          type: string
          format: uuid
          example: "c3d4e5f6-a1b2-7890-cdef-1234567890ab"
        sample_rate_hz:
          type:
            - number
            - "null"
          example: 1.0
        total_samples:
          type:
            - integer
            - "null"
          example: 5400
        stream_duration_seconds:
          type:
            - number
            - "null"
          example: 5400.0
        channels:
          type: object
          properties:
            power:
              type: array
              items:
                type: number
              example: [180, 195, 210, 205, 198]
            hr:
              type: array
              items:
                type: number
              example: [130, 135, 142, 148, 145]
            cadence:
              type: array
              items:
                type: number
              example: [85, 88, 90, 87, 86]
            speed:
              type: array
              items:
                type: number
              example: [8.5, 9.0, 9.2, 8.8, 8.6]
            altitude:
              type: array
              items:
                type: number
              example: [250, 252, 255, 258, 260]
            pace:
              type: array
              items:
                type: number
              example: [300, 295, 290, 298, 302]

    PublicReadiness:
      type: object
      required:
        - date
      properties:
        date:
          type: string
          format: date
          example: "2026-03-11"
        readiness_score:
          type:
            - number
            - "null"
          example: 78.5
        recommendation:
          type:
            - string
            - "null"
          enum:
            - go
            - caution
            - rest
            - null
          example: "go"
        recovery_score:
          type:
            - number
            - "null"
          example: 82.0
        sleep_hours:
          type:
            - number
            - "null"
          example: 7.5
        sleep_quality:
          type:
            - number
            - "null"
          example: 85.0
        hrv:
          type:
            - number
            - "null"
          example: 55.0
        resting_hr:
          type:
            - number
            - "null"
          example: 48.0
        mood_score:
          type:
            - number
            - "null"
          example: 8.0
        motivation_level:
          type:
            - number
            - "null"
          example: 7.0
        gates:
          type:
            - object
            - "null"
          properties:
            sleep:
              type:
                - boolean
                - "null"
              example: true
            energy:
              type:
                - boolean
                - "null"
              example: true
            stress:
              type:
                - boolean
                - "null"
              example: false
            musculoskeletal:
              type:
                - boolean
                - "null"
              example: true
            nutrition:
              type:
                - boolean
                - "null"
              example: true
            autonomic:
              type:
                - boolean
                - "null"
              example: true
            mental_health:
              type:
                - boolean
                - "null"
              example: true
        ctl:
          type:
            - number
            - "null"
          example: 65.0
        atl:
          type:
            - number
            - "null"
          example: 72.0
        tsb:
          type:
            - number
            - "null"
          example: -7.0
        is_yesterday_fallback:
          type: boolean
          example: false

    PublicPmcPoint:
      type: object
      required:
        - date
      properties:
        date:
          type: string
          format: date
          example: "2026-03-10"
        ctl:
          type:
            - number
            - "null"
          example: 65.0
        atl:
          type:
            - number
            - "null"
          example: 72.0
        tsb:
          type:
            - number
            - "null"
          example: -7.0

    PublicZone:
      type: object
      required:
        - zone
        - name
        - min
        - max
      properties:
        zone:
          type: integer
          example: 3
        name:
          type: string
          example: "Tempo"
        min:
          type: number
          example: 210
        max:
          type: number
          example: 250

    PublicZoneConfig:
      type: object
      required:
        - power_zones
        - hr_zones
      properties:
        zone_model:
          type:
            - string
            - "null"
          example: "coggan"
        ftp:
          type:
            - integer
            - "null"
          example: 250
        lthr:
          type:
            - integer
            - "null"
          example: 165
        max_hr:
          type:
            - integer
            - "null"
          example: 190
        power_zones:
          type: array
          items:
            $ref: "#/components/schemas/PublicZone"
          example:
            - zone: 1
              name: "Active Recovery"
              min: 0
              max: 137
            - zone: 2
              name: "Endurance"
              min: 138
              max: 187
            - zone: 3
              name: "Tempo"
              min: 188
              max: 225
            - zone: 4
              name: "Threshold"
              min: 226
              max: 262
            - zone: 5
              name: "VO2max"
              min: 263
              max: 300
            - zone: 6
              name: "Anaerobic"
              min: 301
              max: 9999
        hr_zones:
          type: array
          items:
            $ref: "#/components/schemas/PublicZone"
          example:
            - zone: 1
              name: "Recovery"
              min: 0
              max: 120
            - zone: 2
              name: "Aerobic"
              min: 121
              max: 145
            - zone: 3
              name: "Tempo"
              min: 146
              max: 158
            - zone: 4
              name: "Threshold"
              min: 159
              max: 170
            - zone: 5
              name: "VO2max"
              min: 171
              max: 190

    PublicPlanSummary:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          format: uuid
          example: "d4e5f6a1-b2c3-7890-def0-1234567890cd"
        name:
          type:
            - string
            - "null"
          example: "Spring Century Build"
        methodology:
          type:
            - string
            - "null"
          example: "polarized"
        start_date:
          type:
            - string
            - "null"
          format: date
          example: "2026-01-15"
        end_date:
          type:
            - string
            - "null"
          format: date
          example: "2026-06-01"
        status:
          type:
            - string
            - "null"
          example: "active"
        weekly_hours_target:
          type:
            - number
            - "null"
          example: 12.0
        experience_level:
          type:
            - string
            - "null"
          example: "intermediate"
        created_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2026-01-10T08:00:00.000Z"

    PublicPlanDetail:
      allOf:
        - $ref: "#/components/schemas/PublicPlanSummary"
        - type: object
          properties:
            primary_limiter:
              type:
                - string
                - "null"
              example: "endurance"
            adaptation_enabled:
              type:
                - boolean
                - "null"
              example: true

    PublicWorkoutSummary:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          format: uuid
          example: "e5f6a1b2-c3d4-7890-ef01-234567890def"
        name:
          type:
            - string
            - "null"
          example: "THR 2x20 Steady"
        sport_type:
          type:
            - string
            - "null"
          example: "cycling"
        workout_type:
          type:
            - string
            - "null"
          example: "threshold"
        description:
          type:
            - string
            - "null"
          example: "Two 20-minute threshold intervals with 5 min recovery"
        estimated_duration_minutes:
          type:
            - number
            - "null"
          example: 60.0
        estimated_tss:
          type:
            - number
            - "null"
          example: 75.0
        tags:
          type:
            - array
            - "null"
          items:
            type: string
          example: ["threshold", "indoor", "structured"]
        is_favorite:
          type:
            - boolean
            - "null"
          example: false
        usage_count:
          type:
            - integer
            - "null"
          example: 12
        created_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2025-08-15T10:00:00.000Z"

    PublicWorkoutDetail:
      allOf:
        - $ref: "#/components/schemas/PublicWorkoutSummary"
        - type: object
          properties:
            blocks:
              type:
                - array
                - "null"
              items:
                type: object
              example:
                - type: "warmup"
                  duration_seconds: 600
                  target_power_pct: 55
                - type: "interval"
                  duration_seconds: 1200
                  target_power_pct: 95
                  repeats: 2
                  rest_seconds: 300
                - type: "cooldown"
                  duration_seconds: 600
                  target_power_pct: 50
            equipment_needed:
              type:
                - array
                - "null"
              items:
                type: string
              example: ["smart trainer", "power meter"]
            estimated_if:
              type:
                - number
                - "null"
              example: 0.88
            target_muscles:
              type:
                - array
                - "null"
              items:
                type: string
              example: ["quadriceps", "glutes"]

    PublicIntegration:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          format: uuid
          example: "f6a1b2c3-d4e5-7890-0123-456789abcdef"
        provider:
          type:
            - string
            - "null"
          example: "strava"
        last_sync_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2026-03-11T06:00:00.000Z"
        needs_reauth:
          type:
            - boolean
            - "null"
          example: false
        sync_error:
          type:
            - string
            - "null"
          example: null
        created_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2025-05-20T12:00:00.000Z"

    PublicExportJob:
      type: object
      required:
        - id
      properties:
        id:
          type: string
          format: uuid
          example: "a1b2c3d4-5678-90ab-cdef-1234567890ab"
        status:
          type:
            - string
            - "null"
          enum:
            - pending
            - processing
            - completed
            - failed
            - null
          example: "pending"
        download_url:
          type:
            - string
            - "null"
          format: uri
          example: null
        expires_at:
          type:
            - string
            - "null"
          format: date-time
          example: null
        created_at:
          type:
            - string
            - "null"
          format: date-time
          example: "2026-03-11T12:00:00.000Z"
        error_message:
          type:
            - string
            - "null"
          example: null

  responses:
    Unauthorized:
      description: Missing or invalid API key
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error:
              code: "UNAUTHORIZED"
              message: "Missing or invalid API key"
            meta:
              request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              timestamp: "2026-03-11T12:00:00.000Z"
    Forbidden:
      description: Insufficient permissions or scope
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error:
              code: "SCOPE_INSUFFICIENT"
              message: "API key does not have the required scope"
            meta:
              request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              timestamp: "2026-03-11T12:00:00.000Z"
    NotFound:
      description: Resource not found
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error:
              code: "NOT_FOUND"
              message: "Resource not found"
            meta:
              request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              timestamp: "2026-03-11T12:00:00.000Z"
    ValidationError:
      description: Invalid request parameters or body
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error:
              code: "VALIDATION_ERROR"
              message: "Invalid request body"
              details:
                - field: "ftp"
                  message: "Must be between 50 and 600"
            meta:
              request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              timestamp: "2026-03-11T12:00:00.000Z"
    RateLimited:
      description: Rate limit exceeded
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
        Retry-After:
          $ref: "#/components/headers/Retry-After"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error:
              code: "RATE_LIMITED"
              message: "Rate limit exceeded. Retry after 30 seconds."
            meta:
              request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              timestamp: "2026-03-11T12:00:00.000Z"
    InternalError:
      description: Internal server error
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
          example:
            error:
              code: "INTERNAL_ERROR"
              message: "An unexpected error occurred"
            meta:
              request_id: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
              timestamp: "2026-03-11T12:00:00.000Z"

  parameters:
    ActivityId:
      name: id
      in: path
      required: true
      description: Activity UUID
      schema:
        type: string
        format: uuid
    PlanId:
      name: id
      in: path
      required: true
      description: Plan UUID
      schema:
        type: string
        format: uuid
    WorkoutId:
      name: id
      in: path
      required: true
      description: Workout UUID
      schema:
        type: string
        format: uuid
    ExportId:
      name: id
      in: path
      required: true
      description: Export job UUID
      schema:
        type: string
        format: uuid
    StartDate:
      name: start
      in: query
      required: true
      description: Start date (inclusive, YYYY-MM-DD)
      schema:
        type: string
        format: date
        example: "2026-01-01"
    EndDate:
      name: end
      in: query
      required: true
      description: End date (inclusive, YYYY-MM-DD)
      schema:
        type: string
        format: date
        example: "2026-03-11"

paths:
  /status:
    get:
      operationId: getStatus
      summary: Health check
      description: Returns API status, version, and database health. No authentication required.
      tags:
        - Status
      security: []
      responses:
        "200":
          description: API status
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/StatusResponse"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /athlete:
    get:
      operationId: getAthlete
      summary: Get athlete profile
      description: Returns the authenticated athlete's profile. Requires `athlete:read` scope.
      tags:
        - Athlete
      responses:
        "200":
          description: Athlete profile
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicAthlete"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      operationId: updateAthlete
      summary: Update athlete profile
      description: Updates the authenticated athlete's profile fields. Requires `athlete:write` scope. At least one field must be provided.
      tags:
        - Athlete
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateAthleteRequest"
      responses:
        "200":
          description: Updated athlete profile
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicAthlete"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /activities:
    get:
      operationId: listActivities
      summary: List activities
      description: Returns a paginated list of activities within a date range. Maximum range is 90 days. Requires `activities:read` scope.
      tags:
        - Activities
      parameters:
        - $ref: "#/components/parameters/StartDate"
        - $ref: "#/components/parameters/EndDate"
        - name: type
          in: query
          required: false
          description: Filter by activity type
          schema:
            type: string
            example: "ride"
        - name: limit
          in: query
          required: false
          description: Number of results per page (1-200, default 50)
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
        - name: cursor
          in: query
          required: false
          description: Pagination cursor from a previous response
          schema:
            type: string
      responses:
        "200":
          description: Paginated list of activities
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                  - pagination
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicActivitySummary"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
                  pagination:
                    $ref: "#/components/schemas/ApiPagination"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    post:
      operationId: createActivity
      summary: Create an activity
      description: Creates a new activity record. Requires `activities:write` scope.
      tags:
        - Activities
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateActivityRequest"
      responses:
        "201":
          description: Created activity
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicActivitySummary"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /activities/{id}:
    get:
      operationId: getActivity
      summary: Get activity detail
      description: Returns full detail for a single activity including power peaks and zone times. Requires `activities:read` scope.
      tags:
        - Activities
      parameters:
        - $ref: "#/components/parameters/ActivityId"
      responses:
        "200":
          description: Activity detail
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicActivityDetail"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
    patch:
      operationId: updateActivity
      summary: Update activity metadata
      description: Updates RPE, feeling, or notes on an activity. Requires `activities:write` scope. At least one field must be provided.
      tags:
        - Activities
      parameters:
        - $ref: "#/components/parameters/ActivityId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateActivityRequest"
      responses:
        "200":
          description: Updated activity detail
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicActivityDetail"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /activities/{id}/streams:
    get:
      operationId: getActivityStreams
      summary: Get activity data streams
      description: Returns time-series data streams (power, heart rate, cadence, etc.) for an activity. Requires `activities:read` scope.
      tags:
        - Activities
      parameters:
        - $ref: "#/components/parameters/ActivityId"
        - name: channels
          in: query
          required: false
          description: Comma-separated list of channels to return. If omitted, all available channels are returned.
          schema:
            type: string
            example: "power,hr,cadence"
          explode: false
      responses:
        "200":
          description: Activity data streams
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/ActivityStreams"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /readiness/current:
    get:
      operationId: getCurrentReadiness
      summary: Get current readiness
      description: Returns today's readiness score and recommendation. Falls back to yesterday if today's data is not yet available. Requires `readiness:read` scope.
      tags:
        - Readiness
      responses:
        "200":
          description: Current readiness data
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicReadiness"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /readiness/history:
    get:
      operationId: getReadinessHistory
      summary: Get readiness history
      description: Returns daily readiness scores for a date range. Maximum range is 90 days. Requires `readiness:read` scope.
      tags:
        - Readiness
      parameters:
        - $ref: "#/components/parameters/StartDate"
        - $ref: "#/components/parameters/EndDate"
      responses:
        "200":
          description: Readiness history
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicReadiness"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /pmc:
    get:
      operationId: getPmc
      summary: Get PMC data
      description: Returns Performance Management Chart data (CTL, ATL, TSB) for a date range. Maximum range is 365 days. Requires `pmc:read` scope.
      tags:
        - PMC
      parameters:
        - $ref: "#/components/parameters/StartDate"
        - $ref: "#/components/parameters/EndDate"
      responses:
        "200":
          description: PMC data points
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicPmcPoint"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /zones:
    get:
      operationId: getZones
      summary: Get training zones
      description: Returns the athlete's current training zone configuration for power and heart rate. Requires `zones:read` scope.
      tags:
        - Zones
      responses:
        "200":
          description: Zone configuration
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicZoneConfig"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /plans:
    get:
      operationId: listPlans
      summary: List training plans
      description: Returns all training plans for the authenticated athlete. Requires `plans:read` scope.
      tags:
        - Plans
      responses:
        "200":
          description: List of training plans
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicPlanSummary"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /plans/{id}:
    get:
      operationId: getPlan
      summary: Get plan detail
      description: Returns full detail for a single training plan. Requires `plans:read` scope.
      tags:
        - Plans
      parameters:
        - $ref: "#/components/parameters/PlanId"
      responses:
        "200":
          description: Plan detail
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicPlanDetail"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /workouts:
    get:
      operationId: listWorkouts
      summary: List workouts
      description: Returns all workouts in the athlete's library. Requires `workouts:read` scope.
      tags:
        - Workouts
      responses:
        "200":
          description: List of workouts
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicWorkoutSummary"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /workouts/{id}:
    get:
      operationId: getWorkout
      summary: Get workout detail
      description: Returns full detail for a single workout including structured blocks. Requires `workouts:read` scope.
      tags:
        - Workouts
      parameters:
        - $ref: "#/components/parameters/WorkoutId"
      responses:
        "200":
          description: Workout detail
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicWorkoutDetail"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /integrations:
    get:
      operationId: listIntegrations
      summary: List integrations
      description: Returns all connected integrations for the authenticated athlete. Requires `integrations:read` scope.
      tags:
        - Integrations
      responses:
        "200":
          description: List of integrations
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PublicIntegration"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /export:
    post:
      operationId: createExport
      summary: Request data export
      description: Initiates an export of the athlete's data. Returns a job that can be polled for completion. Requires `export:write` scope.
      tags:
        - Export
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Empty object. No parameters required.
            example: {}
      responses:
        "201":
          description: Export job created
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicExportJob"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"

  /export/{id}:
    get:
      operationId: getExport
      summary: Get export job status
      description: Returns the current status of an export job. Poll until status is `completed` or `failed`. Requires `export:write` scope.
      tags:
        - Export
      parameters:
        - $ref: "#/components/parameters/ExportId"
      responses:
        "200":
          description: Export job status
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: "#/components/schemas/PublicExportJob"
                  meta:
                    $ref: "#/components/schemas/ApiMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
