openapi: "3.1.0"
info:
  title: HerbFull Herb Data API
  version: "1.0.0"
  description: |
    Read-only REST API for HerbFull herb research data — phytochemical compounds,
    clinical profiles, thermal extraction protocols, and matrix layout positions.

    All endpoints are `GET` only. No authentication required; an optional
    `X-Api-Key` header determines the rate-limit tier.
  contact:
    name: HerbFull
    url: https://herbfull.org
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: https://api.herbfull.org
    description: Production

security:
  - {}
  - ApiKeyAuth: []

tags:
  - name: herbs
    description: Herb profiles, compounds, and thermal protocols
  - name: matrix
    description: Herb matrix layout positions
  - name: meta
    description: API metadata and health

paths:
  /v1/herbs:
    get:
      operationId: listHerbs
      tags: [herbs]
      summary: List herbs
      description: |
        Returns a projected list of herbs. All filter parameters are optional;
        omitting them returns all herbs sorted alphabetically.
      parameters:
        - $ref: '#/components/parameters/QueryParam'
        - $ref: '#/components/parameters/MoodParam'
        - $ref: '#/components/parameters/SymptomParam'
        - $ref: '#/components/parameters/CompoundParam'
      responses:
        "200":
          description: Successful herb list
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HerbListResponse'
              examples:
                allHerbs:
                  summary: All herbs (no filter)
                  value:
                    total: 18
                    items:
                      - id: lavender
                        name: Lavender
                        botanicalName: Lavandula angustifolia
                        category: core-50
                        vaporizableStatus: yes
                        safetyFlag: safe
                        intensityLevel: mild
                        moods: [relaxing, anti_anxiety, sedating]
                        symptoms: [Anxiolytic, Sedative, Antispasmodic]
                        compoundNames: [Linalool, Linalyl acetate, Camphor]
                        shortDescription: The classic relaxation herb
        "400":
          $ref: '#/components/responses/BadRequest'
        "429":
          $ref: '#/components/responses/TooManyRequests'
        "500":
          $ref: '#/components/responses/InternalError'

  /v1/herbs/{id}:
    get:
      operationId: getHerb
      tags: [herbs]
      summary: Get herb by ID
      description: Returns the full research profile for the given herb slug.
      parameters:
        - $ref: '#/components/parameters/HerbIdParam'
      responses:
        "200":
          description: Herb found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HerbProfile'
        "400":
          $ref: '#/components/responses/BadRequest'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/TooManyRequests'
        "500":
          $ref: '#/components/responses/InternalError'

  /v1/herbs/{id}/compounds:
    get:
      operationId: getHerbCompounds
      tags: [herbs]
      summary: Get herb compounds
      description: Returns the phytochemical compound list for the given herb.
      parameters:
        - $ref: '#/components/parameters/HerbIdParam'
      responses:
        "200":
          description: Compounds found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompoundsResponse'
        "400":
          $ref: '#/components/responses/BadRequest'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/TooManyRequests'
        "500":
          $ref: '#/components/responses/InternalError'

  /v1/herbs/{id}/thermal:
    get:
      operationId: getHerbThermal
      tags: [herbs]
      summary: Get herb thermal protocol
      description: Returns the vaporisation thermal extraction protocol for the given herb.
      parameters:
        - $ref: '#/components/parameters/HerbIdParam'
      responses:
        "200":
          description: Thermal protocol found
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ThermalProtocol'
        "400":
          $ref: '#/components/responses/BadRequest'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/TooManyRequests'
        "500":
          $ref: '#/components/responses/InternalError'

  /v1/matrix-positions:
    get:
      operationId: getMatrixPositions
      tags: [matrix]
      summary: Get herb matrix positions
      description: |
        Returns all matrix layout coordinate sets. Filter to a single layout
        with the optional `layout` parameter.
      parameters:
        - name: layout
          in: query
          required: false
          description: Filter by layout name (e.g. `mood-default`)
          schema:
            type: string
            maxLength: 100
            pattern: '^[a-zA-Z0-9 \-_,]{0,100}$'
      responses:
        "200":
          description: Matrix positions
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MatrixLayout'
        "400":
          $ref: '#/components/responses/BadRequest'
        "404":
          $ref: '#/components/responses/NotFound'
        "429":
          $ref: '#/components/responses/TooManyRequests'
        "500":
          $ref: '#/components/responses/InternalError'

  /v1/moods:
    get:
      operationId: listMoods
      tags: [meta]
      summary: List available mood tags
      description: Returns all mood enum values that can be used as filter values for `GET /v1/herbs`.
      responses:
        "200":
          description: Available moods
          headers:
            X-Request-Id:
              $ref: '#/components/headers/XRequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MoodsResponse'
        "429":
          $ref: '#/components/responses/TooManyRequests'
        "500":
          $ref: '#/components/responses/InternalError'

  /v1/openapi.yaml:
    get:
      operationId: getOpenAPISpec
      tags: [meta]
      summary: OpenAPI specification
      description: Returns the raw OpenAPI 3.1 YAML specification document.
      responses:
        "200":
          description: OpenAPI YAML
        "429":
          $ref: '#/components/responses/TooManyRequests'
          content:
            text/yaml:
              schema:
                type: string

  /v1/docs:
    get:
      operationId: getSwaggerUI
      tags: [meta]
      summary: Swagger UI
      description: Serves the Swagger UI HTML explorer for this API.
      responses:
        "200":
          description: Swagger UI HTML
        "429":
          $ref: '#/components/responses/TooManyRequests'
          content:
            text/html:
              schema:
                type: string

  /healthz:
    get:
      operationId: healthCheck
      tags: [meta]
      summary: Health check
      description: Returns `{"status":"ok"}` when the service is healthy.
      security: []
      responses:
        "200":
          description: Service healthy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
              example:
                status: ok
        "429":
          $ref: '#/components/responses/TooManyRequests'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Api-Key
      description: |
        Optional API key for elevated rate-limit tier.
        Format: `hf_` prefix + 32 URL-safe base64 characters (35 chars total).
        Obtain a developer key via the HerbFull developer form.

  parameters:
    HerbIdParam:
      name: id
      in: path
      required: true
      description: Herb slug identifier (lowercase, alphanumeric, hyphens, max 64 chars)
      schema:
        type: string
        pattern: '^[a-z0-9\-]{1,64}$'
        maxLength: 64
        example: lavender

    QueryParam:
      name: q
      in: query
      required: false
      description: Full-text search across name, botanical name, and description
      schema:
        type: string
        maxLength: 100
        pattern: '^[a-zA-Z0-9 \-_,]{0,100}$'

    MoodParam:
      name: mood
      in: query
      required: false
      description: Filter by mood tag (e.g. `relaxing`, `energizing`)
      schema:
        type: string
        maxLength: 100
        pattern: '^[a-zA-Z0-9 \-_,]{0,100}$'

    SymptomParam:
      name: symptom
      in: query
      required: false
      description: Filter by symptom or therapeutic indication keyword
      schema:
        type: string
        maxLength: 100
        pattern: '^[a-zA-Z0-9 \-_,]{0,100}$'

    CompoundParam:
      name: compound
      in: query
      required: false
      description: Filter by compound name (e.g. `linalool`, `thc`)
      schema:
        type: string
        maxLength: 100
        pattern: '^[a-zA-Z0-9 \-_,]{0,100}$'

  headers:
    XRequestId:
      description: Unique request trace ID
      schema:
        type: string

  responses:
    BadRequest:
      description: Invalid input — parameter too long or contains disallowed characters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: invalid characters in query parameter
            code: 400

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: herb not found
            code: 404

    TooManyRequests:
      description: Rate limit exceeded
      headers:
        Retry-After:
          description: Seconds to wait before retrying
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Rate limit ceiling for the current window
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Number of requests remaining in the current window
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when the rate limit window resets
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: rate limit exceeded
            code: 429

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: internal server error
            code: 500

  schemas:
    PhytochemicalCompound:
      type: object
      required: [name, concentration, role]
      properties:
        name:
          type: string
          description: Compound name
          example: Linalool
        boilingPointC:
          type: integer
          description: Boiling point in °C; null for non-volatile compounds
          example: 198
        concentration:
          type: string
          enum: [trace, minor, major, very-major]
          description: Relative concentration in the herb
        concentrationPercent:
          type: string
          description: Known percentage range when available
          example: "35–45%"
        role:
          type: string
          description: Pharmacological or aromatic role
          example: "Primary anxiolytic terpene"
        safetyNote:
          type: string
          description: Safety note if applicable

    ThermalPhase:
      type: object
      required: [name, tempRangeMin, tempRangeMax, keyCompounds, effect]
      properties:
        name:
          type: string
          example: Terpene Release
        tempRangeMin:
          type: integer
          description: Phase start temperature in °C
          example: 130
        tempRangeMax:
          type: integer
          description: Phase end temperature in °C
          example: 160
        keyCompounds:
          type: array
          items:
            type: string
          description: Primary compounds released in this phase
        effect:
          type: string
          description: User-readable effect description

    ThermalProtocol:
      type: object
      required: [phases, optimalTempC, maxSafeTempC, grindRecommendation, thermalWarnings]
      properties:
        phases:
          type: array
          minItems: 3
          maxItems: 3
          items:
            $ref: '#/components/schemas/ThermalPhase'
        optimalTempC:
          type: integer
          description: Recommended vaporisation temperature in °C
          example: 165
        maxSafeTempC:
          type: integer
          description: Maximum safe vaporisation temperature in °C
          example: 195
        grindRecommendation:
          type: string
          enum: [coarse, medium, fine]
          description: Recommended grind size
        thermalWarnings:
          type: array
          items:
            type: string
          description: Safety warnings for vaporisation

    HerbProfile:
      type: object
      required: [id, name, botanicalName, category, vaporizableStatus, safetyFlag,
                 intensityLevel, moods, primaryActions, thermalProtocol, evidenceLevel]
      properties:
        id:
          type: string
          description: Unique herb slug
          example: lavender
        name:
          type: string
          example: Lavender
        botanicalName:
          type: string
          example: Lavandula angustifolia
        category:
          type: string
          enum: [core-50, respiratory, adaptogen, kitchen-pharmacy, nootropic]
        vaporizableStatus:
          type: string
          enum: [yes, partial, caution, no]
        safetyFlag:
          type: string
          enum: [safe, caution, strong-caution, do-not-vaporize]
        safetyWarningShort:
          type: string
          description: Brief safety warning if safetyFlag is not 'safe'
        intensityLevel:
          type: string
          enum: [mild, mild-moderate, moderate, moderate-strong, strong]
        moods:
          type: array
          items:
            type: string
        primaryActions:
          type: array
          items:
            type: string
        contraindications:
          type: array
          items:
            type: string
        drugInteractions:
          type: array
          items:
            type: string
        pregnancySafety:
          type: string
          enum: [traditional-safe, culinary-safe, caution, avoid, unknown]
        pregnancyNote:
          type: string
        primaryCompounds:
          type: array
          items:
            $ref: '#/components/schemas/PhytochemicalCompound'
        entourageNotes:
          type: string
        thermalProtocol:
          $ref: '#/components/schemas/ThermalProtocol'
        evidenceLevel:
          type: string
          enum: [traditional, preclinical, clinical-limited, clinical-rct,
                 rct, systematic-review]
        commissionEApproved:
          type: boolean
        emaApproved:
          type: boolean
        researchSources:
          type: array
          items:
            type: string
        shortDescription:
          type: string
        tempRangeMin:
          type: integer
        tempRangeMax:
          type: integer

    HerbListItem:
      type: object
      required: [id, name, botanicalName, category, vaporizableStatus, safetyFlag,
                 intensityLevel, moods]
      properties:
        id:
          type: string
          example: lavender
        name:
          type: string
          example: Lavender
        botanicalName:
          type: string
          example: Lavandula angustifolia
        category:
          type: string
          enum: [core-50, respiratory, adaptogen, kitchen-pharmacy, nootropic]
        vaporizableStatus:
          type: string
          enum: [yes, partial, caution, no]
        safetyFlag:
          type: string
          enum: [safe, caution, strong-caution, do-not-vaporize]
        safetyWarningShort:
          type: string
        intensityLevel:
          type: string
          enum: [mild, mild-moderate, moderate, moderate-strong, strong]
        moods:
          type: array
          items:
            type: string
        symptoms:
          type: array
          maxItems: 5
          items:
            type: string
          description: First 5 primary actions
        compoundNames:
          type: array
          items:
            type: string
          description: Compound names only (no details)
        shortDescription:
          type: string

    HerbListResponse:
      type: object
      required: [items, total]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/HerbListItem'
        total:
          type: integer
          description: Total number of items in this response

    CompoundsResponse:
      type: object
      required: [compounds]
      properties:
        compounds:
          type: array
          items:
            $ref: '#/components/schemas/PhytochemicalCompound'

    MatrixPosition:
      type: object
      required: [herbId, x, y]
      properties:
        herbId:
          type: string
          example: lavender
        x:
          type: number
          format: float
          description: X coordinate (0–100 percentage)
          example: 45.75
        y:
          type: number
          format: float
          description: Y coordinate (0–100 percentage)
          example: 81.51
        layoutId:
          type: string
          example: mood-default

    MatrixLayout:
      type: object
      required: [id, positions]
      properties:
        id:
          type: string
          description: Layout name
          example: mood-default
        positions:
          type: array
          items:
            $ref: '#/components/schemas/MatrixPosition'

    MoodsResponse:
      type: object
      required: [moods]
      properties:
        moods:
          type: array
          items:
            type: string
          description: Available mood tag values for filtering
          example: [anti_anxiety, digestive, dream, energizing, focusing,
                    grounding, pain_relief, relaxing, respiratory, sedating,
                    spiritual, uplifting]

    HealthResponse:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [ok]

    ErrorResponse:
      type: object
      required: [error, code]
      properties:
        error:
          type: string
          description: Human-readable error description (static, never reflects user input)
        code:
          type: integer
          description: HTTP status code
