openapi: 3.0.3
info:
  title: RxTestify — Pharmacy Benefit API
  description: |
    AI-powered pharmacy benefit testing platform that automatically extracts
    benefit rules from plan documents and generates NCPDP-compliant F6 test claims.

    ## Authentication

    Most endpoints require a Firebase ID token passed as a Bearer token:
    ```
    Authorization: Bearer <firebase_id_token>
    ```

    Admin-only endpoints require an `ADMIN_SECRET_KEY` passed as a `key` query parameter.

    ## Base URL

    | Environment | URL |
    |---|---|
    | Production | `https://api.rxtestify.com` |
    | Local Dev | `http://localhost:8080` |

  version: "1.0.0"
  contact:
    email: info@rxtestify.com
  license:
    name: Proprietary

servers:
  - url: https://api.rxtestify.com
    description: Production
  - url: http://localhost:8080
    description: Local development

tags:
  - name: Health
    description: API status and health check endpoints
  - name: Auth
    description: User registration and approval workflow
  - name: Documents
    description: Upload, retrieve, and delete benefit plan documents
  - name: Processing
    description: AI-powered document validation and benefit extraction
  - name: Claims
    description: NCPDP F6 test claim generation
  - name: Test Suites
    description: Generate and manage NCPDP-compliant test scenario suites
  - name: NPI
    description: Provider and pharmacy NPI lookup (no auth required)
  - name: Demo
    description: Public demo request submission
  - name: Admin
    description: |
      **Internal / Admin-only endpoints.**
      These require the `ADMIN_SECRET_KEY` passed as `?key=<secret>`.
      Do not expose the admin key publicly.
  - name: Prompts
    description: Retrieve AI prompt templates used for benefit extraction

# ─────────────────────────────────────────────────────────────────
# Security Schemes
# ─────────────────────────────────────────────────────────────────
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Firebase ID Token
      description: |
        Obtain a Firebase ID token after signing in with Google or Email/Password.
        Pass it in the `Authorization` header as `Bearer <token>`.
    AdminKey:
      type: apiKey
      in: query
      name: key
      description: |
        Admin secret key (`ADMIN_SECRET_KEY` env var). Required for admin-only endpoints.

  # ──────────────────────────────────────────────────────────────
  # Reusable Schemas
  # ──────────────────────────────────────────────────────────────
  schemas:

    # ── Generic ──────────────────────────────────────────────────
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          example: "Missing required fields"
        message:
          type: string
          example: "Detailed error description"

    ValidationObject:
      type: object
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            type: string
        warnings:
          type: array
          items:
            type: string

    # ── Auth ─────────────────────────────────────────────────────
    RegisterRequest:
      type: object
      required: [uid, email]
      properties:
        uid:
          type: string
          description: Firebase UID
          example: "abc123xyz"
        email:
          type: string
          format: email
          example: "user@example.com"
        displayName:
          type: string
          example: "Jane Smith"
        provider:
          type: string
          enum: [google, password, email]
          example: "google"

    RegisterResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        status:
          type: string
          enum: [pending, approved, rejected]
          example: "pending"
        isNew:
          type: boolean
          description: Whether this is a newly created account
          example: true
        message:
          type: string
          example: "Registration successful. Awaiting admin approval."

    AuthStatusResponse:
      type: object
      properties:
        success:
          type: boolean
        status:
          type: string
          enum: [pending, approved, rejected]
        displayName:
          type: string
        email:
          type: string
          format: email

    # ── Documents ────────────────────────────────────────────────
    DocumentRecord:
      type: object
      properties:
        id:
          type: string
          description: Firestore document ID
          example: "abc123"
        displayName:
          type: string
          example: "ACME Health Plan 2025"
        filename:
          type: string
          example: "plan-document.pdf"
        fileType:
          type: string
          enum:
            - application/pdf
            - application/vnd.openxmlformats-officedocument.wordprocessingml.document
            - application/msword
            - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
            - application/vnd.ms-excel
            - application/json
          example: "application/pdf"
        fileSize:
          type: integer
          description: File size in bytes
          example: 204800
        status:
          type: string
          enum: [uploaded, processing, completed, failed, deleted, test_suite_generated]
          example: "completed"
        uploadDate:
          type: string
          format: date-time
        storageUrl:
          type: string
          format: uri
          example: "https://storage.googleapis.com/..."
        extractedText:
          type: string
          nullable: true
          description: Raw text extracted from the document (available after processing)
        aiAnalysis:
          type: object
          nullable: true
          description: Structured benefit data extracted by Claude AI
        testSuiteId:
          type: string
          nullable: true
          description: ID of the most recent test suite generated from this document
        userId:
          type: string
          description: Firebase UID of the owning user
        description:
          type: string
          nullable: true

    DocumentUploadRequest:
      type: object
      required: [file, filename, fileType, userId]
      properties:
        file:
          type: string
          format: byte
          description: Base64-encoded file content (max 10 MB)
          example: "JVBERi0xLjQKJe..."
        filename:
          type: string
          example: "plan-document.pdf"
        fileType:
          type: string
          enum:
            - application/pdf
            - application/vnd.openxmlformats-officedocument.wordprocessingml.document
            - application/msword
            - application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
            - application/vnd.ms-excel
            - application/json
          example: "application/pdf"
        userId:
          type: string
          description: Firebase UID of the uploading user
          example: "uid-abc123"
        displayName:
          type: string
          description: Human-readable label for the document (defaults to filename)
          example: "ACME Plan 2025"
        description:
          type: string
          nullable: true
          example: "Medicare Part D formulary document"

    DocumentUploadResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: "Document uploaded successfully. Processing started in background."
        data:
          type: object
          properties:
            documentId:
              type: string
              example: "doc-xyz789"
            filename:
              type: string
            fileType:
              type: string
            fileSize:
              type: integer
            status:
              type: string
              example: "uploaded"
            storageUrl:
              type: string
              format: uri
            uploadDate:
              type: string
              format: date-time
            processingStatus:
              type: string
              example: "started"
            note:
              type: string
              example: "Poll /api/documents/[id] to check processing status"

    # ── Processing ───────────────────────────────────────────────
    ValidateDocumentRequest:
      type: object
      required: [documentId, userId]
      properties:
        documentId:
          type: string
          example: "doc-xyz789"
        userId:
          type: string
          example: "uid-abc123"

    ValidateDocumentResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            validation:
              type: object
              properties:
                canProceed:
                  type: boolean
                content:
                  type: object
                  description: Content readability assessment
                size:
                  type: object
                  description: File size constraints
                cost:
                  type: object
                  description: Estimated AI processing cost
                rateLimit:
                  type: object
                  description: Rate limit status for the user
                warnings:
                  type: array
                  items:
                    type: string
                errors:
                  type: array
                  items:
                    type: string

    ProcessDocumentRequest:
      type: object
      required: [documentId, userId]
      properties:
        documentId:
          type: string
          example: "doc-xyz789"
        userId:
          type: string
          example: "uid-abc123"
        customPrompt:
          type: string
          nullable: true
          description: Override the default benefit extraction prompt
        category:
          type: string
          nullable: true
          enum: [comprehensive, costShares, accumulators, drugCoverage, formularyTiering, utilizationManagement]
          description: Which category of benefits to extract (defaults to comprehensive)
          example: "comprehensive"

    ProcessDocumentResponse:
      type: object
      properties:
        success:
          type: boolean
        analysis:
          type: object
          description: Structured benefit data extracted by Claude
        metadata:
          type: object
          properties:
            tokenCount:
              type: integer
            model:
              type: string
              example: "claude-3-5-sonnet-20241022"
            processingTime:
              type: integer
              description: Processing time in milliseconds
        promptUsed:
          type: string
          description: The prompt category used for extraction
        category:
          type: string

    # ── Claims ───────────────────────────────────────────────────
    ClaimInputs:
      type: object
      required: [ndc, quantityDispensed, daysSupply, prescriberID, pharmacyNPI, ingredientCost, dispensingFee]
      properties:
        binNumber:
          type: string
          maxLength: 6
          example: "610097"
        pcn:
          type: string
          maxLength: 10
          example: "TESTPCN"
        groupId:
          type: string
          maxLength: 15
          example: "GRP001"
        cardholderID:
          type: string
          maxLength: 20
          example: "CH12345678"
        personCode:
          type: string
          maxLength: 3
          example: "01"
        patientRelationship:
          type: string
          enum: ["0", "1", "2", "3", "4"]
          description: "0=Not Specified, 1=Cardholder, 2=Spouse, 3=Child, 4=Other"
          example: "1"
        dateOfBirth:
          type: string
          pattern: "^\\d{8}$"
          description: CCYYMMDD format
          example: "19800115"
        patientGender:
          type: string
          enum: ["0", "1", "2"]
          description: "0=Not Specified, 1=Male, 2=Female"
          example: "1"
        patientFirstName:
          type: string
          maxLength: 12
          example: "JOHN"
        patientLastName:
          type: string
          maxLength: 15
          example: "DOE"
        patientStreetAddress:
          type: string
          maxLength: 30
          example: "123 MAIN ST"
        patientCity:
          type: string
          maxLength: 20
          example: "COLUMBUS"
        patientState:
          type: string
          maxLength: 2
          example: "OH"
        patientZip:
          type: string
          maxLength: 15
          example: "43215"
        ndc:
          type: string
          pattern: "^\\d{11}$"
          description: 11-digit NDC (no dashes)
          example: "00093721656"
        quantityDispensed:
          type: number
          format: float
          example: 30.0
        daysSupply:
          type: integer
          example: 30
        dawCode:
          type: string
          enum: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
          description: Dispense As Written code
          example: "0"
        prescriberID:
          type: string
          description: NPI of prescriber
          maxLength: 15
          example: "1234567890"
        prescriberLastName:
          type: string
          maxLength: 15
          example: "SMITH"
        prescriberFirstName:
          type: string
          maxLength: 12
          example: "JOHN"
        pharmacyNPI:
          type: string
          maxLength: 10
          example: "9876543210"
        pharmacyName:
          type: string
          maxLength: 30
          example: "WALGREENS PHARMACY"
        pharmacyStreetAddress:
          type: string
          maxLength: 30
          example: "456 ELM AVE"
        pharmacyCity:
          type: string
          maxLength: 20
          example: "COLUMBUS"
        pharmacyState:
          type: string
          maxLength: 2
          example: "OH"
        pharmacyZip:
          type: string
          maxLength: 15
          example: "43201"
        ingredientCost:
          type: number
          format: float
          description: Ingredient cost in dollars
          example: 25.50
        dispensingFee:
          type: number
          format: float
          description: Dispensing fee in dollars
          example: 1.75
        pharmacyType:
          type: string
          enum: [retail, mailOrder, specialty, longTermCare]
          example: "retail"

    GenerateF6ClaimRequest:
      type: object
      required: [benefitId, claimInputs]
      properties:
        benefitId:
          type: string
          description: ID of the processed document/benefit to use as context
          example: "doc-xyz789"
        claimInputs:
          $ref: '#/components/schemas/ClaimInputs'
        options:
          type: object
          description: Optional generation overrides
          properties:
            format:
              type: string
              enum: [F6]
              default: "F6"

    GenerateF6ClaimResponse:
      type: object
      properties:
        success:
          type: boolean
        claimId:
          type: string
          example: "clm-abc123"
        format:
          type: string
          example: "F6"
        claim:
          type: object
          description: Fully populated NCPDP F6 formatted claim object
        metadata:
          type: object
          properties:
            validation:
              $ref: '#/components/schemas/ValidationObject'
            tokenCount:
              type: integer
            model:
              type: string
        downloadUrl:
          type: string
          format: uri
          nullable: true

    # ── Test Suites ──────────────────────────────────────────────
    TestScenario:
      type: object
      properties:
        id:
          type: string
          example: "TS-001"
        description:
          type: string
          example: "Tier 1 generic drug — expected patient pays $10 copay"
        claimInputs:
          $ref: '#/components/schemas/ClaimInputs'
        expectedResult:
          type: string
          enum: [approved, rejected, partially_approved]
          example: "approved"
        expectedPatientPay:
          type: number
          nullable: true
        expectedPlanPay:
          type: number
          nullable: true
        category:
          type: string
          example: "formulary_tier_1"
        notes:
          type: string
          nullable: true

    TestSuiteRecord:
      type: object
      properties:
        testSuiteId:
          type: string
          example: "TS-1234567890"
        planName:
          type: string
          example: "ACME Health Plan 2025"
        generatedAt:
          type: string
          format: date-time
        totalScenarios:
          type: integer
        scenarios:
          type: array
          items:
            $ref: '#/components/schemas/TestScenario'
        validation:
          $ref: '#/components/schemas/ValidationObject'
        confidence:
          type: number
          format: float
          minimum: 0
          maximum: 1
          example: 0.95

    GenerateTestSuiteRequest:
      type: object
      required: [benefitId]
      properties:
        benefitId:
          type: string
          description: ID of the processed document to generate scenarios from
          example: "doc-xyz789"
        mode:
          type: string
          enum: [comprehensive, quick, edgeCase]
          default: comprehensive
          description: |
            - `comprehensive`: Full coverage across all benefit categories
            - `quick`: Smaller set of high-priority scenarios
            - `edgeCase`: Focus on boundary and edge conditions
        options:
          type: object
          description: Optional overrides passed to the generator
          properties:
            maxTokens:
              type: integer
              example: 4000
        correctedTestSuite:
          $ref: '#/components/schemas/TestSuiteRecord'
          nullable: true
          description: Submit a manually corrected test suite to save it (replaces AI generation)
        originalResponseId:
          type: string
          nullable: true
          description: ID of the raw AI response being corrected

    GenerateTestSuiteResponse:
      type: object
      properties:
        success:
          type: boolean
        testSuiteId:
          type: string
          description: Firestore ID of the stored test suite
        testSuite:
          $ref: '#/components/schemas/TestSuiteRecord'
        message:
          type: string
          example: "Generated 12 test scenarios"
        summary:
          type: object
          properties:
            totalScenarios:
              type: integer
            mode:
              type: string
            confidence:
              type: number
            validation:
              $ref: '#/components/schemas/ValidationObject'
            updated:
              type: boolean
              description: True if an existing test suite was updated
            correctionCount:
              type: integer

    # ── NPI ──────────────────────────────────────────────────────
    NpiRecord:
      type: object
      properties:
        npi:
          type: string
          pattern: "^\\d{10}$"
          example: "1234567890"
        type:
          type: string
          enum: [prescriber, pharmacy]
        name:
          type: string
          example: "Smith, John MD"
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        specialty:
          type: string
          nullable: true
          example: "Family Practice"
        address:
          type: object
          properties:
            street:
              type: string
            city:
              type: string
            state:
              type: string
            zip:
              type: string
        phone:
          type: string
          nullable: true
        mailOrder:
          type: boolean
          nullable: true
          description: Present only for pharmacy records

    # ── Demo Request ─────────────────────────────────────────────
    DemoRequestBody:
      type: object
      required: [name, email, description]
      properties:
        name:
          type: string
          minLength: 2
          example: "Jane Smith"
        email:
          type: string
          format: email
          example: "jane@acmehealth.com"
        phone:
          type: string
          nullable: true
          example: "+1-614-555-0123"
        description:
          type: string
          minLength: 10
          example: "We are a mid-size PBM looking to automate F6 benefit testing."

    # ── Admin Demo Requests ──────────────────────────────────────
    DemoRequestRecord:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
          nullable: true
        description:
          type: string
        status:
          type: string
          enum: [new, contacted, demo_scheduled, demo_completed, follow_up, closed]
        submittedAt:
          type: string
          format: date-time
        notes:
          type: array
          items:
            type: string
        nextFollowUpDate:
          type: string
          format: date
          nullable: true
        closedReason:
          type: string
          enum: [won, lost, not_interested, no_budget, other]
          nullable: true
        closedNote:
          type: string
          nullable: true

    UpdateDemoRequestBody:
      type: object
      properties:
        status:
          type: string
          enum: [new, contacted, demo_scheduled, demo_completed, follow_up, closed]
        note:
          type: string
          description: Append a new note to this record
        nextFollowUpDate:
          type: string
          format: date
          nullable: true
        closedReason:
          type: string
          enum: [won, lost, not_interested, no_budget, other]
          nullable: true
        closedNote:
          type: string
          nullable: true

# ─────────────────────────────────────────────────────────────────
# Paths
# ─────────────────────────────────────────────────────────────────
paths:

  # ── Health ────────────────────────────────────────────────────
  /health:
    get:
      tags: [Health]
      summary: Health check
      description: Returns 200 OK. Used by Cloud Run / load balancers to verify the service is running.
      operationId: healthCheck
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"
                  timestamp:
                    type: string
                    format: date-time

  /api:
    get:
      tags: [Health]
      summary: API info & endpoint list
      description: Returns API version, status, and a list of all available endpoints.
      operationId: apiInfo
      responses:
        "200":
          description: API information
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: "RxTestify API is running"
                  version:
                    type: string
                    example: "1.0.0"
                  endpoints:
                    type: array
                    items:
                      type: string

  # ── Auth ──────────────────────────────────────────────────────
  /api/auth/register:
    post:
      tags: [Auth]
      summary: Register a new user
      description: |
        Called automatically after Firebase sign-in. Creates a user record in Firestore
        with `status: pending`. A notification email is sent to the admin for approval.
      operationId: registerUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterRequest'
      responses:
        "200":
          description: Registration successful (existing user returned)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegisterResponse'
        "201":
          description: New user created, pending admin approval
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegisterResponse'
        "400":
          description: Missing or invalid fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/auth/status:
    get:
      tags: [Auth]
      summary: Check user approval status
      description: Poll this endpoint to determine if a user's account has been approved by an admin.
      operationId: getAuthStatus
      parameters:
        - name: uid
          in: query
          required: true
          schema:
            type: string
          description: Firebase UID of the user
          example: "abc123xyz"
      responses:
        "200":
          description: User status returned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthStatusResponse'
        "404":
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/auth/approve:
    get:
      tags: [Admin]
      summary: Admin one-click approve or reject user
      description: |
        **Admin-only.** Sent as an email link to the admin. Approves or rejects
        a pending user account. Sends a notification email to the user.

        > ⚠️ Requires the `ADMIN_SECRET_KEY` as `?key=` query parameter.
      operationId: approveUser
      x-internal: true
      security:
        - AdminKey: []
      parameters:
        - name: uid
          in: query
          required: true
          schema:
            type: string
          description: Firebase UID of the user to approve or reject
        - name: action
          in: query
          required: true
          schema:
            type: string
            enum: [approve, reject]
          description: Action to perform
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Admin secret key
      responses:
        "200":
          description: HTML confirmation page rendered to admin
          content:
            text/html:
              schema:
                type: string
        "401":
          description: Invalid admin key
        "404":
          description: User not found

  # ── Documents ─────────────────────────────────────────────────
  /api/documents/upload:
    post:
      tags: [Documents]
      summary: Upload a benefit plan document
      description: |
        Accepts PDF, DOCX, XLSX, or HPMS JSON files up to **10 MB**.
        The file must be base64-encoded in the request body.

        **Processing is asynchronous** — the endpoint returns immediately after
        upload. AI extraction runs in the background. Poll `GET /api/documents/{id}`
        to monitor `status`.
      operationId: uploadDocument
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DocumentUploadRequest'
            example:
              file: "JVBERi0xLjQKJe..."
              filename: "plan-document.pdf"
              fileType: "application/pdf"
              userId: "uid-abc123"
              displayName: "ACME Health Plan 2025"
      responses:
        "200":
          description: Document uploaded; background processing started
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentUploadResponse'
        "400":
          description: Validation failed (invalid type, size, missing fields)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized — missing or invalid Firebase token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal server error during upload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/documents:
    get:
      tags: [Documents]
      summary: List user's documents
      description: Returns all non-deleted documents belonging to the authenticated user.
      operationId: listDocuments
      security:
        - BearerAuth: []
      parameters:
        - name: userId
          in: query
          required: true
          schema:
            type: string
          description: Firebase UID — must match the authenticated user
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
            maximum: 100
          description: Maximum number of documents to return
      responses:
        "200":
          description: List of documents
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      documents:
                        type: array
                        items:
                          $ref: '#/components/schemas/DocumentRecord'
                      count:
                        type: integer
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/documents/{id}:
    get:
      tags: [Documents]
      summary: Get a document by ID
      description: Returns full document record including extracted text and AI analysis (if available).
      operationId: getDocument
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Document ID
          example: "doc-xyz789"
      responses:
        "200":
          description: Document record
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/DocumentRecord'
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — document belongs to another user
        "404":
          description: Document not found

    delete:
      tags: [Documents]
      summary: Delete (soft-delete) a document
      description: |
        Marks the document as `status: deleted`. The record is retained in Firestore
        but will no longer appear in list queries.
      operationId: deleteDocument
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Document ID
        - name: userId
          in: query
          required: true
          schema:
            type: string
          description: Firebase UID — must match the owning user
      responses:
        "200":
          description: Document deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: "Document deleted successfully"
        "401":
          description: Unauthorized
        "403":
          description: Forbidden
        "404":
          description: Document not found

  # ── Processing ────────────────────────────────────────────────
  /api/validate-document:
    post:
      tags: [Processing]
      summary: Validate document before AI processing
      description: |
        Pre-checks a document for:
        - Content readability (non-empty, parseable)
        - Size compliance (≤ 10 MB)
        - Estimated AI processing cost
        - User rate limit availability

        Returns `canProceed: true` if the document is safe to process.
      operationId: validateDocument
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidateDocumentRequest'
      responses:
        "200":
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateDocumentResponse'
        "400":
          description: Missing required fields
        "401":
          description: Unauthorized
        "404":
          description: Document not found

  /api/process-document:
    post:
      tags: [Processing]
      summary: Extract benefits from document using AI
      description: |
        Sends the extracted document text to Claude for structured benefit extraction.
        The result is stored back on the document record as `aiAnalysis`.

        **Note:** This endpoint calls the Claude API and may take 15–60 seconds depending
        on document size. Consider calling `GET /api/validate-document` first.
      operationId: processDocument
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProcessDocumentRequest'
      responses:
        "200":
          description: AI analysis complete
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProcessDocumentResponse'
        "400":
          description: Missing fields or document not yet uploaded
        "401":
          description: Unauthorized
        "404":
          description: Document not found
        "429":
          description: Rate limit exceeded
        "500":
          description: Claude API error

  # ── Claims ────────────────────────────────────────────────────
  /api/generate-f6-claim:
    post:
      tags: [Claims]
      summary: Generate an NCPDP F6 test claim
      description: |
        Generates a fully populated NCPDP F6 (version 6.x) test claim using:
        - Extracted benefit rules from the specified document
        - Claim inputs provided in the request body

        The generated claim is stored in the `claims` Firestore collection.
      operationId: generateF6Claim
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateF6ClaimRequest'
            example:
              benefitId: "doc-xyz789"
              claimInputs:
                binNumber: "610097"
                pcn: "TESTPCN"
                groupId: "GRP001"
                cardholderID: "CH12345678"
                personCode: "01"
                dateOfBirth: "19800115"
                ndc: "00093721656"
                quantityDispensed: 30
                daysSupply: 30
                dawCode: "0"
                prescriberID: "1234567890"
                prescriberLastName: "SMITH"
                pharmacyNPI: "9876543210"
                pharmacyName: "WALGREENS PHARMACY"
                pharmacyState: "OH"
                ingredientCost: 25.50
                dispensingFee: 1.75
                pharmacyType: "retail"
      responses:
        "200":
          description: F6 claim generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateF6ClaimResponse'
        "400":
          description: Invalid or missing claim inputs
        "401":
          description: Unauthorized
        "404":
          description: Benefit document not found
        "500":
          description: Claim generation failed

  # ── Test Suites ───────────────────────────────────────────────
  /api/test-suites:
    get:
      tags: [Test Suites]
      summary: List test suites for a document
      description: Returns all test suites generated from a specific benefit document.
      operationId: listTestSuites
      security:
        - BearerAuth: []
      parameters:
        - name: documentId
          in: query
          required: true
          schema:
            type: string
          description: ID of the source benefit document
          example: "doc-xyz789"
      responses:
        "200":
          description: List of test suites
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      testSuites:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                            benefitId:
                              type: string
                            mode:
                              type: string
                            scenarioCount:
                              type: integer
                            confidence:
                              type: number
                            generatedAt:
                              type: string
                              format: date-time
                            corrected:
                              type: boolean
                      count:
                        type: integer
        "401":
          description: Unauthorized

  /api/generate-test-suite:
    post:
      tags: [Test Suites]
      summary: Generate a test suite from a benefit document
      description: |
        Uses Claude AI to generate a set of NCPDP-compliant test scenarios based on
        the extracted benefit rules of a processed document.

        You can also submit a `correctedTestSuite` to save a manually edited version
        rather than triggering a new AI generation.
      operationId: generateTestSuite
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/GenerateTestSuiteRequest'
            example:
              benefitId: "doc-xyz789"
              mode: "comprehensive"
      responses:
        "201":
          description: Test suite generated (or updated) successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GenerateTestSuiteResponse'
        "400":
          description: Benefit extraction not complete, or missing fields
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          description: Unauthorized
        "403":
          description: Forbidden — document belongs to another user
        "404":
          description: Document not found
        "500":
          description: Test suite generation failed
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                  message:
                    type: string
                  responseId:
                    type: string
                    nullable: true
                    description: ID of the raw AI response (use to review/correct)
                  canReview:
                    type: boolean

  /api/test-suite-responses/{id}:
    get:
      tags: [Test Suites]
      summary: Get raw AI response for a test suite
      description: |
        Returns the raw Claude API response that was used to generate a test suite.
        Useful for reviewing parse errors or inspecting the model's raw output.
      operationId: getTestSuiteResponse
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Test suite response ID
          example: "resp-abc123"
      responses:
        "200":
          description: Raw response record
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  response:
                    type: object
                    properties:
                      responseId:
                        type: string
                      status:
                        type: string
                        enum: [pending, success, parse_error, failed]
                      mode:
                        type: string
                      rawResponse:
                        type: string
                        description: Raw text returned by Claude
                      parseError:
                        type: string
                        nullable: true
                      parsedTestSuite:
                        $ref: '#/components/schemas/TestSuiteRecord'
                        nullable: true
        "401":
          description: Unauthorized
        "404":
          description: Response not found

  # ── NPI ───────────────────────────────────────────────────────
  /api/npi/lookup:
    get:
      tags: [NPI]
      summary: Look up a specific NPI
      description: |
        Returns provider or pharmacy details for the given 10-digit NPI.

        **Lookup order:** in-memory seed data → Firestore cache → NPPES API.
      operationId: npiLookup
      parameters:
        - name: npi
          in: query
          required: true
          schema:
            type: string
            pattern: "^\\d{10}$"
          description: 10-digit NPI number
          example: "1234567890"
      responses:
        "200":
          description: NPI record found
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/NpiRecord'
                  source:
                    type: string
                    enum: [seed, cache, nppes_api]
        "400":
          description: Invalid or missing NPI parameter
        "404":
          description: NPI not found

  /api/npi/search:
    get:
      tags: [NPI]
      summary: Search providers or pharmacies by name
      description: |
        Search the NPI registry by name and optional filters.

        **Prescriber search:** use `type=prescriber` with `firstName` / `lastName`.

        **Pharmacy search:** use `type=pharmacy` with `name`.
      operationId: npiSearch
      parameters:
        - name: type
          in: query
          required: true
          schema:
            type: string
            enum: [prescriber, pharmacy]
          description: Entity type to search for
        - name: firstName
          in: query
          schema:
            type: string
          description: First name (prescriber only)
          example: "John"
        - name: lastName
          in: query
          schema:
            type: string
          description: Last name (prescriber only)
          example: "Smith"
        - name: name
          in: query
          schema:
            type: string
          description: Organization name (pharmacy only)
          example: "Walgreens"
        - name: state
          in: query
          schema:
            type: string
            maxLength: 2
          description: Two-letter state abbreviation filter
          example: "OH"
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/NpiRecord'
                  count:
                    type: integer
                  filters:
                    type: object
        "400":
          description: Missing required parameters

  /api/npi/random:
    get:
      tags: [NPI]
      summary: Get a random NPI record
      description: Returns a random prescriber or pharmacy NPI matching the given filters.
      operationId: npiRandom
      parameters:
        - name: type
          in: query
          required: true
          schema:
            type: string
            enum: [prescriber, pharmacy]
        - name: state
          in: query
          schema:
            type: string
            maxLength: 2
          description: Two-letter state abbreviation
          example: "OH"
        - name: mailOrder
          in: query
          schema:
            type: boolean
          description: Filter pharmacy by mail-order status (pharmacy only)
        - name: specialty
          in: query
          schema:
            type: boolean
          description: Filter pharmacy by specialty designation (pharmacy only)
      responses:
        "200":
          description: Random NPI record
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/NpiRecord'
                  filters:
                    type: object

  # ── Demo ──────────────────────────────────────────────────────
  /api/demo-request:
    post:
      tags: [Demo]
      summary: Submit a demo request
      description: |
        Public endpoint — no authentication required. Submits a request for an RxTestify
        product demo. An email notification is sent to `info@rxtestify.com`.
      operationId: submitDemoRequest
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DemoRequestBody'
            example:
              name: "Jane Smith"
              email: "jane@acmehealth.com"
              phone: "+1-614-555-0123"
              description: "We are a mid-size PBM looking to automate F6 benefit testing."
      responses:
        "200":
          description: Demo request submitted
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: "Demo request submitted successfully"
                  data:
                    type: object
                    properties:
                      requestId:
                        type: string
        "400":
          description: Validation failed (name too short, invalid email, description too short)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "500":
          description: Internal error saving request or sending email

  # ── Admin Demo Pipeline ───────────────────────────────────────
  /api/admin/demo-requests:
    get:
      tags: [Admin]
      summary: List all demo requests
      description: |
        **Admin-only.** Returns all demo request records from Firestore.

        > ⚠️ Requires `?key=<ADMIN_SECRET_KEY>`.
      operationId: listDemoRequests
      x-internal: true
      security:
        - AdminKey: []
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Admin secret key
      responses:
        "200":
          description: All demo requests
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  count:
                    type: integer
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/DemoRequestRecord'
        "401":
          description: Invalid admin key

  /api/admin/demo-requests/{id}:
    patch:
      tags: [Admin]
      summary: Update a demo request
      description: |
        **Admin-only.** Update the status, add a note, set a follow-up date, or close
        a demo request with a reason.

        > ⚠️ Requires `?key=<ADMIN_SECRET_KEY>`.
      operationId: updateDemoRequest
      x-internal: true
      security:
        - AdminKey: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Demo request ID
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Admin secret key
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateDemoRequestBody'
            example:
              status: "contacted"
              note: "Left voicemail. Will follow up Friday."
              nextFollowUpDate: "2025-02-21"
      responses:
        "200":
          description: Demo request updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  id:
                    type: string
                  updated:
                    type: array
                    items:
                      type: string
                    description: List of field names that were updated
        "400":
          description: Invalid status or missing required fields
        "401":
          description: Invalid admin key
        "404":
          description: Demo request not found

  # ── Prompts ───────────────────────────────────────────────────
  /api/prompts/benefit-analysis:
    get:
      tags: [Prompts]
      summary: Get benefit analysis prompt templates
      description: |
        Returns the map of named prompt templates used by the AI benefit extraction pipeline.
        These can be inspected to understand what the model is asked to extract, or used
        as references when submitting `customPrompt` in `/api/process-document`.
      operationId: getBenefitAnalysisPrompts
      responses:
        "200":
          description: Prompt templates map
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: string
                description: Key-value map of prompt name → prompt text
