openapi: 3.1.0
info:
  title: Healthspan / PlanScout Intake Insight API
  version: 1.2.0
  summary: Completed-intake insight package for advisor-led conversations
  description: Receives Oliver Lin's versioned PlanScout intake event, applies
    Healthspan's transparent six-dimension weighted model, and asynchronously
    returns a complete advisor insight package. The package includes readiness
    and AUM-exposure descriptors, evidence provenance, talking points,
    recommended Healthspan journeys, and reviewed LLM context. The outward
    contract never exposes the private numeric calculation.
  contact:
    name: Black Flag Design
    url: https://blackflag.design
servers:
  - url: https://blessed-squirrel-650.convex.site
    description: Active integration deployment
tags:
  - name: Authentication
    description: Clerk-backed PlanScout machine credentials
  - name: Intake handshake
    description: At-least-once intake receipt and qualitative result status
  - name: Operations
    description: Deployment and model readiness
paths:
  /oauth/token:
    post:
      tags:
        - Authentication
      operationId: issuePlanScoutAccessToken
      summary: Exchange PlanScout machine credentials for a Clerk M2M token
      description: OAuth 2.0 client-credentials request shape backed by Clerk M2M.
        Cache the opaque bearer token until expiry and refresh after expiry or a
        401.
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
                - grant_type
              properties:
                grant_type:
                  type: string
                  const: client_credentials
                scope:
                  type: string
                  const: healthspan.intakes.write
                client_id:
                  type: string
                  description: Optional with HTTP Basic authentication.
                client_secret:
                  type: string
                  format: password
                  description: Optional with HTTP Basic authentication.
      responses:
        "200":
          description: One-hour Clerk M2M access token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthTokenResponse"
        "400":
          $ref: "#/components/responses/OAuthError"
        "401":
          $ref: "#/components/responses/OAuthError"
  /v1/intakes:
    post:
      tags:
        - Intake handshake
      operationId: submitPlanScoutIntake
      summary: Accept a completed PlanScout intake
      description: Returns 202 as soon as the intake is authenticated, validated, and
        deduplicated. Delivery is at least once; eventId is the idempotency key.
        The qualitative result is delivered through the callback and can also be
        read from the protected status resource.
      security:
        - planScoutOAuth:
            - healthspan.intakes.write
        - planScoutHmac: []
      parameters:
        - $ref: "#/components/parameters/PlanScoutTimestamp"
        - $ref: "#/components/parameters/PlanScoutSignature"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlanScoutIntake"
            examples:
              complete:
                summary: Oliver's proposed intake shape
                value:
                  eventType: intake.submitted
                  eventId: f1c2e0a4-9b6d-4f2a-8e21-2c7b9a0d4e55
                  eventTimestamp: 2026-07-10T16:30:00.000Z
                  schemaVersion: v1
                  advisorId: "10482"
                  clientGuid: b7e63d4a-1f88-4c0e-9a2f-3d51c6e9aa10
                  planscoutPlanRequestId: 665f0a1b2c3d4e5f60718293
                  advisor:
                    name: Jane Advisor
                    company: Summit Wealth Partners
                    email: jane@summitwealth.example
                  client:
                    hasPartner: true
                    primary:
                      firstName: Robert
                      lastName: Miyazaki
                      age: 64
                    partner:
                      firstName: Susan
                      lastName: Miyazaki
                      age: 61
                  intake:
                    employment:
                      primary:
                        isRetired: false
                        currentIncome: 145000
                        expectedRetirementAge: 67
                      partner:
                        isRetired: false
                        currentIncome: 60000
                        expectedRetirementAge: 65
                    savings:
                      totalAssets: 1170000
                    goals:
                      targetIncome: 164000
                      legacyGoal: 250000
                  healthSupport:
                    schemaVersion: v1
                    answers:
                      - key: diet_frequency
                        questionId: HQ1
                        type: single_choice_scale
                        subject: primary
                        selected:
                          value: often
                          label: Often
                          ordinal: 3
                      - key: community_connection
                        questionId: HQ2
                        type: single_choice_scale
                        subject: primary
                        selected:
                          value: somewhat_connected
                          label: Somewhat connected
                          ordinal: 2
                      - key: health_conditions
                        questionId: HQ3
                        type: multi_select
                        subject: primary
                        selected:
                          - value: hypertension
                            label: Hypertension
                        noneSelected: false
                      - key: care_reliance
                        questionId: HQ4
                        type: multi_select
                        subject: primary
                        selected:
                          - value: spouse_partner
                            label: Spouse / partner
                          - value: adult_child
                            label: Adult child
                      - key: care_dependents
                        questionId: HQ5
                        type: multi_select
                        subject: primary
                        selected:
                          - value: parent
                            label: Parent
                      - key: emergency_contact
                        questionId: HQ6
                        type: single_choice_with_name
                        subject: primary
                        selected:
                          value: adult_child
                          label: Adult child
                        contactName: Emily Miyazaki
                  healthspan:
                    state: Colorado
                    careAssessment:
                      conditionSummary: Hypertension managed with medication
                      cognitionStage: none
                      adlGrid:
                        bathing: independent
                        dressing: independent
                        toileting: independent
                        transferring: independent
                        continence: independent
                        feeding: independent
                      iadlGrid:
                        medication_management: cueing
                        meal_prep: independent
                        housekeeping: assist
                        shopping: independent
                        transportation: independent
                        finances: independent
                        communication: independent
                      riskFlags: []
                      dmeInUse: []
                      caregiverHoursPerWeek: 20
                      carePreferences: Remain at home as long as practical
                      state: Colorado
      responses:
        "200":
          description: Duplicate event already accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AcceptedResponse"
        "202":
          description: Accepted for asynchronous result delivery
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AcceptedResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/UnprocessableEntity"
  /v1/intakes/{eventId}:
    get:
      tags:
        - Intake handshake
      operationId: getIntakeStatus
      summary: Read result and callback status
      security:
        - planScoutOAuth:
            - healthspan.intakes.write
        - planScoutHmac: []
      parameters:
        - name: eventId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Qualitative result and exact callback payload
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/IntakeStatus"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Event not found
  /health:
    get:
      tags:
        - Operations
      operationId: getHealth
      summary: Check API and model readiness
      responses:
        "200":
          description: Healthy deployment
          content:
            application/json:
              schema:
                type: object
webhooks:
  healthspanResult:
    post:
      operationId: receiveHealthspanResult
      summary: Receive the completed qualitative result
      description: Healthspan posts this payload to PlanScout's configured QA or
        production callback. Callback delivery is at least once with a unique
        callback eventId and echoed advisorId, clientGuid, and
        planscoutPlanRequestId.
      parameters:
        - name: X-Healthspan-Timestamp
          in: header
          required: true
          schema:
            type: integer
            format: int64
        - name: X-Healthspan-Signature
          in: header
          required: true
          schema:
            type: string
            pattern: ^sha256=[0-9a-f]{64}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallbackPayload"
      responses:
        "200":
          description: Callback received and deduplicated
components:
  securitySchemes:
    planScoutOAuth:
      type: oauth2
      description: Primary PlanScout-to-Healthspan machine authentication.
      flows:
        clientCredentials:
          tokenUrl: https://blessed-squirrel-650.convex.site/oauth/token
          scopes:
            healthspan.intakes.write: Submit intakes and read qualitative result status.
    planScoutHmac:
      type: apiKey
      in: header
      name: X-PlanScout-Signature
      description: "Compatibility fallback: sha256=<hex HMAC-SHA256(secret, timestamp
        + '.' + rawBody)> with a five-minute replay window."
  parameters:
    PlanScoutTimestamp:
      name: X-PlanScout-Timestamp
      in: header
      required: false
      schema:
        type: integer
        format: int64
      description: Required only for HMAC authentication.
    PlanScoutSignature:
      name: X-PlanScout-Signature
      in: header
      required: false
      schema:
        type: string
        pattern: ^sha256=[0-9a-f]{64}$
      description: Required only for HMAC authentication.
  responses:
    BadRequest:
      description: Malformed JSON or request headers
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing, expired, or invalid machine authentication
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    OAuthError:
      description: OAuth client authentication or token request failed
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/OAuthError"
    UnprocessableEntity:
      description: Payload does not satisfy Oliver's intake v1 shape
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
  schemas:
    OAuthTokenResponse:
      type: object
      required:
        - access_token
        - token_type
        - expires_in
        - scope
      properties:
        access_token:
          type: string
          description: Opaque Clerk M2M token.
        token_type:
          type: string
          const: Bearer
        expires_in:
          type: integer
          minimum: 1
          maximum: 3600
        scope:
          type: string
          const: healthspan.intakes.write
    OAuthError:
      type: object
      required:
        - error
        - error_description
      properties:
        error:
          type: string
        error_description:
          type: string
    PlanScoutIntake:
      type: object
      required:
        - eventType
        - eventId
        - eventTimestamp
        - schemaVersion
        - advisorId
        - clientGuid
        - planscoutPlanRequestId
        - advisor
        - client
        - intake
        - healthSupport
      properties:
        eventType:
          const: intake.submitted
        eventId:
          type: string
          format: uuid
          description: Idempotency key for at-least-once delivery.
        eventTimestamp:
          type: string
          format: date-time
        schemaVersion:
          const: v1
        advisorId:
          type: string
        clientGuid:
          type: string
          format: uuid
        planscoutPlanRequestId:
          type: string
        advisor:
          type: object
          additionalProperties: true
        client:
          type: object
          additionalProperties: true
        intake:
          type: object
          additionalProperties: true
        estimatesApplied:
          type: object
          additionalProperties:
            type: boolean
          description: Flags inputs estimated by PlanScout.
        estimatedValues:
          type: object
          additionalProperties: true
        healthSupport:
          type: object
          required:
            - schemaVersion
            - answers
          properties:
            schemaVersion:
              const: v1
            answers:
              type: array
              minItems: 1
              items:
                $ref: "#/components/schemas/HealthAnswer"
      additionalProperties: true
    HealthAnswer:
      type: object
      required:
        - key
        - questionId
        - subject
      properties:
        key:
          type: string
        questionId:
          type: string
          examples:
            - HQ1
        label:
          type: string
        type:
          type: string
        subject:
          type: string
          enum:
            - primary
            - partner
        selected: {}
        noneSelected:
          type: boolean
        otherText:
          type:
            - string
            - "null"
        contactName:
          type: string
      additionalProperties: true
    ReadinessResult:
      type: object
      description: Qualitative, advisor-support result. No private numeric calculation
        is exposed.
      required:
        - contractVersion
        - modelVersion
        - methodologyStatus
        - presentation
        - household
        - people
        - dataQuality
        - advisorBrief
        - conversationContext
        - roadmap
        - generatedAt
      properties:
        contractVersion:
          const: v1.2
        modelVersion:
          const: healthspan-insight-v1.0.0
        methodologyStatus:
          const: operational_transparent_weighted_model
        presentation:
          type: object
          required:
            - numericScoreExposed
            - audience
            - purpose
          properties:
            numericScoreExposed:
              const: false
            audience:
              const: licensed_financial_professional
            purpose:
              const: advisor_conversation_support
        household:
          $ref: "#/components/schemas/ReadinessSummary"
        people:
          type: array
          items:
            $ref: "#/components/schemas/PersonInsight"
        dataQuality:
          type: object
          required:
            - answerCount
            - estimatedInputKeys
            - hasEstimatedInputs
            - interpretation
          properties:
            answerCount:
              type: integer
            estimatedInputKeys:
              type: array
              items:
                type: string
            hasEstimatedInputs:
              type: boolean
            interpretation:
              const: initial_intake_subject_to_advisor_revision
        advisorBrief:
          type: object
          required:
            - headline
            - summary
            - talkingPoints
            - preparationGaps
            - recommendedJourneys
            - nextBestActions
            - evidence
            - detailsPath
          properties:
            headline:
              type: string
            summary:
              type: string
            talkingPoints:
              type: array
              items:
                type: string
            preparationGaps:
              type: array
              items:
                type: string
            recommendedJourneys:
              type: array
              items:
                type: string
            nextBestActions:
              type: array
              items:
                type: string
            evidence:
              type: array
              items:
                $ref: "#/components/schemas/DimensionEvidence"
            detailsPath:
              type: string
        conversationContext:
          type: object
          required:
            - schemaVersion
            - purpose
            - reviewRequired
            - prohibitedUses
            - promptInputs
          properties:
            schemaVersion:
              const: v1
            purpose:
              const: advisor_conversation_support
            reviewRequired:
              const: true
            prohibitedUses:
              type: array
              items:
                type: string
            promptInputs:
              type: array
              items:
                type: object
                required:
                  - key
                  - value
                properties:
                  key:
                    type: string
                  value:
                    type: string
        roadmap:
          type: object
          required:
            - phase
            - currentCapability
            - fullPlanSynchronization
            - methodologyAlignment
          properties:
            phase:
              const: pilot_ready
            currentCapability:
              const: completed_intake_to_advisor_insight
            fullPlanSynchronization:
              const: planned_enhancement
            methodologyAlignment:
              const: versioned_contract_ready
        generatedAt:
          type: string
          format: date-time
    ReadinessSummary:
      type: object
      required:
        - status
        - readiness
        - indicator
        - label
        - aumExposure
        - aggregation
      properties:
        status:
          type: string
          enum:
            - classified
            - insufficient_data
        readiness:
          type:
            - string
            - "null"
          enum:
            - established
            - building
            - developing
            - needs_support
            - null
        indicator:
          type: string
          enum: &a1
            - green
            - teal
            - amber
            - orange
            - gray
        label:
          type: string
        aumExposure:
          type: string
          enum:
            - contained
            - watch
            - elevated
            - priority
            - unavailable
        aggregation:
          const: person_average_with_shared_financial_runway
    PersonInsight:
      type: object
      required:
        - subject
        - status
        - readiness
        - indicator
        - label
        - answeredQuestions
        - expectedQuestions
        - completeness
        - dimensions
        - preparationGaps
        - advisorTalkingPoints
        - recommendedJourneys
      properties:
        subject:
          type: string
          enum:
            - primary
            - partner
        status:
          type: string
          enum:
            - classified
            - insufficient_data
        readiness:
          type:
            - string
            - "null"
          enum:
            - established
            - building
            - developing
            - needs_support
            - null
        indicator:
          type: string
          enum: *a1
        label:
          type: string
        answeredQuestions:
          type: integer
        expectedQuestions:
          type: integer
        completeness:
          type: integer
          minimum: 0
          maximum: 100
        dimensions:
          type: array
          items:
            $ref: "#/components/schemas/DimensionEvidence"
        preparationGaps:
          type: array
          items:
            type: string
        advisorTalkingPoints:
          type: array
          items:
            type: string
        recommendedJourneys:
          type: array
          items:
            type: string
    DimensionEvidence:
      type: object
      required:
        - key
        - label
        - descriptor
        - source
        - weight
        - evidenceQuestionIds
        - evidenceSummary
        - journeyKey
      properties:
        key:
          type: string
        label:
          type: string
        descriptor:
          type: string
          enum:
            - established
            - building
            - developing
            - needs_support
        source:
          type: string
          enum:
            - healthspan_question
            - planscout_intake
            - combined
        weight:
          type: number
          minimum: 0
          maximum: 1
        evidenceQuestionIds:
          type: array
          items:
            type: string
        evidenceSummary:
          type: string
        journeyKey:
          type: string
    AcceptedResponse:
      type: object
      required:
        - accepted
        - eventId
        - duplicate
        - status
        - statusUrl
        - resultContractVersion
        - deliveryMode
      properties:
        accepted:
          const: true
        eventId:
          type: string
          format: uuid
        duplicate:
          type: boolean
        status:
          const: accepted
        statusUrl:
          type: string
          format: uri
        resultContractVersion:
          const: v1.2
        deliveryMode:
          const: asynchronous_callback
        authentication:
          type: string
    CallbackPayload:
      type: object
      required:
        - eventType
        - eventId
        - sourceEventId
        - eventTimestamp
        - schemaVersion
        - advisorId
        - clientGuid
        - planscoutPlanRequestId
        - result
      properties:
        eventType:
          const: healthspan.score.completed
          description: Compatibility event name retained from Oliver's original callback
            proposal.
        eventId:
          type: string
          format: uuid
        sourceEventId:
          type: string
          format: uuid
        eventTimestamp:
          type: string
          format: date-time
        schemaVersion:
          const: v1.2
        advisorId:
          type: string
        clientGuid:
          type: string
          format: uuid
        planscoutPlanRequestId:
          type: string
        result:
          $ref: "#/components/schemas/ReadinessResult"
    IntakeStatus:
      type: object
      properties:
        eventId:
          type: string
          format: uuid
        scoreStatus:
          type: string
        callbackStatus:
          type: string
        result:
          $ref: "#/components/schemas/ReadinessResult"
        callback:
          $ref: "#/components/schemas/CallbackPayload"
    Error:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
        message:
          type: string
        details:
          type: array
          items:
            type: object
