> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fitsociety.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Save one programme activity occurrence

> Starts, completes, or cancels one non-workout activity from an assigned calendar programme. The path identifies the authored occurrence; retries update that same occurrence. The caller cannot override ownership. Work-block fields require a structured running session and the composite cardio feature flag; legacy totals remain writable while that flag is off. First running logging stores an immutable execution snapshot. Client-supplied completedOnTarget, workBlockEvaluation, resolvedTargetsSnapshot and externalWorkoutRef are rejected. Only the athlete can report a niggle, which sets the profile safety flag before the log save and triggers future intensity suppression afterwards. With the cardio flag enabled, generated-plan execution commits with a transactional plan fence. Pace-led in_progress logs are refused with CARDIO_NIGGLE_ACTIVE while the athlete flag is active; historical completed actuals remain loggable. No pace tolerance or condition correction is invented. An item is executable throughout its available programme week.

Requires the workout_sessions:write scope. This operation maps to /app/v1/workout/performance/activities/:planId/:scheduleItemId/:plannedDate and retains its Workout V2 permission, feature-flag, and resource-scope checks.

The clientId path parameter is resolved inside the company bound to the Public API token when present.



## OpenAPI

````yaml /openapi/public-v1.json put /public/v1/workout/performance/activities/{planId}/{scheduleItemId}/{plannedDate}
openapi: 3.1.0
info:
  title: FITsociety Public API v1
  version: 1.0.0
  description: >-
    Developer Public API endpoints under `/public/v1`. This reference is
    filtered to OAuth/Bearer Public API resources and excludes provider callback
    receivers, storefront routes, public widgets, wishlist routes, and
    access-device validation endpoints.
  contact:
    name: FITsociety Engineering
servers:
  - url: https://api.fitsociety.io
    description: Production
security: []
tags:
  - name: Workout
    description: >-
      Workout V2 libraries, programmes, client plans, calendars, sessions,
      groups, settings and progress. AI operations are excluded.
  - name: OAuth
    description: Public API OAuth endpoints for server-to-server client credentials.
  - name: Health
    description: Public API token health checks.
  - name: Platform
    description: >-
      Inspect Public API client context, capabilities, scopes, and redacted
      audit logs.
  - name: Company Catalog
    description: Read and manage company profile metadata and locations.
  - name: Clients
    description: >-
      Create and manage clients through the Public API using Bearer access
      tokens.
  - name: Coaches
    description: Retrieve coaches for the authenticated company via integrations.
  - name: Calendar Events
    description: Read Public API calendar events.
  - name: Calendar Templates
    description: >-
      Read and manage event types and event templates used by calendar
      availability and bookings.
  - name: Calendar Extensions
    description: >-
      Read recurring bookings, booking requests, calendar tasks, and
      availability closure metadata.
  - name: Availability
    description: Read bookable availability slots and signed availability tokens.
  - name: Availability Management
    description: >-
      Manage coach and location availability templates used to derive bookable
      slots.
  - name: Bookings
    description: Read and manage Public API bookings.
  - name: Finance
    description: >-
      Read invoices, transactions, products, subscriptions, and memberships with
      guarded finance writes.
  - name: Credits
    description: >-
      Read company-wide and client-scoped credit allocations, mutations, and
      guarded credit adjustments.
  - name: Exports
    description: >-
      Create and monitor asynchronous company exports through Public API Bearer
      endpoints.
  - name: Webhooks
    description: >-
      Manage outbound webhook subscriptions and inspect delivery attempts
      through Public API Bearer endpoints.
  - name: Measurements
    description: Read and write client measurement entries.
  - name: Progress Photos
    description: Read client progress photo metadata and short-lived signed media URLs.
  - name: Forms
    description: Read, create, update, and archive company form templates.
  - name: Intakes
    description: Assign intake forms and read client intake assignments and submissions.
  - name: Check-ups
    description: >-
      Schedule and cancel client check-ups and read their status and
      submissions.
  - name: Documents
    description: >-
      Read client document and folder metadata, register external document
      links, update metadata, and archive documents from Public API listings.
      Binary upload, permanent deletion, and company-wide document management
      are not exposed.
  - name: Habits
    description: Read habits and habit entries.
  - name: Goals
    description: Read client goal summaries.
  - name: Conversations
    description: >-
      Read and manage direct and group chat conversations through the Public
      API.
  - name: Reports
    description: Read aggregate attendance, revenue, and retention summaries.
paths:
  /public/v1/workout/performance/activities/{planId}/{scheduleItemId}/{plannedDate}:
    put:
      tags:
        - Workout
      summary: Save one programme activity occurrence
      description: >-
        Starts, completes, or cancels one non-workout activity from an assigned
        calendar programme. The path identifies the authored occurrence; retries
        update that same occurrence. The caller cannot override ownership.
        Work-block fields require a structured running session and the composite
        cardio feature flag; legacy totals remain writable while that flag is
        off. First running logging stores an immutable execution snapshot.
        Client-supplied completedOnTarget, workBlockEvaluation,
        resolvedTargetsSnapshot and externalWorkoutRef are rejected. Only the
        athlete can report a niggle, which sets the profile safety flag before
        the log save and triggers future intensity suppression afterwards. With
        the cardio flag enabled, generated-plan execution commits with a
        transactional plan fence. Pace-led in_progress logs are refused with
        CARDIO_NIGGLE_ACTIVE while the athlete flag is active; historical
        completed actuals remain loggable. No pace tolerance or condition
        correction is invented. An item is executable throughout its available
        programme week.


        Requires the workout_sessions:write scope. This operation maps to
        /app/v1/workout/performance/activities/:planId/:scheduleItemId/:plannedDate
        and retains its Workout V2 permission, feature-flag, and resource-scope
        checks.


        The clientId path parameter is resolved inside the company bound to the
        Public API token when present.
      operationId: >-
        publicWorkoutputPublicV1WorkoutPerformanceActivitiesPlanIdScheduleItemIdPlannedDate
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema:
            type: string
            minLength: 1
            maxLength: 200
            example: booking-create-20260714-001
          description: >-
            Required for Public API write requests. Reusing the same key with
            the same method, path, and body replays the stored successful
            response; reusing it with a different request returns `409
            idempotency.conflict`.
        - in: path
          name: planId
          required: true
          description: Assigned workout plan id.
          schema:
            type: string
            example: 67f1234567890abcdef1234
        - in: path
          name: scheduleItemId
          required: true
          description: Activity schedule item id.
          schema:
            type: string
            example: 67f1234567890abcdef1234
        - in: path
          name: plannedDate
          required: true
          description: Canonical planned occurrence date.
          schema:
            type: string
            format: date
            pattern: ^\d{4}-\d{2}-\d{2}$
            example: '2026-09-08'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: >-
                #/components/schemas/PublicWorkoutSourcePUTAppV1WorkoutPerformanceActivitiesPlanIdScheduleItemIdPlannedDateRequest
      responses:
        '200':
          description: Activity occurrence saved.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PublicApiWriteSuccessResponse'
                  - type: object
                    properties:
                      data:
                        $ref: >-
                          #/components/schemas/PublicWorkoutSourcePUTAppV1WorkoutPerformanceActivitiesPlanIdScheduleItemIdPlannedDateResponse200
              examples:
                success:
                  summary: Successful response
                  value:
                    data:
                      workBlocks:
                        - blockId: 66f7b8b1e13c8d25f4d3d90a
                          stepId: 66f7b8b1e13c8d25f4d3d90a
                          repIndex: 1
                          durationSec: 1
                          distanceM: 1
                          avgPaceSecPerKm: 1
                          avgHrBpm: 1
                      avgHrBpm: 1
                      missedReason: sick
                      conditions:
                        temperatureC: 1
                        windNote: string
                        elevationGainM: 1
                        preFatigueNote: string
                      niggle:
                        note: string
                      source: manual
                      completedOnTarget: true
                      workBlockEvaluation:
                        reasonCodes:
                          - SESSION_NOT_COMPLETED
                        blocks:
                          - blockId: 66f7b8b1e13c8d25f4d3d90a
                            stepId: 66f7b8b1e13c8d25f4d3d90a
                            repIndex: 1
                            onTarget: true
                            reasonCode: string
                      resolvedTargetsSnapshot:
                        source: execution_snapshot
                        paceUnit: s/km
                        sessionType: easy
                        controlMode: pace
                        blocks:
                          - _id: 66f7b8b1e13c8d25f4d3d90a
                            repeat: 1
                            steps:
                              - _id: 66f7b8b1e13c8d25f4d3d90a
                                kind: warmup
                                durationType: time
                                durationValue: 1
                                target:
                                  kind: paceZone
                                  zone: E
                                  basis: lthr
                                  low: 1
                                  high: 1
                                secondaryTarget:
                                  kind: paceZone
                                  zone: E
                                  basis: lthr
                                  low: 1
                                  high: 1
                                targetReasonCode: string
                                secondaryTargetReasonCode: string
                      externalWorkoutRef: string
                      id: 67f1234567890abcdef1234
                      planId: 67f1234567890abcdef1234
                      programmeScheduleItemId: 67f1234567890abcdef1234
                      plannedDate: '2026-09-08'
                      status: completed
                      startedAt: null
                      completedAt: null
                      actualDurationMinutes: 45
                      actualDistanceMeters: 8000
                      notes: Felt smooth.
                      rpe: 7
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
                      idempotency:
                        replayed: false
        '400':
          description: >-
            Invalid ids, status, metrics, assigned-plan type, schedule item, or
            occurrence date.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                invalidRequest:
                  summary: Invalid request
                  value:
                    error:
                      code: 400
                      key: request.invalid
                      message: The request is invalid.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
                idempotencyRequired:
                  summary: Missing Idempotency-Key
                  value:
                    error:
                      code: 400
                      key: idempotency.required
                      message: >-
                        Idempotency-Key header is required for Public API write
                        requests.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
                idempotencyInvalid:
                  summary: Invalid Idempotency-Key
                  value:
                    error:
                      code: 400
                      key: idempotency.invalid
                      message: Idempotency-Key header must be 200 characters or fewer.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
        '401':
          $ref: '#/components/schemas/ErrorResponse'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                missingBearer:
                  summary: Missing Bearer token
                  value:
                    error:
                      code: 401
                      key: auth.missing_bearer
                      message: Authorization Bearer token is required.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
                invalidToken:
                  summary: Invalid or expired token
                  value:
                    error:
                      code: 401
                      key: auth.invalid_token
                      message: The access token is invalid or expired.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
                invalidClient:
                  summary: Inactive or revoked client
                  value:
                    error:
                      code: 401
                      key: auth.invalid_client
                      message: The Public API client is inactive or revoked.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
        '403':
          description: >-
            The caller cannot access the client, or the programme calendar is
            disabled (WORKOUT_V2_PROGRAMME_CALENDAR_DISABLED). New running
            actual fields require the composite cardio flag
            (WORKOUT_V2_CARDIO_BUILDER_DISABLED). Coaches cannot report athlete
            niggles.
          x-errorCodes:
            - WORKOUT_V2_PROGRAMME_CALENDAR_DISABLED
            - WORKOUT_V2_CARDIO_BUILDER_DISABLED
            - CARDIO_NIGGLE_ACTIVE
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                insufficientScopes:
                  summary: Missing required scope
                  value:
                    error:
                      code: 403
                      key: scopes.insufficient
                      message: The access token does not include the required scope.
                      details:
                        requiredScopes:
                          - required:scope
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
        '404':
          description: Assigned workout plan not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                notFound:
                  summary: Resource not found
                  value:
                    error:
                      code: 404
                      key: resource.not_found
                      message: The requested Public API resource was not found.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
        '409':
          description: >-
            The occurrence week is not available (PROGRAMME_WEEK_NOT_AVAILABLE),
            or a concurrent write changed the log (PROGRAMME_ACTIVITY_CHANGED);
            reload before retrying.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                idempotencyConflict:
                  summary: Idempotency-Key conflict
                  value:
                    error:
                      code: 409
                      key: idempotency.conflict
                      message: >-
                        Idempotency-Key was already used with a different
                        request.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
                idempotencyInProgress:
                  summary: Idempotency-Key in progress
                  value:
                    error:
                      code: 409
                      key: idempotency.in_progress
                      message: >-
                        Idempotency-Key is already processing for this Public
                        API client.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
        '422':
          description: Stored performance validation failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                emptyBody:
                  summary: Empty or invalid JSON object body
                  value:
                    error:
                      code: 422
                      key: EMPTY_BODY
                      message: Request body must be a non-empty JSON object.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
        '429':
          $ref: '#/components/schemas/ErrorResponse'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                rateLimitExceeded:
                  summary: Rate limit exceeded
                  value:
                    error:
                      code: 429
                      key: rate_limit.exceeded
                      message: Too many Public API requests. Retry later.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 0
                        resetSeconds: 1
                        retryAfterSeconds: 1
        '500':
          description: Unexpected server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicApiError'
              examples:
                serverInternalError:
                  summary: Unexpected server error
                  value:
                    error:
                      code: 500
                      key: server.internal_error
                      message: An unexpected Public API server error occurred.
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
      security:
        - PublicBearerAuth: []
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: >-
            curl -X PUT
            "https://api.fitsociety.io/public/v1/workout/performance/activities/{planId}/{scheduleItemId}/{plannedDate}"
            \
              -H "Authorization: Bearer <access_token>" \
              -H "Idempotency-Key: <stable_request_key>" \
              -H "Content-Type: application/json" \
              -d '{}'
components:
  schemas:
    PublicWorkoutSourcePUTAppV1WorkoutPerformanceActivitiesPlanIdScheduleItemIdPlannedDateRequest:
      type: object
      required:
        - status
      properties:
        workBlocks:
          type: array
          maxItems: 1000
          items:
            $ref: '#/components/schemas/PublicWorkoutSourceRunningWorkBlockActual'
          description: >-
            Work steps only, identified by persisted block/step ids and a
            one-based repetition index. Recovery and whole-activity averages
            cannot supply work evidence. Omission preserves existing entries; an
            explicit array replaces them.
        avgHrBpm:
          type: number
          exclusiveMinimum: 0
          description: >-
            Whole-activity mean HR, informational only; never compared with a
            work-step band.
        missedReason:
          type: string
          enum:
            - sick
            - away
            - injured
            - interrupted
            - too_hard
            - other
        conditions:
          $ref: '#/components/schemas/PublicWorkoutSourceRunningConditions'
        niggle:
          type: object
          additionalProperties: false
          required:
            - note
          properties:
            note:
              type: string
              maxLength: 2000
          description: >-
            Authenticated athlete only. Sets the profile niggle active before
            saving the log. Clearing requires the dedicated athlete endpoint;
            coaches cannot clear it.
        source:
          type: string
          enum:
            - manual
          description: >-
            Public writes are manual. Consented wearable import is a later
            phase.
        status:
          type: string
          enum:
            - in_progress
            - completed
            - cancelled
          example: completed
        actualDurationMinutes:
          type: number
          minimum: 0
          example: 45
          description: Actual activity duration in minutes.
        actualDistanceMeters:
          type: number
          minimum: 0
          example: 8000
          description: Actual activity distance in meters.
        notes:
          type: string
          example: Felt smooth.
        rpe:
          type: number
          minimum: 1
          maximum: 10
          example: 7
          description: Perceived exertion from 1 through 10.
    PublicApiWriteSuccessResponse:
      type: object
      additionalProperties: false
      required:
        - data
        - meta
      properties:
        data:
          type: object
        meta:
          $ref: '#/components/schemas/PublicApiWriteMeta'
    PublicWorkoutSourcePUTAppV1WorkoutPerformanceActivitiesPlanIdScheduleItemIdPlannedDateResponse200:
      type: object
      properties:
        workBlocks:
          type: array
          maxItems: 1000
          items:
            type: object
            additionalProperties: false
            required:
              - blockId
              - stepId
              - repIndex
            properties:
              blockId:
                type: string
                pattern: ^[a-fA-F0-9]{24}$
              stepId:
                type: string
                pattern: ^[a-fA-F0-9]{24}$
              repIndex:
                type: integer
                minimum: 1
                maximum: 50
              durationSec:
                type: number
                exclusiveMinimum: 0
                description: Measured work-step seconds.
              distanceM:
                type: number
                exclusiveMinimum: 0
                description: Measured work-step metres.
              avgPaceSecPerKm:
                type: number
                exclusiveMinimum: 0
                description: Measured work-step mean pace in seconds/km.
              avgHrBpm:
                type: number
                exclusiveMinimum: 0
                description: Measured work-step mean HR in bpm.
            description: >-
              At least one measured metric is required. Positive finite numbers
              and localized decimal strings are accepted.
          description: >-
            Work steps only, identified by persisted block/step ids and a
            one-based repetition index. Recovery and whole-activity averages
            cannot supply work evidence. Omission preserves existing entries; an
            explicit array replaces them.
        avgHrBpm:
          type: number
          exclusiveMinimum: 0
          description: >-
            Whole-activity mean HR, informational only; never compared with a
            work-step band.
        missedReason:
          type: string
          enum:
            - sick
            - away
            - injured
            - interrupted
            - too_hard
            - other
        conditions:
          type: object
          additionalProperties: false
          properties:
            temperatureC:
              type: number
              description: >-
                Measured temperature in degrees Celsius; no automatic heat
                correction is inferred.
            windNote:
              type: string
              maxLength: 2000
              description: >-
                Recorded wind conditions; an explicit empty string means no wind
                effect reported.
            elevationGainM:
              type: number
              minimum: 0
              description: Recorded elevation gain in metres.
            preFatigueNote:
              type: string
              maxLength: 2000
              description: >-
                Recorded pre-fatigue; an explicit empty string means none
                reported.
        niggle:
          type: object
          additionalProperties: false
          required:
            - note
          properties:
            note:
              type: string
              maxLength: 2000
          description: >-
            Authenticated athlete only. Sets the profile niggle active before
            saving the log. Clearing requires the dedicated athlete endpoint;
            coaches cannot clear it.
        source:
          type: string
          enum:
            - manual
          description: >-
            Public writes are manual. Consented wearable import is a later
            phase.
        completedOnTarget:
          type:
            - boolean
            - 'null'
          readOnly: true
          description: >-
            Server computed from every prescribed work repetition and its
            primary signal and duration. Missing evidence or a single-value pace
            target with unspecified tolerance returns null. Secondary guidance
            and activity averages are never binding.
        workBlockEvaluation:
          type: object
          readOnly: true
          properties:
            reasonCodes:
              type: array
              items:
                type: string
                enum:
                  - SESSION_NOT_COMPLETED
                  - WORK_PRESCRIPTION_MISSING
                  - WORK_BLOCK_MISSING
                  - WORK_TARGET_UNRESOLVED
                  - CONTROL_MODE_MISMATCH
                  - PACE_TOLERANCE_UNSPECIFIED
                  - WORK_METRIC_MISSING
                  - WORK_DURATION_MISSING
            blocks:
              type: array
              items:
                type: object
                properties:
                  blockId:
                    type: string
                  stepId:
                    type: string
                  repIndex:
                    type: integer
                    minimum: 1
                  onTarget:
                    type:
                      - boolean
                      - 'null'
                  reasonCode:
                    type: string
        resolvedTargetsSnapshot:
          type: object
          readOnly: true
          properties:
            source:
              type: string
              enum:
                - execution_snapshot
            paceUnit:
              type: string
              enum:
                - s/km
            sessionType:
              type: string
              enum:
                - easy
                - zone2
                - long_run
                - threshold_continuous
                - threshold_reps
                - interval
                - race_pace
                - time_trial
                - race
            controlMode:
              type: string
              enum:
                - pace
                - hr
            blocks:
              type: array
              items:
                type: object
                properties:
                  _id:
                    type: string
                  repeat:
                    type: integer
                    minimum: 1
                    maximum: 50
                  steps:
                    type: array
                    items:
                      type: object
                      properties:
                        _id:
                          type: string
                        kind:
                          type: string
                          enum:
                            - warmup
                            - work
                            - recovery
                            - rest
                            - cooldown
                        durationType:
                          type: string
                          enum:
                            - time
                            - distance
                            - open
                        durationValue:
                          type:
                            - number
                            - 'null'
                          minimum: 0
                        target:
                          type: object
                          additionalProperties: false
                          required:
                            - kind
                          description: >-
                            Allowed kinds and zones depend on the sport. Zone
                            targets require zone; other targets require ordered
                            low/high bounds. Pace is seconds/km for
                            running/walking, seconds/500m for rowing,
                            seconds/100m for swimming. Percentages are percent
                            values; HR is bpm, power is watts, RPE is 1–10.
                            Numeric strings with dot or comma decimals are
                            accepted.
                          properties:
                            kind:
                              type: string
                              enum:
                                - paceZone
                                - pacePct
                                - pace
                                - hrZone
                                - hrPct
                                - hr
                                - powerZone
                                - powerPct
                                - power
                                - rpe
                            zone:
                              type: string
                              enum:
                                - E
                                - T
                                - I
                                - R
                                - RP
                                - Z1
                                - Z2
                                - Z3
                                - Z4
                                - Z5
                                - Z6
                                - Z7
                                - easy
                                - endurance
                                - threshold
                                - speed
                              description: >-
                                Run pace: E (easy), T (threshold), I (interval),
                                R (repetition), RP (race pace = goal time / goal
                                distance, never derived from T). Running has no
                                HR zones; HR bands are absolute bpm from athlete
                                data. Walking/rowing pace and five-zone HR:
                                Z1–Z5. Bike/row power: Z1–Z7. Swimming pace:
                                easy, endurance, threshold, speed.
                            basis:
                              type: string
                              enum:
                                - lthr
                                - maxHr
                              description: HR zone/percentage targets only.
                            low:
                              type: number
                              minimum: 0
                            high:
                              type: number
                              minimum: 0
                        secondaryTarget:
                          type: object
                          additionalProperties: false
                          required:
                            - kind
                          description: >-
                            Allowed kinds and zones depend on the sport. Zone
                            targets require zone; other targets require ordered
                            low/high bounds. Pace is seconds/km for
                            running/walking, seconds/500m for rowing,
                            seconds/100m for swimming. Percentages are percent
                            values; HR is bpm, power is watts, RPE is 1–10.
                            Numeric strings with dot or comma decimals are
                            accepted.
                          properties:
                            kind:
                              type: string
                              enum:
                                - paceZone
                                - pacePct
                                - pace
                                - hrZone
                                - hrPct
                                - hr
                                - powerZone
                                - powerPct
                                - power
                                - rpe
                            zone:
                              type: string
                              enum:
                                - E
                                - T
                                - I
                                - R
                                - RP
                                - Z1
                                - Z2
                                - Z3
                                - Z4
                                - Z5
                                - Z6
                                - Z7
                                - easy
                                - endurance
                                - threshold
                                - speed
                              description: >-
                                Run pace: E (easy), T (threshold), I (interval),
                                R (repetition), RP (race pace = goal time / goal
                                distance, never derived from T). Running has no
                                HR zones; HR bands are absolute bpm from athlete
                                data. Walking/rowing pace and five-zone HR:
                                Z1–Z5. Bike/row power: Z1–Z7. Swimming pace:
                                easy, endurance, threshold, speed.
                            basis:
                              type: string
                              enum:
                                - lthr
                                - maxHr
                              description: HR zone/percentage targets only.
                            low:
                              type: number
                              minimum: 0
                            high:
                              type: number
                              minimum: 0
                        targetReasonCode:
                          type: string
                        secondaryTargetReasonCode:
                          type: string
          description: >-
            First logging captures the prescribed structure and resolved
            targets. Later profile or schedule edits never replace this
            snapshot. Client writes are rejected.
        externalWorkoutRef:
          type: string
          readOnly: true
          description: >-
            Reserved for a future consented server import; rejected in public
            writes.
        id:
          type: string
          example: 67f1234567890abcdef1234
        planId:
          type: string
          example: 67f1234567890abcdef1234
        programmeScheduleItemId:
          type: string
          example: 67f1234567890abcdef1234
        plannedDate:
          type: string
          format: date
          pattern: ^\d{4}-\d{2}-\d{2}$
          example: '2026-09-08'
        status:
          type: string
          enum:
            - in_progress
            - completed
            - cancelled
          example: completed
        startedAt:
          type:
            - string
            - 'null'
          format: date-time
          example: null
        completedAt:
          type:
            - string
            - 'null'
          format: date-time
          example: null
        actualDurationMinutes:
          type:
            - number
            - 'null'
          minimum: 0
          example: 45
          description: Actual activity duration in minutes.
        actualDistanceMeters:
          type:
            - number
            - 'null'
          minimum: 0
          example: 8000
          description: Actual activity distance in meters.
        notes:
          type: string
          example: Felt smooth.
        rpe:
          type:
            - number
            - 'null'
          minimum: 1
          maximum: 10
          example: 7
      required:
        - id
        - planId
        - programmeScheduleItemId
        - plannedDate
        - status
        - startedAt
        - completedAt
        - actualDurationMinutes
        - actualDistanceMeters
        - notes
        - rpe
    PublicApiError:
      type: object
      additionalProperties: false
      required:
        - error
        - meta
      properties:
        error:
          type: object
          additionalProperties: false
          required:
            - code
            - key
            - message
          properties:
            code:
              type: integer
              example: 401
            key:
              type: string
              example: auth.invalid_token
            message:
              type: string
              example: The access token is invalid.
            details:
              type: object
              additionalProperties: true
        meta:
          $ref: '#/components/schemas/PublicApiMeta'
    ErrorResponse:
      allOf:
        - $ref: '#/components/schemas/StandardResponse'
        - example:
            status: 401
            error: true
            message: MISSING_AUTH
    PublicWorkoutSourceRunningWorkBlockActual:
      type: object
      additionalProperties: false
      required:
        - blockId
        - stepId
        - repIndex
      properties:
        blockId:
          type: string
          pattern: ^[a-fA-F0-9]{24}$
        stepId:
          type: string
          pattern: ^[a-fA-F0-9]{24}$
        repIndex:
          type: integer
          minimum: 1
          maximum: 50
        durationSec:
          type: number
          exclusiveMinimum: 0
          description: Measured work-step seconds.
        distanceM:
          type: number
          exclusiveMinimum: 0
          description: Measured work-step metres.
        avgPaceSecPerKm:
          type: number
          exclusiveMinimum: 0
          description: Measured work-step mean pace in seconds/km.
        avgHrBpm:
          type: number
          exclusiveMinimum: 0
          description: Measured work-step mean HR in bpm.
      description: >-
        At least one measured metric is required. Positive finite numbers and
        localized decimal strings are accepted.
    PublicWorkoutSourceRunningConditions:
      type: object
      additionalProperties: false
      properties:
        temperatureC:
          type: number
          description: >-
            Measured temperature in degrees Celsius; no automatic heat
            correction is inferred.
        windNote:
          type: string
          maxLength: 2000
          description: >-
            Recorded wind conditions; an explicit empty string means no wind
            effect reported.
        elevationGainM:
          type: number
          minimum: 0
          description: Recorded elevation gain in metres.
        preFatigueNote:
          type: string
          maxLength: 2000
          description: Recorded pre-fatigue; an explicit empty string means none reported.
    PublicApiWriteMeta:
      type: object
      additionalProperties: false
      required:
        - requestId
      properties:
        requestId:
          type: string
          description: Stable request correlation id. Mirrors `X-Request-Id` when supplied.
          example: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
        rateLimit:
          $ref: '#/components/schemas/PublicApiRateLimitMeta'
        idempotency:
          type: object
          additionalProperties: false
          properties:
            replayed:
              type: boolean
              description: >-
                True when the response was replayed from a previous request with
                the same `Idempotency-Key`.
              example: true
    PublicApiMeta:
      type: object
      additionalProperties: false
      required:
        - requestId
      properties:
        requestId:
          type: string
          description: Stable request correlation id. Mirrors `X-Request-Id` when supplied.
          example: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
        rateLimit:
          $ref: '#/components/schemas/PublicApiRateLimitMeta'
    StandardResponse:
      type: object
      properties:
        status:
          type: integer
          example: 200
        error:
          type: boolean
          example: false
        message:
          type: string
          example: SUCCESS
      required:
        - status
        - error
        - message
    PublicApiRateLimitMeta:
      type: object
      additionalProperties: false
      properties:
        limit:
          type: integer
          example: 10
        remaining:
          type: integer
          example: 9
        resetSeconds:
          type: integer
          description: Seconds until the current rate limit window resets.
          example: 1
        retryAfterSeconds:
          type: integer
          description: Present when the request was rate limited.
          example: 1
  securitySchemes:
    PublicBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Opaque
      description: >-
        Public API access token issued by `/public/v1/oauth/token`. Example:
        `Authorization: Bearer fspt_...`. Each resource request rechecks the
        token company's current provider access. Disabling access blocks
        existing tokens with `403 auth.provider_unavailable`.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.