> ## 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.

# Update form

> Requires `forms:write`. Updates an active company form template. Forms with existing answers cannot be edited, including translation-only changes (400 FORM_ALREADY_HAS_ANSWERS_CANNOT_EDIT); formType cannot change. Omitted top-level fields are preserved. Each supplied text map replaces all languages for that field; an empty map clears an optional field. Every non-empty map must contain defaultLanguage. When provided, sections replaces the complete sections/questions collection; include complete text maps and retain existing section/question/option IDs to preserve identity. The first update of an internal form must declare defaultLanguage and persists IDs for its existing options. Reads never modify legacy forms. Internal objectives, measurements, media and AI metadata are preserved. Archived and deleted forms return 404.



## OpenAPI

````yaml /openapi/public-v1.json patch /public/v1/forms/{formId}
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: 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/forms/{formId}:
    patch:
      tags:
        - Forms
      summary: Update form
      description: >-
        Requires `forms:write`. Updates an active company form template. Forms
        with existing answers cannot be edited, including translation-only
        changes (400 FORM_ALREADY_HAS_ANSWERS_CANNOT_EDIT); formType cannot
        change. Omitted top-level fields are preserved. Each supplied text map
        replaces all languages for that field; an empty map clears an optional
        field. Every non-empty map must contain defaultLanguage. When provided,
        sections replaces the complete sections/questions collection; include
        complete text maps and retain existing section/question/option IDs to
        preserve identity. The first update of an internal form must declare
        defaultLanguage and persists IDs for its existing options. Reads never
        modify legacy forms. Internal objectives, measurements, media and AI
        metadata are preserved. Archived and deleted forms return 404.
      operationId: patchPublicV1FormsByFormId
      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`.
        - name: formId
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-fA-F0-9]{24}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FormUpdateInput'
      responses:
        '200':
          description: Form updated
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/PublicApiWriteSuccessResponse'
                  - type: object
                    properties:
                      data:
                        type: object
                        required:
                          - form
                        properties:
                          form:
                            $ref: '#/components/schemas/FormDetail'
              examples:
                success:
                  summary: Successful response
                  value:
                    data:
                      form:
                        formId: 66f7b8b1e13c8d25f4d3d90a
                        name: {}
                        description: {}
                        defaultLanguage: string
                        formType: string
                        sectionCount: 1
                        questionCount: 1
                        hasObjectives: true
                        hasMeasurements: true
                        createdAt: '2026-07-14T10:00:00.000Z'
                        updatedAt: '2026-07-14T10:00:00.000Z'
                        sections:
                          - sectionId: 66f7b8b1e13c8d25f4d3d90a
                            title: {}
                            description: {}
                            questions:
                              - questionId: 66f7b8b1e13c8d25f4d3d90a
                                name: {}
                                type: string
                                required: true
                                options:
                                  - optionId: 66f7b8b1e13c8d25f4d3d90a
                                    label: {}
                                linearScale:
                                  startValue: 1
                                  endValue: 1
                                  startLabel: {}
                                  endLabel: {}
                                ratingScale: 1
                                maxFiles: 1
                                order: 1
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
                      idempotency:
                        replayed: false
        '400':
          description: >-
            Bad request. The request shape, query, path parameter, or
            idempotency header is invalid.
          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':
          description: >-
            Unauthorized. The Bearer token is missing, invalid, expired, or
            belongs to an inactive Public API client.
          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: >-
            Forbidden. The token is valid but does not include the required
            scope.
          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:
                          - forms:write
                    meta:
                      requestId: 4f849d7d-f4f1-45cc-b4b7-3984a3d17f83
                      rateLimit:
                        limit: 10
                        remaining: 9
                        resetSeconds: 1
        '404':
          description: >-
            Not found. The requested company-scoped resource does not exist or
            is not accessible to this token.
          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: >-
            Conflict. The write request conflicts with idempotency or current
            resource state.
          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: >-
            Unprocessable content. The request body is empty or is not a JSON
            object.
          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':
          description: >-
            Too many requests. The Public API client or caller IP exceeded the
            rate limit.
          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: >-
            Server error. The request could not be completed because of an
            unexpected Public API server failure.
          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
          source: |-
            curl -X PATCH "https://api.fitsociety.io/public/v1/forms/{formId}" \
              -H "Authorization: Bearer <access_token>" \
              -H "Idempotency-Key: form-management-20260909-001" \
              -H "Content-Type: application/json" \
              -d '{"name":{"en":"Updated intake","nl":"Bijgewerkte intake"}}'
components:
  schemas:
    FormUpdateInput:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        name:
          $ref: '#/components/schemas/FormLocalizedTextInput'
        description:
          $ref: '#/components/schemas/FormLocalizedTextInput'
        defaultLanguage:
          type: string
          maxLength: 80
          description: >-
            Required on create and on the first Public API update of an internal
            form. A canonical BCP 47 language tag, such as nl or en-GB; und is
            reserved for unknown-language responses. Every non-empty text map
            must include this language. When changing it, all retained maps must
            contain the new language. Declaring the language of a legacy form
            assigns its existing source text to that language without
            translating it.
        formType:
          type: string
          enum:
            - intake
            - checkup
          description: Cannot be changed after creation.
        sections:
          type: array
          items:
            $ref: '#/components/schemas/FormSectionInput'
          description: >-
            On update, replaces the complete sections array. Omit to preserve
            sections. Existing section/question IDs may be retained; duplicate
            or foreign IDs are rejected.
    PublicApiWriteSuccessResponse:
      type: object
      additionalProperties: false
      required:
        - data
        - meta
      properties:
        data:
          type: object
        meta:
          $ref: '#/components/schemas/PublicApiWriteMeta'
    FormDetail:
      type: object
      additionalProperties: false
      required:
        - formId
        - name
        - description
        - defaultLanguage
        - formType
        - sectionCount
        - questionCount
        - hasObjectives
        - hasMeasurements
        - createdAt
        - updatedAt
        - sections
      properties:
        formId:
          type: string
        name:
          $ref: '#/components/schemas/FormLocalizedText'
        description:
          $ref: '#/components/schemas/FormLocalizedText'
        defaultLanguage:
          type:
            - string
            - 'null'
        formType:
          type: string
        sectionCount:
          type: integer
        questionCount:
          type: integer
        hasObjectives:
          type: boolean
        hasMeasurements:
          type: boolean
        createdAt:
          type:
            - string
            - 'null'
          format: date-time
        updatedAt:
          type:
            - string
            - 'null'
          format: date-time
        sections:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - sectionId
              - title
              - description
              - questions
            properties:
              sectionId:
                type: string
              title:
                $ref: '#/components/schemas/FormLocalizedText'
              description:
                $ref: '#/components/schemas/FormLocalizedText'
              questions:
                type: array
                items:
                  type: object
                  additionalProperties: false
                  required:
                    - questionId
                    - name
                    - type
                    - required
                    - options
                    - linearScale
                    - ratingScale
                    - maxFiles
                    - order
                  properties:
                    questionId:
                      type: string
                    name:
                      $ref: '#/components/schemas/FormLocalizedText'
                    type:
                      type: string
                    required:
                      type: boolean
                    options:
                      type: array
                      items:
                        type: object
                        additionalProperties: false
                        required:
                          - optionId
                          - label
                        properties:
                          optionId:
                            type:
                              - string
                              - 'null'
                            description: >-
                              Persisted option identity. Null for an internal
                              option that has not been saved through the Public
                              API; GET does not allocate IDs. Retain non-null
                              IDs on update.
                          label:
                            $ref: '#/components/schemas/FormLocalizedText'
                    linearScale:
                      type:
                        - object
                        - 'null'
                      additionalProperties: false
                      required:
                        - startValue
                        - endValue
                        - startLabel
                        - endLabel
                      properties:
                        startValue:
                          type:
                            - number
                            - 'null'
                        endValue:
                          type:
                            - number
                            - 'null'
                        startLabel:
                          $ref: '#/components/schemas/FormLocalizedText'
                        endLabel:
                          $ref: '#/components/schemas/FormLocalizedText'
                    ratingScale:
                      type:
                        - number
                        - 'null'
                    maxFiles:
                      type:
                        - number
                        - 'null'
                    order:
                      type:
                        - number
                        - 'null'
    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'
    FormLocalizedTextInput:
      type: object
      maxProperties: 25
      description: >-
        Up to 25 languages per field. Valid BCP 47 keys are canonicalized;
        duplicate canonical keys and und are rejected. Every non-empty map must
        contain defaultLanguage. Required text cannot be empty. A supplied map
        replaces all languages for that field; {} clears an optional field. Null
        maps, null values and plain strings are rejected.
      additionalProperties:
        type: string
        minLength: 1
    FormSectionInput:
      type: object
      additionalProperties: false
      required:
        - title
        - questions
      properties:
        sectionId:
          type: string
          pattern: ^[a-fA-F0-9]{24}$
          description: >-
            On update only: retain an existing section ID from this form. Omit
            for new sections.
        title:
          $ref: '#/components/schemas/FormLocalizedTextInput'
        description:
          $ref: '#/components/schemas/FormLocalizedTextInput'
        questions:
          type: array
          items:
            $ref: '#/components/schemas/FormQuestionInput'
    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
    FormLocalizedText:
      type: object
      description: >-
        Stored text by canonical BCP 47 language tag. No language is selected
        and no translations are generated. Empty optional fields return {}.
        Undeclared single-language source text uses und, with defaultLanguage:
        null. Choose the exact requested language, then the declared
        defaultLanguage; an unknown default must be resolved by the integration.
      additionalProperties:
        type: string
    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'
    FormQuestionInput:
      type: object
      additionalProperties: false
      required:
        - name
        - type
      properties:
        questionId:
          type: string
          pattern: ^[a-fA-F0-9]{24}$
          description: >-
            On update only: retain an existing question ID from this section.
            Omit for new questions.
        name:
          $ref: '#/components/schemas/FormLocalizedTextInput'
        type:
          type: string
          enum:
            - Short Answer
            - Open Question
            - Multiple Choice
            - Linear Scale
            - CheckList
            - Yes/No
            - Photo
            - Video
            - Ratings
        required:
          type: boolean
          default: false
        options:
          type: array
          items:
            type: object
            additionalProperties: false
            required:
              - label
            properties:
              optionId:
                type: string
                pattern: ^[a-fA-F0-9]{24}$
                description: >-
                  Retain an existing option ID from this question on update.
                  Omit for new options and for legacy options whose returned
                  optionId is null. IDs remain stable when labels or order
                  change through the Public API.
              label:
                $ref: '#/components/schemas/FormLocalizedTextInput'
          description: >-
            A non-empty array is required for Multiple Choice and CheckList.
            Default-language labels must be unique within a question. Other
            question types accept only an empty options array.
        linearScale:
          type:
            - object
            - 'null'
          additionalProperties: false
          required:
            - startValue
            - endValue
          description: >-
            Required for Linear Scale; startValue must be less than endValue.
            Omit or use null for other types.
          properties:
            startValue:
              oneOf:
                - type: number
                - type: string
                  description: >-
                    Numeric input; localized comma decimals are accepted and
                    normalized to numbers.
            endValue:
              oneOf:
                - type: number
                - type: string
                  description: >-
                    Numeric input; localized comma decimals are accepted and
                    normalized to numbers.
            startLabel:
              $ref: '#/components/schemas/FormLocalizedTextInput'
            endLabel:
              $ref: '#/components/schemas/FormLocalizedTextInput'
        ratingScale:
          oneOf:
            - type: 'null'
            - type: number
              enum:
                - 5
                - 6
                - 7
                - 8
                - 9
                - 10
            - type: string
              description: >-
                Numeric input; localized comma decimals are accepted and
                normalized to numbers.
          description: Required integer 5–10 for Ratings. Omit for other types.
        maxFiles:
          oneOf:
            - type: 'null'
            - type: integer
              minimum: 1
              maximum: 5
            - type: string
              description: >-
                Numeric input; localized comma decimals are accepted and
                normalized to numbers.
          description: Optional integer 1–5 for Photo or Video. Omit for other types.
        order:
          oneOf:
            - type: 'null'
            - type: integer
              minimum: 0
            - type: string
              description: >-
                Numeric input; localized comma decimals are accepted and
                normalized to numbers.
          description: Defaults to the question's zero-based position in this request.
    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`.

````