openapi: 3.1.0
info:
  title: Glucose Service API
  description: |
    Production-grade API for aggregating continuous glucose monitoring (CGM) data 
    from multiple providers (LibreLinkUp, Dexcom).
    
    ## Authentication
    All endpoints (except health checks) require authentication via API Gateway.
    The gateway validates Clerk JWTs and passes `X-Auth-User-ID` header.
    
    ## Rate Limiting
    - 100 requests per minute per user
    - Rate limit headers included in all responses
    
    ## Error Handling
    All errors follow RFC 7807 Problem Details format.
  version: 1.0.0
  contact:
    name: API Support
    email: support@yourapp.com
  license:
    name: Proprietary
    url: https://yourapp.com/terms

servers:
  - url: https://glucoseservice.api.gluciq.com/api/v1
    description: Production
  - url: https://glucoseservice.defeat.site/api/v1
    description: Staging
  - url: http://localhost:8080/api/v1
    description: Local development

tags:
  - name: Health
    description: Health check endpoints
  - name: Auth
    description: User authentication and registration
  - name: Providers
    description: CGM provider management
  - name: Libre
    description: LibreLinkUp specific endpoints
  - name: Glucose
    description: Glucose data access
  - name: Settings
    description: User preferences and target ranges
  - name: Sync
    description: Background sync configuration
  - name: Dashboard
    description: Dashboard data aggregation

paths:
  /health:
    get:
      tags:
        - Health
      summary: Liveness probe
      description: Returns service status. Used by orchestrator for liveness checks.
      operationId: getHealth
      security: []
      responses:
        '200':
          description: Service is alive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: up
                timestamp: '2025-12-19T12:00:00Z'

  /health/ready:
    get:
      tags:
        - Health
      summary: Readiness probe
      description: Returns service readiness with dependency health. Used by orchestrator for readiness checks.
      operationId: getHealthReady
      security: []
      responses:
        '200':
          description: Service is ready to accept traffic
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReadinessResponse'
              example:
                status: ready
                checks:
                  database:
                    status: up
                    latency_ms: 5
                  redis:
                    status: up
                    latency_ms: 1
                  libre:
                    status: up
                    circuit: closed
                  dexcom:
                    status: up
                    circuit: closed
        '503':
          description: Service is not ready
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReadinessResponse'

  /providers:
    get:
      tags:
        - Providers
      summary: List linked providers
      description: Returns all CGM providers linked to the authenticated user.
      operationId: listProviders
      responses:
        '200':
          description: List of linked providers
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProvidersResponse'
              example:
                data:
                  - id: 550e8400-e29b-41d4-a716-446655440000
                    type: libre
                    status: active
                    lastSyncAt: '2025-12-19T11:55:00Z'
                    linkedAt: '2025-12-01T10:00:00Z'
                  - id: 550e8400-e29b-41d4-a716-446655440001
                    type: dexcom
                    status: active
                    lastSyncAt: '2025-12-19T11:57:00Z'
                    linkedAt: '2025-12-05T14:30:00Z'
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /providers/libre/link:
    post:
      tags:
        - Providers
      summary: Link LibreLinkUp account
      description: |
        Links a LibreLinkUp account using email and password. The password is used 
        once to authenticate with LibreLinkUp, then discarded. Only the resulting 
        JWT token is stored (encrypted).
      operationId: linkLibre
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkLibreRequest'
            example:
              email: user@example.com
              password: secret123
              region: us
      responses:
        '201':
          description: Provider linked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkProviderResponse'
              example:
                data:
                  id: 550e8400-e29b-41d4-a716-446655440000
                  type: libre
                  status: active
                  linkedAt: '2025-12-19T12:00:00Z'
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Provider already linked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
              example:
                type: /errors/provider-already-linked
                title: Provider Already Linked
                status: 409
                detail: You already have a LibreLinkUp account linked. Unlink it first to link a different account.
                instance: /providers/libre/link
                requestId: req_abc123
        '422':
          description: Invalid credentials
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
              example:
                type: /errors/invalid-credentials
                title: Invalid Credentials
                status: 422
                detail: The email or password is incorrect. Please check your LibreLinkUp credentials.
                instance: /providers/libre/link
                requestId: req_abc123

  /providers/dexcom/authorize:
    get:
      tags:
        - Providers
      summary: Get Dexcom authorization URL
      description: |
        Returns the Dexcom OAuth2 authorization URL. The mobile app should open 
        this URL in a secure browser (ASWebAuthenticationSession on iOS, Chrome 
        Custom Tabs on Android).
      operationId: getDexcomAuthUrl
      parameters:
        - name: redirectUri
          in: query
          description: The URI to redirect to after authorization (must be registered with Dexcom)
          required: true
          schema:
            type: string
            format: uri
          example: yourapp://dexcom/callback
      responses:
        '200':
          description: Authorization URL generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DexcomAuthResponse'
              example:
                data:
                  authorizationUrl: https://api.dexcom.com/v2/oauth2/login?client_id=xxx&redirect_uri=yourapp://dexcom/callback&response_type=code&scope=offline_access&state=abc123
                  state: abc123
                  expiresAt: '2025-12-19T12:15:00Z'
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /providers/dexcom/callback:
    post:
      tags:
        - Providers
      summary: Complete Dexcom OAuth
      description: |
        Completes the Dexcom OAuth2 flow by exchanging the authorization code 
        for tokens. The mobile app calls this after receiving the redirect 
        from Dexcom with the authorization code.
      operationId: dexcomCallback
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DexcomCallbackRequest'
            example:
              code: auth_code_from_dexcom
              state: abc123
      responses:
        '201':
          description: Provider linked successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkProviderResponse'
              example:
                data:
                  id: 550e8400-e29b-41d4-a716-446655440001
                  type: dexcom
                  status: active
                  linkedAt: '2025-12-19T12:00:00Z'
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Provider already linked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
        '422':
          description: Invalid or expired state/code
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
              example:
                type: /errors/oauth-state-invalid
                title: Invalid OAuth State
                status: 422
                detail: The OAuth state has expired or is invalid. Please restart the linking process.
                instance: /providers/dexcom/callback
                requestId: req_abc123

  /providers/{providerId}:
    delete:
      tags:
        - Providers
      summary: Unlink provider
      description: |
        Unlinks a CGM provider. This deletes stored credentials but retains 
        historical glucose readings.
      operationId: unlinkProvider
      parameters:
        - name: providerId
          in: path
          required: true
          description: The provider ID to unlink
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Provider unlinked successfully
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Provider not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
              example:
                type: /errors/provider-not-found
                title: Provider Not Found
                status: 404
                detail: No provider found with the given ID.
                instance: /providers/550e8400-e29b-41d4-a716-446655440000
                requestId: req_abc123

  /providers/{providerId}/sync:
    post:
      tags:
        - Providers
      summary: Sync provider data
      description: |
        Triggers an on-demand data sync from the provider. Fetches the latest 
        3 hours of glucose data and stores it in the database.
      operationId: syncProvider
      parameters:
        - name: providerId
          in: path
          required: true
          description: The provider ID to sync
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Sync completed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncResponse'
              example:
                data:
                  readingsCount: 36
                  latestReading:
                    valueMgdl: 120
                    valueMmol: 6.7
                    timestamp: '2025-12-19T12:00:00Z'
                    trend: stable
                    trendArrow: →
                    source: libre
                    providerId: 550e8400-e29b-41d4-a716-446655440000
                  syncedAt: '2025-12-19T12:00:00Z'
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: Provider not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'

  /glucose/latest:
    get:
      tags:
        - Glucose
      summary: Get latest glucose reading
      description: |
        Returns the most recent glucose reading across all linked providers. 
        Data is cached for up to 5 minutes unless `forceRefresh=true`.
      operationId: getLatestGlucose
      parameters:
        - name: forceRefresh
          in: query
          description: Bypass cache and fetch fresh data from providers
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Latest glucose reading
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LatestGlucoseResponse'
              example:
                data:
                  valueMgdl: 120
                  valueMmol: 6.7
                  timestamp: '2025-12-19T11:55:00Z'
                  trend: stable
                  trendArrow: →
                  trendRate: 0.5
                  source: libre
                  providerId: 550e8400-e29b-41d4-a716-446655440000
                  isStale: false
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
                  cached: true
                  cachedAt: '2025-12-19T11:57:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          description: No readings available
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
              example:
                type: /errors/no-readings
                title: No Readings Available
                status: 404
                detail: No glucose readings found. Please ensure you have a provider linked and synced.
                instance: /glucose/latest
                requestId: req_abc123

  /glucose/history:
    get:
      tags:
        - Glucose
      summary: Get glucose history
      description: |
        Returns historical glucose readings within a date range. Results are 
        paginated using cursor-based pagination. Maximum 90 days of history.
      operationId: getGlucoseHistory
      parameters:
        - name: startDate
          in: query
          description: Start of date range (inclusive)
          required: true
          schema:
            type: string
            format: date-time
          example: '2025-12-18T00:00:00Z'
        - name: endDate
          in: query
          description: End of date range (exclusive)
          required: true
          schema:
            type: string
            format: date-time
          example: '2025-12-19T00:00:00Z'
        - name: limit
          in: query
          description: Maximum number of readings to return
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
        - name: cursor
          in: query
          description: Pagination cursor from previous response
          schema:
            type: string
        - name: providerId
          in: query
          description: Filter by specific provider
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Glucose history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GlucoseHistoryResponse'
              example:
                data:
                  - valueMgdl: 120
                    valueMmol: 6.7
                    timestamp: '2025-12-18T23:55:00Z'
                    trend: stable
                    source: libre
                    providerId: 550e8400-e29b-41d4-a716-446655440000
                  - valueMgdl: 115
                    valueMmol: 6.4
                    timestamp: '2025-12-18T23:50:00Z'
                    trend: falling
                    source: libre
                    providerId: 550e8400-e29b-41d4-a716-446655440000
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
                pagination:
                  nextCursor: eyJpZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCIsInRzIjoiMjAyNS0xMi0xOFQyMzo1MDowMFoifQ==
                  hasMore: true
                  totalCount: 288
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /glucose/stats:
    get:
      tags:
        - Glucose
      summary: Get glucose statistics
      description: |
        Returns aggregated glucose statistics for a time period. Useful for 
        charts and dashboards. Includes time-in-range, average, min/max, and GMI.
      operationId: getGlucoseStats
      parameters:
        - name: period
          in: query
          description: Predefined time period
          schema:
            type: string
            enum: [3h, 6h, 12h, 24h, 7d, 14d, 30d, 90d]
            default: 24h
        - name: startDate
          in: query
          description: Custom start date (overrides period)
          schema:
            type: string
            format: date-time
        - name: endDate
          in: query
          description: Custom end date (overrides period)
          schema:
            type: string
            format: date-time
      responses:
        '200':
          description: Glucose statistics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GlucoseStatsResponse'
              example:
                data:
                  period: 24h
                  startDate: '2025-12-18T12:00:00Z'
                  endDate: '2025-12-19T12:00:00Z'
                  readingsCount: 288
                  average: 125.5
                  minimum: 72
                  maximum: 195
                  stdDev: 28.3
                  timeInRange: 78.5
                  timeLow: 3.2
                  timeHigh: 18.3
                  gmi: 6.3
                meta:
                  requestId: req_abc123
                  timestamp: '2025-12-19T12:00:00Z'
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ============================================================================
  # AUTH ENDPOINTS
  # ============================================================================

  /auth/register:
    post:
      tags:
        - Auth
      summary: Register a new user
      description: Creates a new user account with email and password.
      operationId: registerUser
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
      responses:
        '201':
          description: User registered successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '409':
          description: Email already registered

  /auth/login:
    post:
      tags:
        - Auth
      summary: Login user
      description: Authenticates user and returns JWT token.
      operationId: loginUser
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthResponse'
        '401':
          description: Invalid credentials

  /auth/me:
    get:
      tags:
        - Auth
      summary: Get current user profile
      description: Returns the authenticated user's profile.
      operationId: getCurrentUser
      responses:
        '200':
          description: User profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ============================================================================
  # SETTINGS ENDPOINTS
  # ============================================================================

  /settings:
    get:
      tags:
        - Settings
      summary: Get user settings
      description: Returns the authenticated user's settings.
      operationId: getUserSettings
      responses:
        '200':
          description: User settings
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSettings'
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      tags:
        - Settings
      summary: Update user settings
      description: Updates user preferences.
      operationId: updateUserSettings
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSettingsRequest'
      responses:
        '200':
          description: Settings updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserSettings'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /settings/targets:
    get:
      tags:
        - Settings
      summary: Get target ranges
      description: Returns the user's glucose target ranges.
      operationId: getTargetRanges
      responses:
        '200':
          description: Target ranges
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TargetRanges'
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      tags:
        - Settings
      summary: Update target ranges
      description: Updates custom glucose target ranges (e.g., 80-150).
      operationId: updateTargetRanges
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTargetRangesRequest'
      responses:
        '200':
          description: Targets updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PatientSettingsResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ============================================================================
  # LIBRE ENDPOINTS
  # ============================================================================

  /libre/link:
    post:
      tags:
        - Libre
      summary: Link LibreLinkUp account
      description: Links a LibreLinkUp account using email and password.
      operationId: linkLibreAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LinkLibreRequest'
      responses:
        '201':
          description: Account linked
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LinkProviderResponse'
        '409':
          description: Already linked

  /libre/unlink:
    delete:
      tags:
        - Libre
      summary: Unlink LibreLinkUp account
      description: Removes the linked LibreLinkUp account.
      operationId: unlinkLibreAccount
      responses:
        '200':
          description: Account unlinked
        '404':
          description: No Libre account linked

  /libre/sync:
    post:
      tags:
        - Libre
      summary: Sync LibreLinkUp data
      description: Triggers a manual sync from LibreLinkUp.
      operationId: syncLibre
      responses:
        '200':
          description: Sync completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  readingsSync:
                    type: integer

  /libre/patient:
    get:
      tags:
        - Libre
      summary: Get patient info
      description: Returns patient and sensor information from LibreLinkUp.
      operationId: getLibrePatient
      responses:
        '200':
          description: Patient info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LibrePatientInfo'

  /libre/sensor:
    get:
      tags:
        - Libre
      summary: Get sensor info
      description: Returns current sensor details (serial, days remaining).
      operationId: getLibreSensor
      responses:
        '200':
          description: Sensor info
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SensorInfo'
        '404':
          description: No sensor found

  /libre/connections:
    get:
      tags:
        - Libre
      summary: List LibreLinkUp connections
      description: Returns all LibreLinkUp connections for the user.
      operationId: getLibreConnections
      responses:
        '200':
          description: Connections list
          content:
            application/json:
              schema:
                type: object
                properties:
                  connections:
                    type: array
                    items:
                      $ref: '#/components/schemas/Provider'
                  count:
                    type: integer

  /libre/alarms:
    get:
      tags:
        - Libre
      summary: Get alarm settings
      description: Returns alarm thresholds from LibreLinkUp.
      operationId: getLibreAlarms
      responses:
        '200':
          description: Alarm settings
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AlarmSettings'

  # ============================================================================
  # SYNC ENDPOINTS
  # ============================================================================

  /sync/status:
    get:
      tags:
        - Sync
      summary: Get sync status
      description: Returns background sync status and configuration.
      operationId: getSyncStatus
      responses:
        '200':
          description: Sync status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncStatusResponse'

  /sync/config:
    put:
      tags:
        - Sync
      summary: Update sync configuration
      description: Updates background sync settings (premium feature).
      operationId: updateSyncConfig
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSyncConfigRequest'
      responses:
        '200':
          description: Config updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SyncConfigResponse'
        '403':
          description: Premium subscription required

  /sync/trigger:
    post:
      tags:
        - Sync
      summary: Trigger manual sync
      description: Triggers an immediate sync for all providers.
      operationId: triggerSync
      responses:
        '200':
          description: Sync triggered
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  providersSynced:
                    type: integer
                  totalProviders:
                    type: integer

  # ============================================================================
  # DASHBOARD ENDPOINT
  # ============================================================================

  /dashboard:
    get:
      tags:
        - Dashboard
      summary: Get dashboard data
      description: Returns all data needed for dashboard in a single call.
      operationId: getDashboard
      responses:
        '200':
          description: Dashboard data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DashboardResponse'

  /users/search:
    get:
      tags:
        - Users
      summary: Search users by email
      description: |
        Searches for users by email address. Returns limited results for performance.
        Primarily intended for admin use in dashboards.
      operationId: searchUsers
      parameters:
        - name: q
          in: query
          required: true
          description: Email search query (partial match)
          schema:
            type: string
            minLength: 1
            maxLength: 100
        - name: limit
          in: query
          description: Maximum number of results to return (default 10, max 50)
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
      responses:
        '200':
          description: User search results
          content:
            application/json:
              schema:
                type: object
                required:
                  - users
                properties:
                  users:
                    type: array
                    items:
                      $ref: '#/components/schemas/UserSummary'
        '400':
          $ref: '#/components/responses/ValidationError'

components:
  securitySchemes:
    ApiGateway:
      type: apiKey
      in: header
      name: X-Auth-User-ID
      description: User ID injected by API Gateway after JWT validation

  schemas:
    UserSummary:
      type: object
      required:
        - id
        - email
        - isAdmin
        - emailVerified
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: User unique identifier
        email:
          type: string
          format: email
          description: User email address
        displayName:
          type: string
          description: User's display name (optional)
        isAdmin:
          type: boolean
          description: Whether user has admin privileges
        emailVerified:
          type: boolean
          description: Whether email address is verified
        createdAt:
          type: string
          format: date-time
          description: Account creation timestamp

    HealthResponse:
      type: object
      required:
        - status
        - timestamp
      properties:
        status:
          type: string
          enum: [up, down]
        timestamp:
          type: string
          format: date-time

    ReadinessResponse:
      type: object
      required:
        - status
        - checks
      properties:
        status:
          type: string
          enum: [ready, not_ready]
        checks:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/HealthCheck'

    HealthCheck:
      type: object
      required:
        - status
      properties:
        status:
          type: string
          enum: [up, down]
        latency_ms:
          type: integer
        circuit:
          type: string
          enum: [closed, open, half_open]

    Provider:
      type: object
      required:
        - id
        - type
        - status
        - linkedAt
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum: [libre, dexcom]
        status:
          type: string
          enum: [active, expired, error, revoked]
        lastSyncAt:
          type: string
          format: date-time
          nullable: true
        linkedAt:
          type: string
          format: date-time
        lastError:
          type: string
          nullable: true

    ProvidersResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Provider'
        meta:
          $ref: '#/components/schemas/ResponseMeta'

    LinkLibreRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
          description: LibreLinkUp account email
        password:
          type: string
          format: password
          minLength: 1
          description: LibreLinkUp account password
        region:
          type: string
          enum: [us, eu, ap, global]
          default: us
          description: LibreLinkUp region

    DexcomCallbackRequest:
      type: object
      required:
        - code
        - state
      properties:
        code:
          type: string
          description: Authorization code from Dexcom redirect
        state:
          type: string
          description: State parameter for CSRF verification

    DexcomAuthResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: object
          required:
            - authorizationUrl
            - state
            - expiresAt
          properties:
            authorizationUrl:
              type: string
              format: uri
            state:
              type: string
            expiresAt:
              type: string
              format: date-time
        meta:
          $ref: '#/components/schemas/ResponseMeta'

    LinkProviderResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          $ref: '#/components/schemas/Provider'
        meta:
          $ref: '#/components/schemas/ResponseMeta'

    GlucoseReading:
      type: object
      required:
        - valueMgdl
        - valueMmol
        - timestamp
        - trend
        - source
        - providerId
      properties:
        valueMgdl:
          type: number
          format: float
          description: Glucose value in mg/dL
        valueMmol:
          type: number
          format: float
          description: Glucose value in mmol/L
        timestamp:
          type: string
          format: date-time
        trend:
          type: string
          enum: [rising_fast, rising, stable, falling, falling_fast, unknown]
        trendArrow:
          type: string
          description: Visual representation of trend
        trendRate:
          type: number
          format: float
          nullable: true
          description: Rate of change in mg/dL per minute
        source:
          type: string
          description: Provider that reported this reading
        providerId:
          type: string
          format: uuid

    LatestGlucoseResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          allOf:
            - $ref: '#/components/schemas/GlucoseReading'
            - type: object
              properties:
                isStale:
                  type: boolean
                  description: True if data is older than expected sync interval
        meta:
          allOf:
            - $ref: '#/components/schemas/ResponseMeta'
            - type: object
              properties:
                cached:
                  type: boolean
                cachedAt:
                  type: string
                  format: date-time

    GlucoseHistoryResponse:
      type: object
      required:
        - data
        - meta
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/GlucoseReading'
        meta:
          $ref: '#/components/schemas/ResponseMeta'
        pagination:
          $ref: '#/components/schemas/Pagination'

    Pagination:
      type: object
      required:
        - hasMore
      properties:
        nextCursor:
          type: string
          nullable: true
          description: Cursor for next page (null if no more pages)
        hasMore:
          type: boolean
        totalCount:
          type: integer
          description: Total count of readings in range (may be approximate)

    SyncResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: object
          required:
            - readingsCount
            - syncedAt
          properties:
            readingsCount:
              type: integer
              description: Number of readings synced
            latestReading:
              $ref: '#/components/schemas/GlucoseReading'
            syncedAt:
              type: string
              format: date-time
        meta:
          $ref: '#/components/schemas/ResponseMeta'

    GlucoseStatsResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          type: object
          required:
            - period
            - startDate
            - endDate
            - readingsCount
            - average
            - minimum
            - maximum
            - timeInRange
            - timeLow
            - timeHigh
          properties:
            period:
              type: string
              description: The time period for the stats
            startDate:
              type: string
              format: date-time
            endDate:
              type: string
              format: date-time
            readingsCount:
              type: integer
            average:
              type: number
              format: float
              description: Average glucose in mg/dL
            minimum:
              type: number
              format: float
            maximum:
              type: number
              format: float
            stdDev:
              type: number
              format: float
              description: Standard deviation
            timeInRange:
              type: number
              format: float
              description: Percentage of readings in target range (70-180 mg/dL)
            timeLow:
              type: number
              format: float
              description: Percentage of readings below 70 mg/dL
            timeHigh:
              type: number
              format: float
              description: Percentage of readings above 180 mg/dL
            gmi:
              type: number
              format: float
              description: Glucose Management Indicator (estimated A1C)
        meta:
          $ref: '#/components/schemas/ResponseMeta'

    ResponseMeta:
      type: object
      required:
        - requestId
        - timestamp
      properties:
        requestId:
          type: string
        timestamp:
          type: string
          format: date-time

    ProblemDetails:
      type: object
      required:
        - type
        - title
        - status
      properties:
        type:
          type: string
          format: uri-reference
          description: A URI reference identifying the problem type
        title:
          type: string
          description: Short human-readable summary
        status:
          type: integer
          description: HTTP status code
        detail:
          type: string
          description: Human-readable explanation specific to this occurrence
        instance:
          type: string
          format: uri-reference
          description: URI reference identifying this specific occurrence
        requestId:
          type: string
        errors:
          type: array
          items:
            $ref: '#/components/schemas/ValidationError'

    ValidationError:
      type: object
      required:
        - field
        - message
      properties:
        field:
          type: string
        message:
          type: string

    # Auth Schemas
    RegisterRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password
          minLength: 8
        displayName:
          type: string

    LoginRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password

    AuthResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            user:
              $ref: '#/components/schemas/UserProfile'
            token:
              type: string
              description: JWT token for authentication

    UserProfile:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        displayName:
          type: string
        emailVerified:
          type: boolean
        createdAt:
          type: string
          format: date-time

    # Settings Schemas
    UserSettings:
      type: object
      properties:
        id:
          type: string
          format: uuid
        authUserId:
          type: string
        displayName:
          type: string
        glucoseUnit:
          type: string
          enum: [mgdl, mmol]
        syncTier:
          type: string
          enum: [free, premium, enterprise]
        backgroundSync:
          type: boolean
        syncIntervalMins:
          type: integer
          minimum: 1
          maximum: 60

    UpdateSettingsRequest:
      type: object
      properties:
        displayName:
          type: string
        glucoseUnit:
          type: string
          enum: [mgdl, mmol]
        backgroundSync:
          type: boolean
        syncIntervalMins:
          type: integer
          minimum: 1
          maximum: 60

    TargetRanges:
      type: object
      properties:
        targetLow:
          type: number
          description: Low target in mg/dL (e.g., 80)
        targetHigh:
          type: number
          description: High target in mg/dL (e.g., 150)
        lowAlarmEnabled:
          type: boolean
        lowAlarm:
          type: number
        highAlarmEnabled:
          type: boolean
        highAlarm:
          type: number

    UpdateTargetRangesRequest:
      type: object
      required:
        - providerId
      properties:
        providerId:
          type: string
          format: uuid
        targetLow:
          type: number
          minimum: 40
          maximum: 200
        targetHigh:
          type: number
          minimum: 100
          maximum: 400

    PatientSettingsResponse:
      type: object
      properties:
        id:
          type: string
          format: uuid
        targetLow:
          type: number
        targetHigh:
          type: number
        firstName:
          type: string
        lastName:
          type: string

    # Libre Schemas
    LibrePatientInfo:
      type: object
      properties:
        sensor:
          $ref: '#/components/schemas/SensorInfo'
        settings:
          $ref: '#/components/schemas/PatientSettings'

    SensorInfo:
      type: object
      properties:
        serialNumber:
          type: string
        activatedAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
        daysRemaining:
          type: number
        isStreaming:
          type: boolean

    PatientSettings:
      type: object
      properties:
        patientName:
          type: string
        targetLow:
          type: number
        targetHigh:
          type: number
        highAlarm:
          type: number
        lowAlarm:
          type: number
        highAlarmEnabled:
          type: boolean
        lowAlarmEnabled:
          type: boolean
        units:
          type: string

    AlarmSettings:
      type: object
      properties:
        lowAlarm:
          type: object
          properties:
            enabled:
              type: boolean
            threshold:
              type: number
        highAlarm:
          type: object
          properties:
            enabled:
              type: boolean
            threshold:
              type: number
        targetRange:
          type: object
          properties:
            low:
              type: number
            high:
              type: number

    # Sync Schemas
    SyncStatusResponse:
      type: object
      properties:
        tier:
          type: string
          enum: [free, premium, enterprise]
        backgroundSync:
          type: boolean
        syncIntervalMins:
          type: integer
        canBackgroundSync:
          type: boolean
        lastSyncAt:
          type: string
          format: date-time
        nextSyncAt:
          type: string
          format: date-time
        providersCount:
          type: integer

    UpdateSyncConfigRequest:
      type: object
      properties:
        backgroundSync:
          type: boolean
        syncIntervalMins:
          type: integer
          minimum: 1
          maximum: 60

    SyncConfigResponse:
      type: object
      properties:
        backgroundSync:
          type: boolean
        syncIntervalMins:
          type: integer
        tier:
          type: string

    # Dashboard Schema
    DashboardResponse:
      type: object
      properties:
        current:
          $ref: '#/components/schemas/GlucoseReading'
        stats:
          type: object
          properties:
            average:
              type: number
            minimum:
              type: number
            maximum:
              type: number
            timeInRange:
              type: number
            timeLow:
              type: number
            timeHigh:
              type: number
            gmi:
              type: number
            readingsCount:
              type: integer
        readings:
          type: array
          items:
            $ref: '#/components/schemas/GlucoseReading'
        providers:
          type: array
          items:
            $ref: '#/components/schemas/Provider'
        sensor:
          $ref: '#/components/schemas/SensorInfo'
        settings:
          $ref: '#/components/schemas/PatientSettings'

  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
          example:
            type: /errors/validation-failed
            title: Validation Failed
            status: 400
            detail: Request validation failed
            requestId: req_abc123
            errors:
              - field: startDate
                message: must be a valid ISO 8601 date-time

    Unauthorized:
      description: Authentication required
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
          example:
            type: /errors/unauthorized
            title: Unauthorized
            status: 401
            detail: Missing or invalid authentication
            requestId: req_abc123

    TooManyRequests:
      description: Rate limit exceeded
      headers:
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Request limit per window
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Remaining requests in window
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix timestamp when window resets
        Retry-After:
          schema:
            type: integer
          description: Seconds until retry
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
          example:
            type: /errors/rate-limit-exceeded
            title: Rate Limit Exceeded
            status: 429
            detail: You have exceeded the rate limit. Please wait before retrying.
            requestId: req_abc123

security:
  - ApiGateway: []

