---
openapi: 3.0.3
info:
  title: CareConnect API
  description: |
    Public API for accessing community services in Kingston, Ontario.

    **Base URL:** `https://careconnect.ing/api/v1`

    ## Authentication
    - Public service listing and read endpoints require no authentication.
    - Service creation and update endpoints require a valid session cookie plus organization authorization.
    - Analytics endpoints require a valid session cookie (Magic Link login).
    - Internal pilot endpoints require authentication and organization/admin authorization.
  version: 1.0.0
  contact:
    email: support@careconnect.ing

servers:
  - url: https://careconnect.ing/api/v1
    description: Production
  - url: http://localhost:3000/api/v1
    description: Local Development

paths:
  /services:
    get:
      summary: List Services
      description: |
        Retrieve a paginated list of community services with optional filtering.
      operationId: listServices
      tags:
        - Services
      parameters:
        - name: q
          in: query
          description: Search query (searches name and description)
          schema:
            type: string
            example: "food bank"
        - name: category
          in: query
          description: Filter by service category
          schema:
            type: string
            enum:
              - Food
              - Shelter
              - Crisis
              - Legal
              - Employment
              - Health
              - Youth
              - Seniors
              - Newcomer
              - General
        - name: limit
          in: query
          description: Max results per page (default 20, max 100)
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: offset
          in: query
          description: Pagination offset
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/ServiceSummary"
                  total:
                    type: integer
                    description: Total number of matching services
                  limit:
                    type: integer
                  offset:
                    type: integer
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      summary: Create Service (Authenticated)
      description: |
        Create a new service within the authenticated user's organization.
        Requires authentication, organization membership, and `canCreateServices` permission.
        If `org_id` is supplied in the payload it must match the caller's organization.
      operationId: createService
      tags:
        - Services
      security:
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ServiceSummary"
      responses:
        "201":
          description: Service created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ServiceDetail"
        "400":
          description: Validation error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "500":
          $ref: "#/components/responses/InternalError"

  /search/services:
    post:
      summary: Search Services (Hybrid Ranking)
      description: |
        Perform an intelligent search for community services using keywords
        and location. Implements v16.0 hybrid ranking with authority
        tiers and transparency factors.
      operationId: searchServices
      tags:
        - Search
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SearchRequest"
      responses:
        "200":
          description: Successful search results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchResponse"
        "400":
          description: Invalid search parameters
        "500":
          $ref: "#/components/responses/InternalError"

  /services/{id}:
    get:
      summary: Get Service by ID
      description: Retrieve detailed information about a specific service.
      operationId: getServiceById
      tags:
        - Services
      parameters:
        - name: id
          in: path
          required: true
          description: Unique service identifier
          schema:
            type: string
            example: "kingston-food-bank"
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ServiceDetail"
        "404":
          description: Service not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/InternalError"

    put:
      summary: Update Service (Authenticated)
      description: Update an existing service. Requires authentication.
      operationId: updateService
      tags:
        - Services
      security:
        - cookieAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Unique service identifier
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ServiceDetail"
      responses:
        "200":
          description: Service updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/ServiceDetail"
        "401":
          description: Unauthorized
        "404":
          description: Service not found
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      summary: Delete Service (Authenticated)
      description: Delete an existing service. Requires authentication.
      operationId: deleteService
      tags:
        - Services
      security:
        - cookieAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Unique service identifier
          schema:
            type: string
      responses:
        "200":
          description: Service deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
        "401":
          description: Unauthorized
        "404":
          description: Service not found
        "500":
          $ref: "#/components/responses/InternalError"

  /services/{id}/summary:
    get:
      summary: Get Simplified Summary
      description: |
        Retrieve a plain-language summary and "how-to-use" guide for a service.
      operationId: getServiceSummary
      tags:
        - Services
      parameters:
        - name: id
          in: path
          required: true
          description: Unique service identifier
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: "#/components/schemas/PlainLanguageSummary"
        "404":
          description: Summary not found
        "500":
          $ref: "#/components/responses/InternalError"

  /services/{id}/printable:
    get:
      summary: Get Printable Resource Card
      description: |
        Render a print-optimized HTML resource card for a service.
      operationId: getServicePrintable
      tags:
        - Services
      parameters:
        - name: id
          in: path
          required: true
          description: Unique service identifier
          schema:
            type: string
      responses:
        "200":
          description: Returns HTML document
          content:
            text/html:
              schema:
                type: string
        "404":
          description: Service not found
        "500":
          $ref: "#/components/responses/InternalError"

  /feedback:
    post:
      summary: Submit User Feedback
      description: Submit privacy-preserving feedback.
      operationId: submitFeedback
      tags:
        - Feedback
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FeedbackSubmit"
      responses:
        "200":
          description: Feedback received
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        "400":
          description: Validation error
        "429":
          description: Rate limited
        "500":
          $ref: "#/components/responses/InternalError"

  /analytics:
    get:
      summary: Get Analytics (Authenticated)
      description: |
        Retrieve analytics data for services. Requires authentication
        via Magic Link. Partners can view aggregated view/click counts
        for their services.
      operationId: getAnalytics
      tags:
        - Analytics
      security:
        - cookieAuth: []
      parameters:
        - name: service_id
          in: query
          description: Filter by specific service ID
          schema:
            type: string
        - name: days
          in: query
          description: Number of days to look back (default 30, max 90)
          schema:
            type: integer
            default: 30
            maximum: 90
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AnalyticsEntry"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "500":
          $ref: "#/components/responses/InternalError"

  /pilot/events/contact-attempt:
    post:
      summary: Record Pilot Contact Attempt (Internal)
      description: |
        Internal v22 Phase 0 endpoint for pilot instrumentation.
        Authenticated + organization-authorized.
      operationId: recordPilotContactAttempt
      tags:
        - Pilot
      security:
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PilotContactAttemptCreate"
      responses:
        "201":
          description: Contact attempt recorded
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "429":
          description: Rate limited
        "501":
          description: Pilot storage not initialized
        "500":
          $ref: "#/components/responses/InternalError"

  /pilot/events/referral:
    post:
      summary: Record Pilot Referral Event (Internal)
      description: |
        Internal v22 Phase 0 endpoint for referral state capture.
      operationId: recordPilotReferralEvent
      tags:
        - Pilot
      security:
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PilotReferralCreate"
      responses:
        "201":
          description: Referral event recorded
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "429":
          description: Rate limited
        "501":
          description: Pilot storage not initialized
        "500":
          $ref: "#/components/responses/InternalError"

  /pilot/events/referral/{id}:
    patch:
      summary: Update Pilot Referral Event (Internal)
      operationId: updatePilotReferralEvent
      tags:
        - Pilot
      security:
        - cookieAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PilotReferralUpdate"
      responses:
        "200":
          description: Referral event updated
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "429":
          description: Rate limited
        "501":
          description: Pilot storage not initialized
        "500":
          $ref: "#/components/responses/InternalError"

  /pilot/integration-feasibility:
    post:
      summary: Record Integration Feasibility Decision (Internal)
      operationId: recordIntegrationFeasibilityDecision
      tags:
        - Pilot
      security:
        - cookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/IntegrationFeasibilityDecision"
      responses:
        "201":
          description: Decision recorded
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Admin required
        "429":
          description: Rate limited
        "501":
          description: Pilot storage not initialized
        "500":
          $ref: "#/components/responses/InternalError"

  /pilot/metrics/scorecard:
    get:
      summary: Get Pilot Scorecard (Internal)
      operationId: getPilotScorecard
      tags:
        - Pilot
      security:
        - cookieAuth: []
      parameters:
        - name: pilot_cycle_id
          in: query
          required: true
          schema:
            type: string
        - name: org_id
          in: query
          required: true
          schema:
            type: string
            format: uuid
        - name: baseline_failed_contact_rate
          in: query
          required: false
          schema:
            type: number
            minimum: 0
            maximum: 1
        - name: baseline_p50_seconds_to_connection
          in: query
          required: false
          schema:
            type: number
            minimum: 0
      responses:
        "200":
          description: Pilot scorecard
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      scorecard:
                        $ref: "#/components/schemas/PilotScorecard"
                      gate1Evaluation:
                        type: object
                        nullable: true
        "400":
          description: Validation error
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "429":
          description: Rate limited
        "501":
          description: Pilot storage not initialized
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: sb-access-token
      description: Supabase session cookie (set after Magic Link login)

  schemas:
    ServiceSummary:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        name_fr:
          type: string
        description:
          type: string
        description_fr:
          type: string
        address:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        url:
          type: string
          nullable: true
        category:
          type: string
        verification_status:
          type: string
          enum: [L0, L1, L2, L3]

    ServiceDetail:
      allOf:
        - $ref: "#/components/schemas/ServiceSummary"
        - type: object
          properties:
            email:
              type: string
              nullable: true
            hours:
              type: string
              nullable: true
            fees:
              type: string
              nullable: true
            eligibility:
              type: string
              nullable: true
            application_process:
              type: string
              nullable: true
            languages:
              type: string
              nullable: true
            bus_routes:
              type: string
              nullable: true
            accessibility:
              type: string
              nullable: true
            last_verified:
              type: string
              format: date-time
              nullable: true

    SearchRequest:
      type: object
      required:
        - locale
      properties:
        query:
          type: string
          description: Keyword search term
          maxLength: 500
          default: ""
        locale:
          type: string
          enum: [en, fr, ar, zh-Hans, es, pa, pt]
        filters:
          type: object
          properties:
            category:
              type: string
        options:
          type: object
          properties:
            limit:
              type: integer
              default: 20
            offset:
              type: integer
              default: 0
        location:
          type: object
          description: Latitude and longitude for proximity ranking (v16.0)
          properties:
            lat:
              type: number
              minimum: -90
              maximum: 90
            lng:
              type: number
              minimum: -180
              maximum: 180

    SearchResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/ServiceDetail"
        meta:
          type: object
          properties:
            total:
              type: integer
            limit:
              type: integer
            offset:
              type: integer

    AnalyticsEntry:
      type: object
      properties:
        service_id:
          type: string
        views:
          type: integer
          description: Number of detail page views
        clicks:
          type: integer
          description: Number of website/call clicks
        period_days:
          type: integer
          description: Lookback period in days

    FeedbackSubmit:
      type: object
      required:
        - feedback_type
      properties:
        service_id:
          type: string
          nullable: true
        feedback_type:
          type: string
          enum: [helpful_yes, helpful_no, issue, not_found]
        message:
          type: string
          maxLength: 1000
          nullable: true
        category_searched:
          type: string
          enum:
            - Food
            - Crisis
            - Housing
            - Health
            - Legal
            - Financial
            - Employment
            - Education
            - Transport
            - Community
            - Indigenous
          nullable: true

    PlainLanguageSummary:
      type: object
      properties:
        service_id:
          type: string
        summary_en:
          type: string
        summary_fr:
          type: string
          nullable: true
        how_to_use_en:
          type: string
        how_to_use_fr:
          type: string
          nullable: true

    PilotContactAttemptCreate:
      type: object
      required:
        - pilot_cycle_id
        - service_id
        - recorded_by_org_id
        - entity_key_hash
        - attempt_channel
        - attempt_outcome
        - attempted_at
      properties:
        pilot_cycle_id:
          type: string
        service_id:
          type: string
        recorded_by_org_id:
          type: string
          format: uuid
        entity_key_hash:
          type: string
          pattern: "^[0-9a-fA-F]{64}$"
          description: Opaque SHA-256 hex digest used for repeat-failure attribution. Raw identifiers are not accepted.
        attempt_channel:
          type: string
          enum: [phone, website, email, in_person, referral]
        attempt_outcome:
          type: string
          enum: [connected, disconnected_number, no_response, intake_unavailable, invalid_routing, other_failure]
        attempted_at:
          type: string
          format: date-time
        resolved_at:
          type: string
          format: date-time
          nullable: true
        outcome_notes_code:
          type: string
          nullable: true

    PilotReferralCreate:
      type: object
      required:
        - pilot_cycle_id
        - source_org_id
        - target_service_id
        - referral_state
        - created_at
        - updated_at
      properties:
        pilot_cycle_id:
          type: string
        source_org_id:
          type: string
          format: uuid
        target_service_id:
          type: string
        referral_state:
          type: string
          enum: [initiated, connected, failed, client_withdrew, no_response_timeout]
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        terminal_at:
          type: string
          format: date-time
          nullable: true
        failure_reason_code:
          type: string
          nullable: true

    PilotReferralUpdate:
      type: object
      required:
        - source_org_id
        - referral_state
        - updated_at
      properties:
        source_org_id:
          type: string
          format: uuid
        referral_state:
          type: string
          enum: [initiated, connected, failed, client_withdrew, no_response_timeout]
        updated_at:
          type: string
          format: date-time
        terminal_at:
          type: string
          format: date-time
          nullable: true
        failure_reason_code:
          type: string
          nullable: true

    IntegrationFeasibilityDecision:
      type: object
      required:
        - decision
        - decision_date
        - redline_checklist_version
        - violations
        - compensating_controls
        - owners
      properties:
        decision:
          type: string
          enum: [go, conditional, blocked]
        decision_date:
          type: string
          format: date
        redline_checklist_version:
          type: string
        violations:
          type: array
          items:
            type: string
            enum:
              - raw_query_text_required
              - forced_user_identifying_telemetry
              - reidentification_capability_required
              - retention_policy_conflict
              - auditability_gap
        compensating_controls:
          type: array
          items:
            type: string
        owners:
          type: array
          items:
            type: string

    PilotScorecard:
      type: object
      properties:
        pilot_cycle_id:
          type: string
        generated_at:
          type: string
          format: date-time
        m1_failed_contact_rate:
          type: number
          nullable: true
        m2_p50_seconds_to_connection:
          type: number
          nullable: true
        m2_p75_seconds_to_connection:
          type: number
          nullable: true
        m2_p90_seconds_to_connection:
          type: number
          nullable: true
        m3_referral_completion_capture_rate:
          type: number
          nullable: true
        m4_freshness_sla_compliance:
          type: number
          nullable: true
        m5_repeat_failure_rate:
          type: number
          nullable: true
        m6_data_decay_fatal_error_rate:
          type: number
          nullable: true
        m7_preference_fit_indicator:
          type: number
          nullable: true

    Error:
      type: object
      properties:
        error:
          type: string

  responses:
    InternalError:
      description: Internal Server Error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
