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

# Start an intake session and get the first question

> Begins an intake questionnaire for your patient and returns the first question to present. On the first call the server mints an enrollment id (returned as `journey_id`) that identifies the session on every later call; send it back in the `X-Journey-Id` request header on a return visit to resume. The server owns the question sequence, branching, and completion — a client cannot skip, hide, or reorder questions, and any value pre-filled from an entry link is always shown for explicit confirmation, never accepted on the patient's behalf. WHICH questionnaire is served follows your brand's configured therapies: a brand with exactly one enabled therapy always serves that therapy's questionnaire; a brand with more than one must name an `offering` on the entry link so the therapy is unambiguous. If that therapy's questionnaire is briefly still loading, the session opens on the platform's shared questionnaire rather than failing. If your brand's therapy has no questionnaire at all — none is published for it, or the brand is configured for a therapy this platform does not serve — the call is REFUSED (422) rather than answered with a different therapy's questions, which would be neither safe nor useful; and an error is likewise answered when no questionnaire is available at all. PROOF-OF-IDENTITY CAPTURE IS NEVER PART OF THIS QUESTIONNAIRE. Questions that collect a government ID are withheld from this surface and are not counted toward its completion, so the patient finishes the intake here and verifies identity in the member portal's own identity step. Clinical photo capture and the consent video are unaffected and continue to be served exactly as before.



## OpenAPI

````yaml /openapi/public-openapi.json get /v1/instrument/resolve
openapi: 3.1.0
info:
  title: Purple API
  version: 0.0.0
servers:
  - url: https://api.dev.purplelabelmd.com
security: []
tags:
  - description: Sign a patient in and out and manage the browser session.
    name: Sessions & authentication
  - description: Run the server-driven intake questionnaire question by question.
    name: Intake
  - description: Address autocomplete for the intake flow.
    name: Addresses
  - description: Read the public status of a patient's enrollment.
    name: Enrollment
  - description: Track a client's onboarding progress.
    name: Onboarding
  - description: Read back the platform configuration stored for your brand.
    name: Brand configuration
  - description: Enable offerings for your brand and set their display copy.
    name: Offering configuration
  - description: Start and track payments for an order.
    name: Payments
  - description: Subscribe to platform events and manage delivery endpoints.
    name: Webhooks
paths:
  /v1/instrument/resolve:
    parameters:
      - $ref: '#/components/parameters/BrandIdHeader'
    get:
      tags:
        - Intake
      summary: Start an intake session and get the first question
      description: >-
        Begins an intake questionnaire for your patient and returns the first
        question to present. On the first call the server mints an enrollment id
        (returned as `journey_id`) that identifies the session on every later
        call; send it back in the `X-Journey-Id` request header on a return
        visit to resume. The server owns the question sequence, branching, and
        completion — a client cannot skip, hide, or reorder questions, and any
        value pre-filled from an entry link is always shown for explicit
        confirmation, never accepted on the patient's behalf. WHICH
        questionnaire is served follows your brand's configured therapies: a
        brand with exactly one enabled therapy always serves that therapy's
        questionnaire; a brand with more than one must name an `offering` on the
        entry link so the therapy is unambiguous. If that therapy's
        questionnaire is briefly still loading, the session opens on the
        platform's shared questionnaire rather than failing. If your brand's
        therapy has no questionnaire at all — none is published for it, or the
        brand is configured for a therapy this platform does not serve — the
        call is REFUSED (422) rather than answered with a different therapy's
        questions, which would be neither safe nor useful; and an error is
        likewise answered when no questionnaire is available at all.
        PROOF-OF-IDENTITY CAPTURE IS NEVER PART OF THIS QUESTIONNAIRE. Questions
        that collect a government ID are withheld from this surface and are not
        counted toward its completion, so the patient finishes the intake here
        and verifies identity in the member portal's own identity step. Clinical
        photo capture and the consent video are unaffected and continue to be
        served exactly as before.
      operationId: resolveInstrument
      parameters:
        - $ref: '#/components/parameters/JourneyResumeHeader'
        - description: >-
            an existing enrollment id (returning patient) as a query parameter —
            a compatibility channel. On the hosted platform this query parameter
            is not read: the enrollment id rides the `X-Journey-Id` request
            header, and that header is the resume mechanism. Absent both, a new
            enrollment id is minted.
          in: query
          name: journey_id
          required: false
          schema:
            pattern: ^jny_[A-Za-z0-9][A-Za-z0-9_-]*$
            type: string
        - description: >-
            serve ONE tier of the instrument as an independent phase.
            `qualification` plays the pre-pay funnel-top tier and completes at
            the qualification terminal (the journey then hands to checkout);
            `clinical` plays the post-pay tier (re-resolved by the same
            enrollment id after checkout) and completes at the usual intake
            terminal. OMITTED ⇒ the continuous single-pass flow (unchanged): the
            whole instrument, one terminal. Requires a tier-aware bundle; an
            unknown value is rejected 400.
          in: query
          name: phase
          required: false
          schema:
            enum:
              - qualification
              - clinical
            type: string
        - description: >-
            preselect a catalog offering by STABLE ref (never a free-text
            product name). Resolvable only against the compiled bundle's
            declared offering refs; an unresolvable ref FAILS CLOSED to the
            generic flow. Effect is presentation-only narrowing of display-node
            offering_refs — EXCEPT for a brand configured with MORE THAN ONE
            enabled therapy, where this parameter is additionally REQUIRED and
            is a DECLARED RESOLUTION INPUT: it selects which of that brand's
            enabled therapies the session's questionnaire comes from. Omitting
            it, naming a ref that belongs to none of the brand's enabled
            therapies, or naming one that belongs to more than one, is REFUSED
            (422) — never guessed. For a brand with a single enabled therapy
            (and for a brand with none) the parameter does not change which
            SHARED questionnaire is served. SECOND DECLARED EFFECT — YOUR OWN
            QUALIFICATION QUESTIONS. On an entry that carries no journey (the
            direct-buy intake the site kit uses, where this endpoint is the op
            that mints the journey), this parameter ALSO selects WHICH of your
            brand's own qualification questions are composed onto the served
            plan, funnel-top: the ones you attached to the offering you name
            here. This applies only when your brand has declared its enabled
            therapies and the session resolved one of them; a brand that has
            declared none is served exactly as before. When the session's
            journey already recorded an offering, THAT offering wins and this
            parameter is not consulted for question selection. Naming an
            offering the platform's committed catalog does not bind, naming one
            you attached no questions to, or naming a malformed ref all FAIL
            CLOSED to the shared questionnaire with no error — the parameter can
            never conjure questions you did not author, and can never reach
            another brand's questions. THE TREATMENT-AGREEMENT RULE, and the one
            case that is REFUSED rather than ignored: the offering you name must
            belong to the SAME treatment the session resolved. Naming a real
            offering of a DIFFERENT treatment — even one your own brand sells —
            is refused with the 422 config-gap, not served: a patient must never
            be asked one treatment's questions while buying another, and
            answering them silently would be worse than refusing. The refusal is
            oracle-free: it is the same single answer every structural cause
            gives, and it names no offering, treatment or brand. An offering the
            platform cannot bind at all is NOT this case — it simply selects
            nothing and the shared questionnaire serves as before, so a stale or
            mistyped link degrades gently while a genuinely wrong-treatment link
            is caught.
          in: query
          name: offering
          required: false
          schema:
            maxLength: 128
            pattern: ^[A-Za-z0-9][A-Za-z0-9_.:-]*$
            type: string
        - description: >-
            stage a contact-email prefill. Lands `unverified`: nothing is
            written until the patient acts at the confirm step (which ALWAYS
            renders — prefill is never silent acceptance); a confirm-as-is
            records method=patient_reported, never validated/patient_confirmed.
          in: query
          name: prefill_email
          required: false
          schema:
            maxLength: 254
            type: string
        - description: same contract as prefill_email, for the contact phone.
          in: query
          name: prefill_phone
          required: false
          schema:
            maxLength: 32
            type: string
        - description: >-
            an opaque promo token, passed through unchanged to the completion
            hand-off. It is not interpreted here; validation, eligibility, and
            stacking are applied downstream.
          in: query
          name: promo
          required: false
          schema:
            maxLength: 256
            type: string
        - description: >-
            post-completion return URL. Honored ONLY if it matches the brand
            config's redirect_allowlist (absolute https, exact origin +
            path-segment prefix); anything else is refused — open redirect
            barred, fail closed. Surfaces on the completion handoff.
          in: query
          name: redirect
          required: false
          schema:
            maxLength: 2048
            type: string
        - description: per-client test-mode flag; passed through unchanged.
          in: query
          name: test
          required: false
          schema:
            enum:
              - 'true'
              - 'false'
              - '1'
              - '0'
            type: string
        - $ref: '#/components/parameters/CorrelationIdHeader'
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InstrumentStep'
          description: the minted/loaded session + the first node (or complete)
        '400':
          $ref: '#/components/responses/EdgeBadRequest'
        '422':
          $ref: '#/components/responses/EdgeInstrumentBundleConfigGap'
        '429':
          $ref: '#/components/responses/EdgeRateLimited'
        '503':
          $ref: '#/components/responses/EdgeInstrumentBundleUnavailable'
      security:
        - apiKey: []
components:
  parameters:
    BrandIdHeader:
      description: >-
        Opaque brand id (brd_...). Validated to belong to the resolved client
        (§2).
      in: header
      name: X-Brand-Id
      required: true
      schema:
        pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
        type: string
    JourneyResumeHeader:
      description: >-
        The patient's enrollment id (`jny_...`) — the resume channel. Send it on
        a return visit and the questionnaire resumes that patient's session with
        everything already answered applied; omit it and a new enrollment id is
        minted. A malformed value is ignored and treated as absent, never
        trusted.
      in: header
      name: X-Journey-Id
      required: false
      schema:
        pattern: ^jny_[A-Za-z0-9][A-Za-z0-9_-]*$
        type: string
    CorrelationIdHeader:
      description: >-
        An optional id you send to tie this request to your own logs. Send one
        and it is echoed back unchanged; omit it and one is assigned for you.
        Either way the id is returned in the X-Correlation-Id response header on
        every response, including errors, so you can match a response to the
        request that produced it.
      in: header
      name: X-Correlation-Id
      required: false
      schema:
        maxLength: 128
        pattern: ^[A-Za-z0-9][A-Za-z0-9._:-]*$
        type: string
  schemas:
    InstrumentStep:
      description: >-
        The result of a resolve, next, or abandon call — the session identity,
        the current question (or null when finished), the status, any flags
        (e.g. ship_state_mismatch), and any validation issues. On completion it
        may carry the entry-link hand-off.
      properties:
        blocked:
          $ref: '#/components/schemas/BlockedOutcome'
        flags:
          items:
            type: string
          type: array
        handoff:
          $ref: '#/components/schemas/EntryHandoff'
        issues:
          items:
            $ref: '#/components/schemas/ControlIssue'
          type: array
        journey_id:
          pattern: ^jny_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
        node:
          oneOf:
            - $ref: '#/components/schemas/RenderedNode'
            - type: 'null'
        progress:
          $ref: '#/components/schemas/StepProgress'
        session_id:
          type: string
        status:
          description: >-
            `active` (a node is presented), `complete` (server-authoritative
            completion), `abandoned` (patient drop-off), or `blocked` — a
            terminal, server-authoritative refusal to proceed (a required
            consent was declined or could not be affirmed). On `blocked`, `node`
            is null and `blocked` carries the honest stop-screen outcome.
          enum:
            - active
            - complete
            - abandoned
            - blocked
          type: string
      required:
        - session_id
        - journey_id
        - status
      type: object
    BlockedOutcome:
      description: >-
        The honest stop-screen outcome. Present only when `status` is `blocked`.
        Carries the machine-readable reason, the node at which the block
        occurred, the consent scopes that were not granted (codes, never patient
        data), and a support-routing hint. The stop-screen copy is a
        presentation concern (the intake kit renders it); no patient data ever
        rides here. No order lock ever forms without a positive affirmation
        where one is required.
      properties:
        code:
          description: stable reason code (e.g. `consent_declined`)
          type: string
        node_id:
          description: the node at which the block occurred (the consent node id)
          type: string
        support_route:
          description: >-
            a stable support-routing key (edge owns portal→GHL support routing);
            never PII
          type: string
        ungranted_scopes:
          description: consent scopes not granted — codes, never patient data
          items:
            type: string
          type: array
      required:
        - code
        - node_id
      type: object
    EntryHandoff:
      additionalProperties: false
      description: >-
        Post-completion hand-off carried when a session that began from an entry
        link completes. Present only when `status` is `complete` and the session
        carried a validated entry context. `redirect` holds a brand-allowlisted
        https URL; `promo` is an opaque pass-through token validated downstream;
        `test` is the per-client test-mode flag. Never carries patient data.
      properties:
        promo:
          maxLength: 256
          type: string
        redirect:
          maxLength: 2048
          type: string
        test:
          type: boolean
      type: object
    ControlIssue:
      properties:
        code:
          type: string
        conflicting_codes:
          description: >-
            chore-refuse-contradictory-answer (additive; paired with
            `exclusive_codes`). The option code(s) the exclusive selection
            contradicted — the other half of "which options clashed". When the
            submission carried only exclusive options, they contradict each
            other and both lists name them.
          items:
            type: string
          type: array
        exclusive_codes:
          description: >-
            chore-refuse-contradictory-answer (additive; present ONLY on `code:
            renderer.exclusive_violation`, absent on every other issue). The
            exclusive ("none of these"-class) option code(s) the refused
            submission carried. A contradictory selection on a clinical-safety
            question is a SERVE defect, not a client nicety: the server refuses
            it with a 422 and never a 200-advance, and the refusal must say
            WHICH selections contradicted so the client can correct the exact
            ones. These are the caller's OWN submitted codes echoed back to the
            caller.
          items:
            type: string
          type: array
        message:
          type: string
      required:
        - code
        - message
      type: object
    RenderedNode:
      description: >-
        A node as presented to the client — PRESENTATION ONLY. The client never
        owns the sequence, controls, or option order (those come from the
        compiled bundle; a brand theme cannot alter them). Brand theme/copy,
        when applied, ride alongside as presentation overlay.
      properties:
        computed_stub:
          type: boolean
        consent_version:
          description: >-
            the consent version served on a `consent` node. The client echoes it
            on the affirmation; it rides into the recorded consent fact as
            provenance. The MD/counsel-locked affirmation copy rides `content`;
            brand can never supply it.
          type: string
        content:
          additionalProperties:
            type: string
          description: >-
            Named display-content slots for a display node — MD-locked copy
            carried in the compiled instrument bundle, versioned and reviewed
            with the instrument. Brand config themes presentation only and can
            never supply or override these slots. The `copy` field is the
            separate question-headline channel; its authoritative source is
            likewise the MD-locked instrument bundle, and the brand overlay can
            never override it.
          type: object
        control:
          type: string
        copy:
          description: >-
            The question headline, as a single string, served from the MD-locked
            question prompt in the compiled instrument bundle (versioned and
            reviewed with the instrument). For a question node this is its only
            source — the per-brand copy overlay can never supply or override it
            (theming yes, wording never). Absent is valid; the renderer falls
            back deterministically (for example a formatted fact code).
          type: string
        display:
          enum:
            - static
            - computed
          type: string
        fact:
          type: string
        grants:
          description: >-
            the consent scopes an affirmative answer grants on a `consent` node.
            Codes, never labels; nothing branches on the copy text.
          items:
            type: string
          type: array
        interstitial:
          description: >-
            Marks a display node as an interstitial moment for skin treatment
            (the tint pair is reserved for these). Presentation flag only; never
            carried on question nodes.
          type: boolean
        kind:
          description: >-
            `question` (collects a fact), `display` (MD-locked informational
            node, no answer channel), or `consent` (a consent-capture
            affirmation surface — binds no fact; its MD/counsel-locked copy
            rides `content`, its `consent_version` + `grants` ride alongside).
          enum:
            - question
            - display
            - consent
          type: string
        may_auto_advance:
          type: boolean
        media:
          $ref: '#/components/schemas/MediaConfig'
        node_id:
          type: string
        offering_refs:
          items:
            type: string
          type: array
        option_codes:
          items:
            type: string
          type: array
        options:
          description: >-
            Per-option presentation hints for this question. When present, it
            lists the same codes in the same order as `option_codes` — option
            order comes from the questionnaire and cannot be altered by theming.
            When absent, render from `option_codes` alone.
          items:
            $ref: '#/components/schemas/RenderedOption'
          type: array
        prefill:
          enum:
            - collect
            - suppress
            - confirm
          type: string
        prefilled_value: {}
        reassurance:
          description: >-
            A single hedged reassurance line for a computed display panel,
            served from the MD-locked instrument content and reviewed with it.
            The line is SELECTED server-side by where the primary computed value
            falls against the served zone (below / in_zone / above) — never
            served statically — and is PRESENT only when the matching band is
            authored and the band could be determined; otherwise it is ABSENT
            (fail to neutral). Display copy only; it carries no eligibility
            language and defers every decision to the clinician.
          type: string
        required:
          type: boolean
        section_id:
          type: string
        theme:
          additionalProperties: true
          type: object
        values:
          additionalProperties: {}
          description: >-
            Server-supplied display values keyed by fact/derivation CODE (codes,
            never label text). Derived codes are computed server-side on read
            from registered pure derivations — the client renders these values
            and never computes anything of record. A display node whose primary
            referenced values cannot all be resolved is never emitted
            (server-skipped, fail-soft). Ancillary echoed values (for example
            the height and weight behind a computed number) ride this same map
            alongside the primary value; an echoed value that cannot be resolved
            is omitted, and never blocks the node.
          type: object
        zone:
          $ref: '#/components/schemas/DisplayZone'
      required:
        - node_id
        - kind
        - section_id
      type: object
    StepProgress:
      description: >-
        Server-computed progress metadata, present on every resolve/next step:
        computed from the live effective plan — never client-guessed.
        `position_estimate` is the honest fraction of the plan completed (0..1);
        display nodes participate in the fraction. Whether and how it renders
        (thin persistent bar, step ring, interstitial pacing, none) is
        brand-theme presentation.
      properties:
        position_estimate:
          description: honest fraction of the effective plan completed, 0..1
          maximum: 1
          minimum: 0
          type: number
        scope:
          description: '`instrument`, or a subflow id once named optional sub-flows ship'
          type: string
        section_label:
          description: >-
            optional server-supplied section label; omitted while bundles carry
            none
          type: string
      required:
        - scope
        - position_estimate
      type: object
    EdgeProblem:
      description: RFC 7807 problem+json error body.
      properties:
        detail:
          type: string
        status:
          type: integer
        title:
          type: string
        type:
          format: uri-reference
          type: string
      required:
        - type
        - title
        - status
      type: object
    MediaConfig:
      description: >-
        Capture configuration for a file-capture question. When absent, the
        question is a standard photo or document upload. Presentation and
        capture-bounds only — the answer is an upload reference and nothing
        branches on the media content; faces and voices are never matched,
        recognized, or graphed.
      properties:
        accept:
          description: MIME allowlist (e.g. video/mp4, video/webm)
          items:
            type: string
          type: array
        capture_mode:
          enum:
            - upload
            - record
            - record_or_upload
          type: string
        consent_copy:
          description: >-
            DELIVERY-163 — the PLAIN DISPLAY TEXT the member must be shown
            before any affirmative action, pinned to `consent_version`. The
            portal renders THIS EXACT text; it is not a key, not a template, and
            the client never substitutes its own wording — consent to copy the
            server did not serve is not consent. Optional on the contract
            because non-video captures may not gate on consent at all, but
            REQUIRED (nonempty) for a managed video capture alongside
            `consent_version` and `consent_fact`: all three, or no grant and no
            recorded consent. Authored and served through the same MediaConfig,
            so the text the member reads and the version the server records can
            never drift apart.
          type: string
        consent_fact:
          description: >-
            fact code the consent acknowledgment projects to (discrete auditable
            consent fact)
          type: string
        consent_version:
          description: >-
            the version OF RECORD for the consent copy below. The server checks
            the echoed version against this one and records it on the
            acknowledgment; there is NO versionless fallback — a managed video
            capture whose node omits this refuses rather than recording a
            consent that cannot say which copy was agreed to.
          type: string
        facing:
          description: advisory front/rear camera hint; not enforced
          enum:
            - user
            - environment
          type: string
        kind:
          enum:
            - image
            - video
          type: string
        max_bytes:
          type: integer
        max_duration_s:
          type: number
        min_duration_s:
          type: number
        require_consent:
          description: gate capture on a consent-to-record acknowledgment (video ⇒ true)
          type: boolean
        resumable:
          type: boolean
      required:
        - kind
      type: object
    RenderedOption:
      description: >-
        One selectable option with its presentation hints — presentation only,
        carried alongside `option_codes`. An `exclusive` option (a "none of
        these"-class choice) cannot be combined with any sibling — selecting it
        should clear the other selections, and the server rejects an answer that
        carries an exclusive code together with another selection (`422`, the
        session does not advance). A banded choice (for example a blood-pressure
        or frequency range) may carry a `classification_code` alongside its
        stable option code.
      properties:
        classification_code:
          description: >-
            classification code carried by a banded choice alongside its stable
            option code
          type: string
        code:
          description: the stable option code submitted as the answer value
          type: string
        exclusive:
          description: >-
            marks a "none of these"-class option that cannot be combined with
            any sibling selection
          type: boolean
        label:
          description: >-
            MD-locked display label for this option, carried in the compiled
            instrument bundle and resolved by the stable option code (never
            positional). Presentation only — the code remains the submitted
            answer value and the sole binding; consumers fall back to the code
            when no label is present.
          type: string
      required:
        - code
      type: object
    DisplayZone:
      description: >-
        A designed-panel band descriptor for a computed display node: a scale
        from `scale_min` to `scale_max` with a highlighted band from `zone_min`
        to `zone_max`, named by `zone_label`. The client places the patient's
        marker and the highlighted band for the picture. Display copy and
        presentation only — a zone carries no eligibility language and never
        asserts eligibility; the server remains authoritative on jurisdiction
        and eligibility.
      properties:
        scale_max:
          description: high end of the rendered scale axis
          type: number
        scale_min:
          description: low end of the rendered scale axis
          type: number
        zone_label:
          description: label naming the highlighted band (display copy; never an outcome)
          type: string
        zone_max:
          description: high bound of the highlighted band (within the scale)
          type: number
        zone_min:
          description: low bound of the highlighted band (within the scale)
          type: number
      required:
        - scale_min
        - scale_max
        - zone_min
        - zone_max
        - zone_label
      type: object
  responses:
    EdgeBadRequest:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: malformed or absent X-Brand-Id
    EdgeInstrumentBundleConfigGap:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: >-
        No questionnaire can be served for this session. Unlike the temporary
        503, neither state below is transient and neither is cured by retrying.
        Two distinct problem `type`s are answered here, and they are cured by
        different acts:


        `instrument-bundle-config-gap` — no questionnaire is published and
        activated for the therapy and mode this intake surface serves. Cured by
        publishing + activating an instrument bundle for that therapy and mode.


        `instrument-therapy-config-gap` — the session's own therapy cannot be
        served: the brand is configured for a therapy the platform publishes no
        questionnaire for, is configured for a therapy this platform does not
        serve at all, or has more than one enabled therapy and the entry link
        named no offering that resolves to exactly one of them. Cured by
        correcting the brand's configuration (or publishing that therapy's
        questionnaire), never by retrying. This answer exists because serving a
        DIFFERENT therapy's questions would be neither safe nor useful. It is
        deliberately uniform across all of those causes: the response carries no
        therapy key, brand id, or roster content, so it cannot be used to
        enumerate what another brand has enabled or what the platform serves.
    EdgeRateLimited:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: >-
        Too many requests. You have exceeded the request rate allowed for this
        endpoint. Wait the number of seconds given in the Retry-After response
        header, then retry the request.
      headers:
        Retry-After:
          description: Seconds to wait before you retry.
          schema:
            type: integer
        X-Correlation-Id:
          description: The correlation id for this request, echoed back.
          schema:
            type: string
    EdgeInstrumentBundleUnavailable:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: >-
        The intake questionnaire is temporarily unavailable. The service returns
        this status when the compiled questionnaire cannot be loaded right now,
        rather than serving anything that is not the current questionnaire. This
        condition is transient — retry shortly.


        This status also covers a second, equally transient case: when a
        session's therapy has already been determined and a questionnaire for
        that therapy has just become available, but this particular server has
        not finished loading it yet, the response is this same 503 — never
        another therapy's questions. A session whose therapy is known is never
        served a different therapy's questionnaire, so a seconds-long honest
        wait replaces a wrong-therapy intake. (Unlike the 422 below, this IS
        cured by retrying; no action beyond that is required.)
  securitySchemes:
    apiKey:
      description: 'Per-client API key (M2M). Presented as `Authorization: Bearer <key>`.'
      scheme: bearer
      type: http

````