openapi: 3.0.3
info:
  title: NovaTrek Adventures - Guest Profiles Service
  description: |
    Manages guest registration, profile management, preferences, medical
    information, and certifications for NovaTrek Adventures outdoor recreation
    experiences. This service is the authoritative source for guest identity
    and readiness data consumed by reservation, scheduling, and safety systems.

    ## Related Services
    - **svc-reservations** - Adventure booking and scheduling (references `guest_id`)
    - **svc-adventures** - Adventure catalog and requirements (references certification types)
    - **svc-safety** - Incident reporting and safety compliance
  version: 2.4.0
  contact:
    name: NovaTrek Platform Engineering
    email: platform-eng@novatrek.example.com
  license:
    name: Proprietary
    url: https://novatrek.example.com/legal/api-license

servers:
  - url: https://api.novatrek.example.com/guests/v1
    description: Production
  - url: https://api-staging.novatrek.example.com/guests/v1
    description: Staging

tags:
  - name: Guests
    description: Guest registration and profile management
  - name: Certifications
    description: Adventure certifications and qualifications
  - name: Medical
    description: Guest medical information and clearances
  - name: History
    description: Adventure participation history

paths:
  /guests:
    get:
      operationId: searchGuests
      summary: Search guests
      description: |
        Search and filter the guest registry. Supports partial name matching,
        email lookup, status filtering, and certification-based queries.
        Results are paginated and sorted by last name ascending by default.
      tags:
        - Guests
      parameters:
        - name: q
          in: query
          description: Free-text search across name and email fields
          schema:
            type: string
            maxLength: 200
        - name: email
          in: query
          description: Exact email address match
          schema:
            type: string
            format: email
        - name: status
          in: query
          description: Filter by guest account status
          schema:
            $ref: '#/components/schemas/GuestStatus'
        - name: certification_type
          in: query
          description: Filter guests holding a specific certification
          schema:
            $ref: '#/components/schemas/CertificationType'
        - name: page
          in: query
          description: Page number (1-based)
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: page_size
          in: query
          description: Number of results per page
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: sort_by
          in: query
          description: Field to sort results by
          schema:
            type: string
            enum:
              - last_name
              - created_at
              - updated_at
            default: last_name
        - name: sort_order
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: asc
      responses:
        '200':
          description: Paginated list of guests matching the search criteria
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GuestSearchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          $ref: '#/components/responses/InternalServerError'

    post:
      operationId: createGuest
      summary: Register a new guest
      description: |
        Create a new guest profile. The email address must be unique across
        all active and suspended accounts. Deactivated accounts with the same
        email may exist but will not conflict.
      tags:
        - Guests
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateGuestRequest'
      responses:
        '201':
          description: Guest successfully registered
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Guest'
          headers:
            Location:
              description: URI of the newly created guest resource
              schema:
                type: string
                format: uri
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: A guest with this email address already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /guests/{guest_id}:
    parameters:
      - $ref: '#/components/parameters/GuestId'

    get:
      operationId: getGuest
      summary: Get guest profile
      description: Retrieve the full profile for a specific guest by ID.
      tags:
        - Guests
      responses:
        '200':
          description: Guest profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Guest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

    patch:
      operationId: updateGuest
      summary: Update guest profile
      description: |
        Partially update a guest profile. Only the fields provided in the
        request body will be modified. To update medical information or
        certifications, use the dedicated endpoints.
      tags:
        - Guests
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateGuestRequest'
      responses:
        '200':
          description: Guest profile updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Guest'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Conflict - email address already in use by another guest
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /guests/{guest_id}/certifications:
    parameters:
      - $ref: '#/components/parameters/GuestId'

    get:
      operationId: listGuestCertifications
      summary: List guest certifications
      description: |
        Retrieve all certifications on file for a guest, including expired
        certifications. Use the `active_only` parameter to filter to current,
        non-expired certifications.
      tags:
        - Certifications
      parameters:
        - name: active_only
          in: query
          description: When true, exclude expired certifications
          schema:
            type: boolean
            default: false
        - name: type
          in: query
          description: Filter by certification type
          schema:
            $ref: '#/components/schemas/CertificationType'
      responses:
        '200':
          description: List of certifications
          content:
            application/json:
              schema:
                type: object
                properties:
                  guest_id:
                    type: string
                    format: uuid
                  certifications:
                    type: array
                    items:
                      $ref: '#/components/schemas/Certification'
                  total_count:
                    type: integer
                    description: Total number of certifications returned
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

    post:
      operationId: addGuestCertification
      summary: Add a certification
      description: |
        Record a new certification for a guest. Certification documents
        (scanned cards, digital badges) can be attached via the
        `verification_document_url` field. Duplicate active certifications
        of the same type are not permitted.
      tags:
        - Certifications
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateCertificationRequest'
      responses:
        '201':
          description: Certification added successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Certification'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Guest already holds an active certification of this type
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /guests/{guest_id}/medical-info:
    parameters:
      - $ref: '#/components/parameters/GuestId'

    get:
      operationId: getGuestMedicalInfo
      summary: Get guest medical information
      description: |
        Retrieve the medical information on file for a guest. Access to this
        endpoint requires elevated permissions (role: MEDICAL_STAFF or
        SAFETY_OFFICER). Medical data is encrypted at rest and all access
        is audit-logged.
      tags:
        - Medical
      responses:
        '200':
          description: Guest medical information
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MedicalInfo'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Insufficient permissions to access medical data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

    put:
      operationId: updateGuestMedicalInfo
      summary: Update guest medical information
      description: |
        Replace the medical information record for a guest. This is a full
        replacement operation; all fields must be provided. Partial updates
        are not supported for medical data to ensure completeness and
        consistency of health records.
      tags:
        - Medical
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MedicalInfoRequest'
      responses:
        '200':
          description: Medical information updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MedicalInfo'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Insufficient permissions to modify medical data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /guests/{guest_id}/adventure-history:
    parameters:
      - $ref: '#/components/parameters/GuestId'

    get:
      operationId: getGuestAdventureHistory
      summary: Get guest adventure history
      description: |
        Retrieve the adventure participation history for a guest. Each entry
        references a reservation from **svc-reservations** via `reservation_id`.
        Results are sorted by participation date descending (most recent first).
      tags:
        - History
      parameters:
        - name: from_date
          in: query
          description: Filter adventures on or after this date (inclusive)
          schema:
            type: string
            format: date
        - name: to_date
          in: query
          description: Filter adventures on or before this date (inclusive)
          schema:
            type: string
            format: date
        - name: adventure_type
          in: query
          description: Filter by adventure category
          schema:
            type: string
            enum:
              - ROCK_CLIMBING
              - SCUBA_DIVING
              - WHITEWATER_RAFTING
              - BACKCOUNTRY_SKIING
              - WILDERNESS_TREKKING
              - CANYONEERING
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: page_size
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
      responses:
        '200':
          description: Paginated adventure history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdventureHistoryResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  parameters:
    GuestId:
      name: guest_id
      in: path
      required: true
      description: Unique identifier for the guest
      schema:
        type: string
        format: uuid

  schemas:
    GuestStatus:
      type: string
      description: Current status of the guest account
      enum:
        - ACTIVE
        - SUSPENDED
        - DEACTIVATED

    CertificationType:
      type: string
      description: |
        Recognized adventure certification types. Each certification
        corresponds to specific adventure eligibility requirements.
      enum:
        - ROCK_CLIMBING_L1
        - ROCK_CLIMBING_L2
        - SCUBA_OPEN_WATER
        - SCUBA_ADVANCED
        - WILDERNESS_FIRST_AID
        - SWIFT_WATER_RESCUE
        - AVALANCHE_SAFETY

    Guest:
      type: object
      description: Full guest profile record
      required:
        - guest_id
        - first_name
        - last_name
        - email
        - status
        - created_at
        - updated_at
      properties:
        guest_id:
          type: string
          format: uuid
          readOnly: true
          description: System-generated unique guest identifier
        first_name:
          type: string
          minLength: 1
          maxLength: 100
          example: Jordan
        last_name:
          type: string
          minLength: 1
          maxLength: 100
          example: Castillo
        email:
          type: string
          format: email
          example: jordan.castillo@example.com
        phone:
          type: string
          pattern: '^\+[1-9]\d{1,14}$'
          description: Phone number in E.164 format
          example: '+14155551234'
        date_of_birth:
          type: string
          format: date
          example: '1992-03-15'
        status:
          $ref: '#/components/schemas/GuestStatus'
        preferences:
          $ref: '#/components/schemas/GuestPreferences'
        emergency_contact:
          $ref: '#/components/schemas/EmergencyContact'
        profile_image_url:
          type: string
          format: uri
          description: URL to the guest's profile photo
        loyalty_tier:
          type: string
          enum:
            - EXPLORER
            - PATHFINDER
            - SUMMIT
            - APEX
          description: Current loyalty program tier
        total_adventures:
          type: integer
          readOnly: true
          description: Lifetime count of completed adventures
          example: 14
        created_at:
          type: string
          format: date-time
          readOnly: true
        updated_at:
          type: string
          format: date-time
          readOnly: true

    GuestPreferences:
      type: object
      description: Guest adventure and communication preferences
      properties:
        preferred_adventure_types:
          type: array
          items:
            type: string
            enum:
              - ROCK_CLIMBING
              - SCUBA_DIVING
              - WHITEWATER_RAFTING
              - BACKCOUNTRY_SKIING
              - WILDERNESS_TREKKING
              - CANYONEERING
          description: Adventure categories the guest is interested in
        difficulty_preference:
          type: string
          enum:
            - BEGINNER
            - INTERMEDIATE
            - ADVANCED
            - EXPERT
          description: Preferred adventure difficulty level
        group_size_preference:
          type: string
          enum:
            - SOLO
            - SMALL_GROUP
            - LARGE_GROUP
            - NO_PREFERENCE
          default: NO_PREFERENCE
        communication_opt_in:
          type: boolean
          default: true
          description: Whether the guest opts in to marketing communications
        preferred_language:
          type: string
          description: ISO 639-1 language code
          example: en
          default: en
        dietary_restrictions:
          type: array
          items:
            type: string
          description: Dietary restrictions for catered adventures
          example:
            - vegetarian
            - gluten-free

    EmergencyContact:
      type: object
      description: Emergency contact details for the guest
      required:
        - name
        - phone
        - relationship
      properties:
        name:
          type: string
          maxLength: 200
          example: Taylor Castillo
        phone:
          type: string
          pattern: '^\+[1-9]\d{1,14}$'
          example: '+14155559876'
        relationship:
          type: string
          maxLength: 50
          example: Spouse

    MedicalInfo:
      type: object
      description: |
        Guest medical information. This data is classified as sensitive PII
        and is subject to additional access controls and encryption.
      required:
        - guest_id
        - blood_type
        - has_allergies
        - fitness_clearance
        - last_updated
      properties:
        guest_id:
          type: string
          format: uuid
          readOnly: true
        blood_type:
          type: string
          enum:
            - A_POSITIVE
            - A_NEGATIVE
            - B_POSITIVE
            - B_NEGATIVE
            - AB_POSITIVE
            - AB_NEGATIVE
            - O_POSITIVE
            - O_NEGATIVE
            - UNKNOWN
        has_allergies:
          type: boolean
        allergies:
          type: array
          items:
            type: string
          description: List of known allergies
          example:
            - bee stings
            - penicillin
        medical_conditions:
          type: array
          items:
            type: string
          description: Relevant medical conditions (e.g., asthma, vertigo)
          example:
            - mild asthma
        current_medications:
          type: array
          items:
            type: string
          description: Medications the guest currently takes
        fitness_clearance:
          type: string
          enum:
            - FULL_CLEARANCE
            - LIMITED_CLEARANCE
            - RESTRICTED
            - PENDING_REVIEW
          description: |
            Overall fitness clearance level. RESTRICTED guests cannot
            participate in strenuous adventures.
        fitness_clearance_expiry:
          type: string
          format: date
          description: Date the fitness clearance expires and must be renewed
        physician_name:
          type: string
          maxLength: 200
          description: Name of the certifying physician
        physician_phone:
          type: string
          pattern: '^\+[1-9]\d{1,14}$'
        notes:
          type: string
          maxLength: 2000
          description: Additional medical notes from screening staff
        last_updated:
          type: string
          format: date-time
          readOnly: true

    MedicalInfoRequest:
      type: object
      description: Request body for creating or replacing medical information
      required:
        - blood_type
        - has_allergies
        - fitness_clearance
      properties:
        blood_type:
          type: string
          enum:
            - A_POSITIVE
            - A_NEGATIVE
            - B_POSITIVE
            - B_NEGATIVE
            - AB_POSITIVE
            - AB_NEGATIVE
            - O_POSITIVE
            - O_NEGATIVE
            - UNKNOWN
        has_allergies:
          type: boolean
        allergies:
          type: array
          items:
            type: string
        medical_conditions:
          type: array
          items:
            type: string
        current_medications:
          type: array
          items:
            type: string
        fitness_clearance:
          type: string
          enum:
            - FULL_CLEARANCE
            - LIMITED_CLEARANCE
            - RESTRICTED
            - PENDING_REVIEW
        fitness_clearance_expiry:
          type: string
          format: date
        physician_name:
          type: string
          maxLength: 200
        physician_phone:
          type: string
          pattern: '^\+[1-9]\d{1,14}$'
        notes:
          type: string
          maxLength: 2000

    Certification:
      type: object
      description: |
        An adventure certification held by a guest. Certifications determine
        eligibility for specific adventure types and difficulty levels.
      required:
        - certification_id
        - guest_id
        - type
        - issued_date
        - status
      properties:
        certification_id:
          type: string
          format: uuid
          readOnly: true
        guest_id:
          type: string
          format: uuid
          readOnly: true
        type:
          $ref: '#/components/schemas/CertificationType'
        issuing_organization:
          type: string
          maxLength: 200
          description: Name of the organization that issued the certification
          example: American Alpine Club
        credential_number:
          type: string
          maxLength: 100
          description: External credential or card number
        issued_date:
          type: string
          format: date
          description: Date the certification was issued
        expiry_date:
          type: string
          format: date
          description: Date the certification expires (null if non-expiring)
        status:
          type: string
          enum:
            - ACTIVE
            - EXPIRED
            - REVOKED
          description: Current status of the certification
        verification_document_url:
          type: string
          format: uri
          description: URL to the uploaded verification document or badge
        verified_by:
          type: string
          description: Staff member who verified the certification
        verified_at:
          type: string
          format: date-time
          description: Timestamp when the certification was verified by staff
        created_at:
          type: string
          format: date-time
          readOnly: true

    CreateCertificationRequest:
      type: object
      required:
        - type
        - issued_date
      properties:
        type:
          $ref: '#/components/schemas/CertificationType'
        issuing_organization:
          type: string
          maxLength: 200
        credential_number:
          type: string
          maxLength: 100
        issued_date:
          type: string
          format: date
        expiry_date:
          type: string
          format: date
        verification_document_url:
          type: string
          format: uri

    AdventureHistoryEntry:
      type: object
      description: |
        A single adventure participation record. Cross-references the
        svc-reservations service for booking details.
      required:
        - entry_id
        - guest_id
        - reservation_id
        - adventure_name
        - adventure_type
        - participation_date
        - status
      properties:
        entry_id:
          type: string
          format: uuid
          readOnly: true
        guest_id:
          type: string
          format: uuid
          readOnly: true
        reservation_id:
          type: string
          format: uuid
          description: |
            Reference to the booking in svc-reservations. Use
            GET /reservations/{reservation_id} for full booking details.
        adventure_name:
          type: string
          description: Display name of the adventure
          example: Summit Ridge Ascent
        adventure_type:
          type: string
          enum:
            - ROCK_CLIMBING
            - SCUBA_DIVING
            - WHITEWATER_RAFTING
            - BACKCOUNTRY_SKIING
            - WILDERNESS_TREKKING
            - CANYONEERING
        difficulty_level:
          type: string
          enum:
            - BEGINNER
            - INTERMEDIATE
            - ADVANCED
            - EXPERT
        participation_date:
          type: string
          format: date
        location:
          type: string
          description: Adventure location or trailhead
          example: Granite Peaks Base Camp
        guide_name:
          type: string
          description: Assigned guide for the adventure
        status:
          type: string
          enum:
            - COMPLETED
            - CANCELLED
            - NO_SHOW
            - IN_PROGRESS
          description: Outcome of the guest's participation
        rating:
          type: integer
          minimum: 1
          maximum: 5
          description: Guest's post-adventure rating (1-5 stars)
        feedback:
          type: string
          maxLength: 1000
          description: Guest's written feedback

    AdventureHistoryResponse:
      type: object
      properties:
        guest_id:
          type: string
          format: uuid
        entries:
          type: array
          items:
            $ref: '#/components/schemas/AdventureHistoryEntry'
        pagination:
          $ref: '#/components/schemas/PaginationMeta'

    GuestSearchResponse:
      type: object
      properties:
        guests:
          type: array
          items:
            $ref: '#/components/schemas/Guest'
        pagination:
          $ref: '#/components/schemas/PaginationMeta'

    PaginationMeta:
      type: object
      description: Standard pagination metadata
      properties:
        page:
          type: integer
          example: 1
        page_size:
          type: integer
          example: 25
        total_items:
          type: integer
          example: 142
        total_pages:
          type: integer
          example: 6

    CreateGuestRequest:
      type: object
      required:
        - first_name
        - last_name
        - email
        - date_of_birth
        - emergency_contact
      properties:
        first_name:
          type: string
          minLength: 1
          maxLength: 100
        last_name:
          type: string
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
        phone:
          type: string
          pattern: '^\+[1-9]\d{1,14}$'
        date_of_birth:
          type: string
          format: date
        preferences:
          $ref: '#/components/schemas/GuestPreferences'
        emergency_contact:
          $ref: '#/components/schemas/EmergencyContact'

    UpdateGuestRequest:
      type: object
      description: |
        Partial update payload. Only provided fields will be modified.
        At least one field must be present.
      minProperties: 1
      properties:
        first_name:
          type: string
          minLength: 1
          maxLength: 100
        last_name:
          type: string
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
        phone:
          type: string
          pattern: '^\+[1-9]\d{1,14}$'
        date_of_birth:
          type: string
          format: date
        status:
          $ref: '#/components/schemas/GuestStatus'
        preferences:
          $ref: '#/components/schemas/GuestPreferences'
        emergency_contact:
          $ref: '#/components/schemas/EmergencyContact'
        profile_image_url:
          type: string
          format: uri

    ErrorResponse:
      type: object
      description: Standard error response envelope
      required:
        - error_code
        - message
        - timestamp
      properties:
        error_code:
          type: string
          description: Machine-readable error code
          example: GUEST_NOT_FOUND
        message:
          type: string
          description: Human-readable error description
          example: No guest found with the specified ID
        details:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
                description: Field that caused the error
              reason:
                type: string
                description: Specific validation failure reason
          description: Field-level validation errors (if applicable)
        trace_id:
          type: string
          format: uuid
          description: Correlation ID for distributed tracing
        timestamp:
          type: string
          format: date-time

  responses:
    BadRequest:
      description: Invalid request parameters or body
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

    Unauthorized:
      description: Missing or invalid authentication credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

    NotFound:
      description: The requested resource was not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

    InternalServerError:
      description: An unexpected internal error occurred
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token issued by the NovaTrek identity provider

security:
  - BearerAuth: []
