openapi: 3.1.0
info:
  # Do not change the title, if the title changes, the import paths will be broken
  title: Api
  version: 0.1.0
  description: FERNS — Federated Ecological Resource Network System API
servers:
  - url: /api
    description: Base API path
tags:
  - name: health
    description: Health operations
  - name: bonap
    description: BONAP North American Plant Atlas — Distribution Maps
  - name: gbif
    description: GBIF Taxonomic Backbone, Name Reconciliation, and Occurrence Records
  - name: inat
    description: iNaturalist — Observations, Phenology, and Species Appearance
  - name: miflora
    description: Michigan Flora — Vascular Plants of Michigan
  - name: coefficient
    description: Coefficient of Conservatism — Floristic Quality Assessment C-Value Reference
  - name: wetland-indicator
    description: Wetland Indicator Status & Coefficient of Wetness — USDA NRCS NWPL Reference
  - name: wucols
    description: WUCOLS — Water Use Classifications of Landscape Species
  - name: s2c
    description: Seeds to Community Washtenaw — Annual Native Plant Species Availability
  - name: universal-fqa
    description: Universal FQA — Federated Floristic Quality Assessment Databases
  - name: lcscg
    description: Lake County Seed Collection Guides — Native Seed Identification and Collection
  - name: mnfi
    description: Michigan Natural Features Inventory — Natural Community Types and County Element Occurrences
  - name: natureserve
    description: NatureServe Explorer — Species Conservation Status and Ecological Systems
  - name: gobotany
    description: Go Botany — Native Plant Trust Species Pages
  - name: google-images
    description: Google Images — Species Photo Search URLs
  - name: illinois-wildflowers
    description: Illinois Wildflowers — Species Reference Pages
  - name: minnesota-wildflowers
    description: Minnesota Wildflowers — Species Reference Pages
  - name: missouri-plants
    description: Missouri Plants — Species Reference Pages
  - name: prairie-moon
    description: Prairie Moon Nursery — Native Seed and Plant Catalog
  - name: usda-plants
    description: USDA PLANTS Database — Official Federal Plant Data
  - name: lady-bird-johnson
    description: Lady Bird Johnson Wildflower Center — Native Plant Information
  - name: botanical-refs
    description: Botanical References — Aggregated URL Lookup Across All Reference Sources
  - name: registry
    description: FERNS Service Registry
paths:
  /healthz:
    get:
      operationId: healthCheck
      tags: [health]
      summary: Health check
      description: Returns server health status
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthStatus"

  /bonap/map:
    get:
      operationId: getBonapMap
      tags: [bonap]
      summary: Get BONAP distribution map
      description: >
        Returns the BONAP distribution map URL for a given species or genus.
        The map image is served directly from BONAP's server — FERNS returns
        the URL only and never proxies or stores the image.
        Results are cached permanently for found species; 30 days for not-found.
      parameters:
        - name: genus
          in: query
          required: true
          description: >
            Genus name. First letter capitalized, remainder lowercase (e.g. Asclepias).
            The service normalizes to title case before URL construction.
          schema:
            type: string
            minLength: 1
        - name: species
          in: query
          required: false
          description: >
            Species epithet, all lowercase (e.g. tuberosa).
            Required when map_type=county_species or map_type=state_species — omitting species returns 400.
            Subspecies/variety designations are stripped automatically; species_stripped is set true.
          schema:
            type: string
        - name: map_type
          in: query
          required: false
          description: >
            Map type to retrieve. county_species (default) returns a county-level distribution map.
            state_species returns a state/continental-level distribution map.
            Both types require a genus+species pair.
          schema:
            type: string
            enum: [county_species, state_species]
            default: county_species
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from BONAP.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Map lookup complete (found or not found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BonapMapResponse"
        "400":
          description: Invalid input (missing genus, missing species, invalid map_type)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /bonap/metadata:
    get:
      operationId: getBonapMetadata
      tags: [bonap]
      summary: BONAP service metadata
      description: >
        Returns static service identity, data vintage, attribution, permission status,
        and the full color key array. Use this to populate 'About this data' panels
        in any application displaying BONAP maps.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BonapMetadataResponse"

  /gbif/match:
    get:
      operationId: getGbifMatch
      tags: [gbif]
      summary: Match a scientific name against the GBIF backbone taxonomy
      description: >
        Matches a plant scientific name against the GBIF Backbone Taxonomy.
        Returns the matched taxon's key, canonical name, taxonomic status, match type,
        confidence, full classification, and provenance. Results are cached 30 days for
        hits; 7 days for no-match. A no-match is signaled by matchType=NONE in the
        response data — GBIF always returns HTTP 200. DOUBTFUL status means the name
        exists but taxonomic standing is uncertain; treat as unusable.
      parameters:
        - name: name
          in: query
          required: true
          description: Scientific name to match (e.g. Asclepias tuberosa)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from GBIF
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Match result (check matchType — NONE means no match found)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifMatchResponse"
        "400":
          description: Missing or invalid name parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/reconcile:
    get:
      operationId: getGbifReconcile
      tags: [gbif]
      summary: Fetch synonyms and common names for a GBIF taxon
      description: >
        Given a GBIF usageKey, returns all synonyms (all ranks, unfiltered) and
        vernacular names. Synonyms cached 30 days; vernacular names 90 days.
        If a synonym usageKey is passed, the server automatically resolves it to
        the accepted taxon's usageKey before fetching; the resolved_from_synonym_key
        field in the response indicates when this occurred. The resolved accepted
        taxon's synonyms and vernacular names are always returned.
      parameters:
        - name: usageKey
          in: query
          required: true
          description: GBIF backbone usageKey for the accepted taxon
          schema:
            type: integer
      responses:
        "200":
          description: Synonyms and vernacular names for this taxon
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifReconcileResponse"
        "400":
          description: Missing or invalid usageKey
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/occurrences:
    get:
      operationId: getGbifOccurrences
      tags: [gbif]
      summary: Fetch occurrence records for a GBIF taxon with configurable geography
      description: >
        Returns occurrence count and up to 300 recent georeferenced records from GBIF
        for the given taxon. Geography is configurable via three mutually exclusive modes:
        countries (comma-separated ISO codes, OR'd), continent (single GBIF continent
        value), or bbox (minLat,minLon,maxLat,maxLon). All records include
        hasCoordinate=true and hasGeospatialIssue=false filters. Cached 7 days per
        geography combination. Fetch on demand only.
      parameters:
        - name: usageKey
          in: query
          required: true
          description: GBIF backbone usageKey
          schema:
            type: integer
        - name: countries
          in: query
          required: false
          description: >
            Comma-separated ISO 3166-1 alpha-2 country codes (e.g. US,CA,MX).
            Mutually exclusive with continent and bbox.
          schema:
            type: string
        - name: continent
          in: query
          required: false
          description: >
            GBIF continent value. One of AFRICA, ANTARCTICA, ASIA, EUROPE,
            NORTH_AMERICA, OCEANIA, SOUTH_AMERICA. Mutually exclusive with countries and bbox.
          schema:
            type: string
            enum: [AFRICA, ANTARCTICA, ASIA, EUROPE, NORTH_AMERICA, OCEANIA, SOUTH_AMERICA]
        - name: bbox
          in: query
          required: false
          description: >
            Bounding box as minLat,minLon,maxLat,maxLon (decimal degrees, WGS84).
            e.g. 24.396,-125.0,49.384,-66.93 for continental US.
            Mutually exclusive with countries and continent.
          schema:
            type: string
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from GBIF
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Occurrence count and recent records
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifOccurrencesResponse"
        "400":
          description: Missing usageKey or invalid geography parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/search:
    get:
      operationId: getGbifSearch
      tags: [gbif]
      summary: Search GBIF species by common (vernacular) name
      description: >
        Searches GBIF for species whose vernacular names match the query string.
        Returns a ranked list of candidate species. Not cached — query space is
        too wide for meaningful caching. Applications are responsible for
        disambiguation when multiple candidates are returned.
      parameters:
        - name: q
          in: query
          required: true
          description: Common name search string (e.g. butterfly milkweed)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: List of candidate species matching the common name query
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifSearchResponse"
        "400":
          description: Missing query parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/metadata:
    get:
      operationId: getGbifMetadata
      tags: [gbif]
      summary: GBIF service metadata and controlled vocabularies
      description: >
        Returns service identity, attribution, permission status, controlled
        vocabulary definitions (basisOfRecord, matchType, taxonomicStatus,
        occurrenceStatus), and the registry entry for the GBIF service.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifMetadataResponse"

  /inat/place:
    get:
      operationId: getInatPlace
      tags: [inat]
      summary: Look up an iNaturalist place by name
      description: >
        Searches iNaturalist for places matching the query string and returns up to 5
        candidates. The iNaturalist place ID from this response is required to filter
        phenology and observation queries geographically. Place lookups are cached
        permanently — place IDs are stable once assigned. Applications select the correct
        place when multiple candidates are returned.
      parameters:
        - name: q
          in: query
          required: true
          description: Place name to search (e.g. Washtenaw County, Michigan, Sleeping Bear Dunes)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from iNaturalist
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Place lookup complete (found or not found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatPlaceResponse"
        "400":
          description: Missing or empty query parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /inat/species:
    get:
      operationId: getInatSpecies
      tags: [inat]
      summary: Look up species appearance data from iNaturalist
      description: >
        Given a scientific name, returns what iNaturalist knows about that species:
        a representative photo, a Wikipedia description (raw HTML — strip tags before
        display), common names in all languages, native/introduced status by place,
        global observation count, IUCN conservation status, and the iNaturalist taxon ID
        needed for phenology queries. Two API calls are always required. Results cached
        30 days for found species; 7 days for not-found. match_type indicates whether
        the name matched exactly (exact) or whether the first result was used as a
        fallback (fallback — applications should flag this to users).
      parameters:
        - name: name
          in: query
          required: true
          description: Scientific name to look up (e.g. Asclepias tuberosa)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from iNaturalist
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Species lookup complete (found or not found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatSpeciesResponse"
        "400":
          description: Missing or empty name parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /inat/histogram:
    get:
      operationId: getInatHistogram
      tags: [inat]
      summary: Passthrough for iNaturalist observations/histogram
      description: >
        Passthrough for iNaturalist's GET /observations/histogram endpoint.
        Returns the raw iNaturalist response containing observation counts by
        calendar month (month_of_year key in results). Cached 7 days keyed by
        taxon_id and sorted set of place_ids.
      parameters:
        - name: taxon_id
          in: query
          required: true
          description: iNaturalist numeric taxon ID (from the species endpoint)
          schema:
            type: integer
        - name: place_id
          in: query
          required: false
          description: >
            One or more iNaturalist place IDs, comma-separated (e.g. 2649 or 2649,986).
            Place IDs from the place lookup endpoint. Sorted ascending when building cache key.
            When omitted, returns global (worldwide) data.
          schema:
            type: string
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from iNaturalist
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Raw iNaturalist histogram response in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatHistogramResponse"
        "400":
          description: Missing or invalid taxon_id or place_id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /inat/field-values:
    get:
      operationId: getInatFieldValues
      tags: [inat]
      summary: Passthrough for iNaturalist observations/popular_field_values
      description: >
        Passthrough for iNaturalist's GET /observations/popular_field_values endpoint.
        Returns the raw iNaturalist response containing controlled annotation field
        values with per-month counts. Stage labels include Flowers, Flower Buds,
        Fruits or Seeds, No Flowers or Fruits, Green Leaves, Colored Leaves, No Live
        Leaves, Breaking Leaf Buds. Cached 7 days keyed by taxon_id, sorted place_ids,
        and verifiable flag.
      parameters:
        - name: taxon_id
          in: query
          required: true
          description: iNaturalist numeric taxon ID (from the species endpoint)
          schema:
            type: integer
        - name: place_id
          in: query
          required: false
          description: >
            One or more iNaturalist place IDs, comma-separated (e.g. 2649 or 2649,986).
            When omitted, returns global data.
          schema:
            type: string
        - name: verifiable
          in: query
          required: false
          description: >
            If true (default), restricts to verifiable observations. Pass false to
            include all quality grades.
          schema:
            type: boolean
            default: true
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from iNaturalist
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Raw iNaturalist popular_field_values response in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatFieldValuesResponse"
        "400":
          description: Missing or invalid taxon_id or place_id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /inat/observations:
    get:
      operationId: getInatObservations
      tags: [inat]
      summary: Get iNaturalist observation URL construction for a species or place
      description: >
        This endpoint does not return or cache observation records. It constructs and
        returns direct URLs that applications use to link to iNaturalist or to query
        the API directly. Individual observation records are live event data that changes
        constantly — FERNS provides URL patterns for applications to fetch them fresh.
        Both taxon_id and place_id are optional. When provided, they are validated and
        incorporated into the returned URLs.
      parameters:
        - name: taxon_id
          in: query
          required: false
          description: iNaturalist taxon ID to incorporate into URLs
          schema:
            type: integer
        - name: place_id
          in: query
          required: false
          description: iNaturalist place ID to incorporate into URLs
          schema:
            type: integer
      responses:
        "200":
          description: Constructed observation URLs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatObservationsResponse"
        "400":
          description: Invalid taxon_id or place_id (must be positive integers when provided)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /inat/metadata:
    get:
      operationId: getInatMetadata
      tags: [inat]
      summary: iNaturalist service metadata
      description: >
        Returns service identity, attribution, permission status, and the full
        registry entry for the iNaturalist service. Use this to populate
        'About this data' panels in any application displaying iNaturalist data.
        Also seeds the iNaturalist entry in the FERNS source registry.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatMetadataResponse"

  /miflora/species:
    get:
      operationId: getMifloraSpecies
      tags: [miflora]
      summary: Look up a species in Michigan Flora
      description: >
        Looks up a vascular plant species in the Michigan Flora REST API.
        Returns the complete passthrough response from Michigan Flora: all search
        records from flora_search_sp, spec_text (taxonomic details and description),
        synonyms, and plant images. Two to four API calls are required per lookup.
        Results are cached permanently (no TTL) — Michigan Flora data does not change;
        use ?refresh=true to force a re-fetch from upstream.
        The pimage_info object in the response is addtively enriched by FERNS with two
        computed fields: image_url and thumbnail_url (constructed from the plant_id and
        image_id using the Michigan Flora static asset URL formula). All other source
        fields are returned unchanged.
        The st field in source records uses the literal string 'NULL' (not JSON null)
        for unknown or absent status — this is a quirk of the Michigan Flora API.
        The c field is always a string; '*' indicates an adventive (non-native) species.
        Scientific names for adventive species are returned ALL-CAPS by the source API.
      parameters:
        - name: name
          in: query
          required: true
          description: Scientific name to look up (e.g. Asclepias tuberosa)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from Michigan Flora API
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Species lookup complete (found or not found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraSpeciesResponse"
        "400":
          description: Missing or empty name parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Michigan Flora API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /miflora/counties:
    get:
      operationId: getMifloraCounties
      tags: [miflora]
      summary: Get county-level occurrence records for a Michigan Flora species by name
      description: >
        Returns county-level occurrence records for all 83 Michigan counties for
        the given species name. Two-step lookup: first calls flora_search_sp?scientific_name={name}
        to resolve the plant_id, then calls locs_sp?id={plant_id} for county data.
        Results are the raw passthrough response from the Michigan Flora county API endpoint.
        Cached permanently (no TTL) — Michigan Flora data does not change;
        use ?refresh=true to force a re-fetch from upstream.
      parameters:
        - name: name
          in: query
          required: true
          description: Scientific name to look up county records for (e.g. Asclepias tuberosa)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from Michigan Flora API
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: County occurrence records (check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraCountiesResponse"
        "400":
          description: Missing or empty name parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Michigan Flora API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /miflora/images:
    get:
      operationId: getMifloraImages
      tags: [miflora]
      summary: Get the full photo gallery for a Michigan Flora species
      description: >
        Returns all images from the Michigan Flora allimage_info endpoint for a given species,
        each enriched with constructed absolute image_url and thumbnail_url fields.
        Two-step lookup: flora_search_sp resolves the plant_id, then allimage_info fetches
        all available photos. Results are cached permanently (no TTL) — Michigan Flora image
        data does not change. Use ?refresh=true to force a re-fetch.
        Image URL formula (reverse-engineered from Michigan Flora frontend):
        Full: https://michiganflora.net/static/species_images/_pid_{plant_id}/{image_id}.jpg
        Thumbnail: https://michiganflora.net/static/species_images/_pid_{plant_id}/thumb_{image_id}.jpg
      parameters:
        - name: name
          in: query
          required: true
          description: Scientific name to look up (e.g. Asclepias tuberosa)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from Michigan Flora API
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Image gallery (check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraImagesResponse"
        "400":
          description: Missing or empty name parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Michigan Flora API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /miflora/metadata:
    get:
      operationId: getMifloraMetadata
      tags: [miflora]
      summary: Michigan Flora service metadata
      description: >
        Returns service identity, attribution, permission status, and the full
        registry entry for the Michigan Flora service. Use this to populate
        'About this data' panels in any application displaying Michigan Flora data.
        Also seeds the Michigan Flora entry in the FERNS source registry.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraMetadataResponse"

  /coefficient-of-conservatism:
    get:
      operationId: getCoefficientByValue
      tags: [coefficient]
      summary: Look up a Coefficient of Conservatism value
      description: >
        Returns the full ecological definition for a given C-value (0–10) or '*' for non-native species.
        The Coefficient of Conservatism (C-value) is a Floristic Quality Assessment metric developed by
        Swink & Wilhelm (1994) to rate a plant species' fidelity to high-quality natural habitats.
        This is NOT the Coefficient of Wetness (W, -5 to +5) and NOT a Wetland Indicator Status code (OBL/FACW/FAC/FACU/UPL).
      parameters:
        - name: value
          in: query
          required: true
          description: >
            C-value to look up. Must be a string: '0'–'10' for native species, or '*' for non-native/adventive species.
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: C-value definition or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CoefficientResponse"
        "400":
          description: Missing or invalid value parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /coefficient-of-conservatism/all:
    get:
      operationId: getAllCoefficientValues
      tags: [coefficient]
      summary: Get all C-value definitions
      description: >
        Returns all 12 C-value definitions (0–10 and '*') in a single call.
        Useful for populating reference tables or caching locally.
      responses:
        "200":
          description: All C-value definitions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CoefficientAllResponse"

  /coefficient-of-conservatism/metadata:
    get:
      operationId: getCoefficientMetadata
      tags: [coefficient]
      summary: Coefficient of Conservatism service metadata
      description: >
        Returns service identity, permission status, and the full registry entry for the
        Coefficient of Conservatism source. Also seeds the registry entry on first call.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VocabularyMetadataResponse"

  /wetland-indicator:
    get:
      operationId: getWetlandIndicatorByCode
      tags: [wetland-indicator]
      summary: Look up a Wetland Indicator Status code
      description: >
        Returns the full definition for a given WIS code (OBL, FACW, FAC, FACU, or UPL)
        including its Coefficient of Wetness (W-value) and ecological description.
        WIS codes are defined by the USDA NRCS National Wetland Plant List (NWPL) under USACE authority.
        This is NOT WUCOLS (a nursery irrigation system using VL/L/M/H codes) and NOT the Coefficient of Conservatism (0–10).
      parameters:
        - name: code
          in: query
          required: true
          description: Wetland Indicator Status code to look up (OBL, FACW, FAC, FACU, or UPL). Case-insensitive.
          schema:
            type: string
            enum: [OBL, FACW, FAC, FACU, UPL]
      responses:
        "200":
          description: WIS code definition or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WetlandIndicatorResponse"
        "400":
          description: Missing or invalid code parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /wetland-indicator/w:
    get:
      operationId: getWetlandIndicatorByW
      tags: [wetland-indicator]
      summary: Look up a Wetland Indicator Status by Coefficient of Wetness (W-value)
      description: >
        Returns the WIS code and full definition for a given numeric W-value (-5, -3, 0, 3, or 5).
        W-values map directly to WIS codes: OBL=-5, FACW=-3, FAC=0, FACU=+3, UPL=+5.
      parameters:
        - name: value
          in: query
          required: true
          description: Numeric W-value to look up (-5, -3, 0, 3, or 5).
          schema:
            type: integer
            enum: [-5, -3, 0, 3, 5]
      responses:
        "200":
          description: WIS definition for the given W-value, or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WetlandIndicatorResponse"
        "400":
          description: Missing or invalid value parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /wetland-indicator/all:
    get:
      operationId: getAllWetlandIndicators
      tags: [wetland-indicator]
      summary: Get all Wetland Indicator Status definitions
      description: >
        Returns all five WIS code definitions (OBL, FACW, FAC, FACU, UPL) with their
        W-values and ecological descriptions in a single call.
      responses:
        "200":
          description: All WIS definitions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WetlandIndicatorAllResponse"

  /wetland-indicator/metadata:
    get:
      operationId: getWetlandIndicatorMetadata
      tags: [wetland-indicator]
      summary: Wetland Indicator Status service metadata
      description: >
        Returns service identity, permission status, and the full registry entry for the
        Wetland Indicator Status source. Also seeds the registry entry on first call.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VocabularyMetadataResponse"

  /wucols:
    get:
      operationId: getWucolsByCode
      tags: [wucols]
      summary: Look up a WUCOLS water use classification code
      description: >
        Returns the full definition for a given WUCOLS code (VL, L, M, or H)
        including ETo percentage range and irrigation description.
        WUCOLS is the UC Cooperative Extension system for rating supplemental irrigation needs
        of landscape plants. This is NOT the Wetland Indicator Status (OBL/FACW/FAC/FACU/UPL),
        which classifies natural wetland habitat occurrence, not irrigation needs.
      parameters:
        - name: code
          in: query
          required: true
          description: WUCOLS water use code to look up (VL, L, M, or H). Case-insensitive.
          schema:
            type: string
            enum: [VL, L, M, H]
      responses:
        "200":
          description: WUCOLS code definition or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WucolsResponse"
        "400":
          description: Missing or invalid code parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /wucols/all:
    get:
      operationId: getAllWucols
      tags: [wucols]
      summary: Get all WUCOLS water use classification definitions
      description: >
        Returns all four WUCOLS classification levels (VL, L, M, H) with their
        ETo ranges and descriptions in a single call.
      responses:
        "200":
          description: All WUCOLS definitions
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WucolsAllResponse"

  /wucols/metadata:
    get:
      operationId: getWucolsMetadata
      tags: [wucols]
      summary: WUCOLS service metadata
      description: >
        Returns service identity, permission status, and the full registry entry for the
        WUCOLS source. Also seeds the registry entry on first call.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VocabularyMetadataResponse"

  /s2c:
    get:
      operationId: getS2CSpeciesByYear
      tags: [s2c]
      summary: Get species list for a given Seeds to Community Washtenaw program year
      description: >
        Returns the list of botanical names offered by Seeds to Community Washtenaw
        for the specified program year. Program years are labeled by the calendar year
        in which the January–March growing workshops occur.
        Available years: 2023, 2024, 2025, 2026.
        Where tracked, includes neat_and_tidy and sweet_and_simple metadata flags.
      parameters:
        - name: year
          in: query
          required: true
          description: Program year (2023–2026).
          schema:
            type: integer
            enum: [2023, 2024, 2025, 2026]
      responses:
        "200":
          description: Species list for the given year, or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/S2CSpeciesResponse"
        "400":
          description: Missing or invalid year parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /s2c/years:
    get:
      operationId: getS2CYears
      tags: [s2c]
      summary: List available Seeds to Community Washtenaw program years
      description: >
        Returns all available program years with species counts and source notes.
        Use this to discover what data is available before querying /s2c?year=.
      responses:
        "200":
          description: Available program years
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/S2CYearsResponse"

  /s2c/metadata:
    get:
      operationId: getS2CMetadata
      tags: [s2c]
      summary: Seeds to Community Washtenaw service metadata
      description: >
        Returns service identity, permission status, and the full registry entry for the
        Seeds to Community Washtenaw source. Also seeds the registry entry on first call.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VocabularyMetadataResponse"

  /lcscg/metadata:
    get:
      operationId: getLcscgMetadata
      tags: [lcscg]
      summary: Lake County Seed Collection Guides service metadata
      description: >
        Returns service identity, permission status, guide count, species count, and the full registry
        entry for the Lake County Seed Collection Guides source. Also seeds the registry entry on first call.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LcscgMetadataResponse"

  /lcscg/guides:
    get:
      operationId: getLcscgGuides
      tags: [lcscg]
      summary: List all Lake County Seed Collection Guides
      description: >
        Returns all 12 guide records (Field Museum Guide IDs 1271–1282), including title, season,
        habitat type, license, Cloudinary folder name, and harvest notes.
      responses:
        "200":
          description: All available guides
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LcscgGuidesResponse"

  /lcscg/guide/{guideId}:
    get:
      operationId: getLcscgGuide
      tags: [lcscg]
      summary: Get species list for a single Lake County Seed Collection Guide
      description: >
        Returns all species records for the specified guide (Field Museum Guide ID 1271–1282).
        Each species record includes harvest notes, seed dispersal categories, photo date,
        plant family, and Cloudinary image URLs.
      parameters:
        - name: guideId
          in: path
          required: true
          description: Field Museum Guide ID (1271–1282).
          schema:
            type: integer
            minimum: 1271
            maximum: 1282
      responses:
        "200":
          description: Guide detail with species list, or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LcscgGuideResponse"
        "400":
          description: Invalid guideId
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /lcscg/species:
    get:
      operationId: getLcscgSpecies
      tags: [lcscg]
      summary: Search Lake County Seed Collection Guides species by name
      description: >
        Returns species records matching the given name (partial match against scientific_name
        and common_name, case-insensitive) across all 12 guides.
        Results include harvest notes, seed dispersal categories, photo date, plant family,
        guide context, and Cloudinary image URLs.
      parameters:
        - name: name
          in: query
          required: true
          description: >
            Scientific or common name to search (partial match, case-insensitive).
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Matching species records
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LcscgSpeciesResponse"
        "400":
          description: Missing name parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/sources:
    get:
      operationId: getSourcesIndex
      tags: [registry]
      summary: List all registered FERNS Knowledge Services
      description: >
        Returns a summary entry for every registered FERNS Knowledge Service.
        This is the primary discovery endpoint. Each entry contains enough
        information to make a routing decision without a follow-up call to
        individual /metadata endpoints.
      responses:
        "200":
          description: Registry index
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourcesIndexResponse"

  /universal-fqa/metadata:
    get:
      operationId: getUniversalFqaMetadata
      tags: [universal-fqa]
      summary: Universal FQA service metadata
      description: >
        Returns service identity, attribution, permission status, derivation descriptions,
        and the registry entry for the Universal FQA source.
      responses:
        "200":
          description: Service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UniversalFqaMetadataResponse"

  /universal-fqa/databases:
    get:
      operationId: getUniversalFqaDatabases
      tags: [universal-fqa]
      summary: List all Universal FQA regional databases
      description: >
        Returns all regional FQA databases available on universalfqa.org.
        Each entry contains an id, region name, year, and full citation string.
        The region and citation fields together describe geographic scope, ecoregion
        coverage, institutional source, and methodology — read both in full to
        determine relevance for a given project or location.
      responses:
        "200":
          description: List of all regional databases
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UniversalFqaDatabasesResponse"
        "502":
          description: universalfqa.org API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /universal-fqa/databases/{id}:
    get:
      operationId: getUniversalFqaDatabase
      tags: [universal-fqa]
      summary: Get a full Universal FQA database with all species records
      description: >
        Returns the complete parsed database for the given ID — all species records
        with named fields, plus the database-level summary metrics (total species,
        native/non-native counts, mean C values). On the first request for a given
        database ID, downloads the database from universalfqa.org and persists it to
        the FERNS PostgreSQL cache. Subsequent requests are served from the cache
        (cache_status: hit). Consumers may retrieve the full database and build their
        own in-memory structures without polling FERNS repeatedly.
      parameters:
        - name: id
          in: path
          required: true
          description: Universal FQA database ID (from the databases list)
          schema:
            type: integer
      responses:
        "200":
          description: Full database with all species records
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UniversalFqaDatabaseDetailResponse"
        "400":
          description: Invalid database ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: universalfqa.org API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /universal-fqa/species:
    get:
      operationId: getUniversalFqaSpecies
      tags: [universal-fqa]
      summary: Look up a species by scientific name in a Universal FQA database
      description: >
        Looks up a species by scientific name within the specified Universal FQA database.
        On the first request for a given database_id, downloads the full database from
        universalfqa.org and stores it in the FERNS PostgreSQL cache. Subsequent lookups
        for the same database_id are served from the cache. Matching is exact and
        case-insensitive.
        Returns all nine source fields: scientific_name, family, acronym, native, c, w,
        physiognomy, duration, common_name.
      parameters:
        - name: name
          in: query
          required: true
          description: Scientific name to look up (e.g. Lobelia cardinalis)
          schema:
            type: string
            minLength: 1
        - name: database_id
          in: query
          required: true
          description: >
            Universal FQA database ID. Use /universal-fqa/databases to list all available
            databases with their region and citation strings.
          schema:
            type: integer
      responses:
        "200":
          description: Species lookup result (check found field — false means not in this database)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UniversalFqaSpeciesResponse"
        "400":
          description: Missing or invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: universalfqa.org API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /universal-fqa/assessments:
    get:
      operationId: getUniversalFqaAssessments
      tags: [universal-fqa]
      summary: List public site assessments for a Universal FQA database
      description: >
        Returns all publicly shared site assessments for the specified database.
        Each entry contains the assessment id, name, date, site, and practitioner.
        Assessment county and state are only available in the individual assessment
        detail endpoint — the list does not include location fields.
        Michigan 2014 (ID 50) has 4800+ assessments; Michigan 2024 (ID 267) has 400+.
      parameters:
        - name: database_id
          in: query
          required: true
          description: Universal FQA database ID
          schema:
            type: integer
      responses:
        "200":
          description: List of public assessments for this database
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UniversalFqaAssessmentsResponse"
        "400":
          description: Missing or invalid database_id
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: universalfqa.org API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /universal-fqa/assessment/{id}:
    get:
      operationId: getUniversalFqaAssessment
      tags: [universal-fqa]
      summary: Get a single Universal FQA site assessment in full detail
      description: >
        Returns the full detail for a public site assessment: site name, date,
        city/county/state/country, practitioner, which FQA database was used,
        all computed FQI metrics (Total/Native FQI, Adjusted FQI, Mean C, Mean Wetness,
        species richness, physiognomy breakdown, duration breakdown), and the complete
        observed species list with all nine per-species source fields.
        Returns 404 if the assessment does not exist or is not public.
      parameters:
        - name: id
          in: path
          required: true
          description: Universal FQA assessment ID (from the assessments list)
          schema:
            type: integer
      responses:
        "200":
          description: Full assessment detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UniversalFqaAssessmentResponse"
        "404":
          description: Assessment not found or not public
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: universalfqa.org API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/sources/metadata:
    get:
      operationId: getSourcesMetadata
      tags: [registry]
      summary: Metadata for the FERNS Source Registry service itself
      description: >
        Returns metadata about the registry service itself — its identity,
        descriptions, and its own registry entry. Follows the same envelope
        pattern as all other FERNS /metadata endpoints.
      responses:
        "200":
          description: Registry service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourcesMetadataResponse"

  /v1/source-relationships:
    get:
      operationId: getSourceRelationships
      tags: [registry]
      summary: List cross-source relationships (overlaps, conflicts, complements)
      description: >
        Returns all documented relationships between FERNS sources — overlaps, conflicts,
        and complementary pairings. Optionally filter by source_id to get only relationships
        involving a specific source. Use this endpoint to understand whether two sources
        double-count data, disagree on terminology, or cover the same domain from different
        methodologies. Individual source metadata does not contain cross-source information;
        this endpoint is the canonical location for all inter-source knowledge.
      parameters:
        - name: source_id
          in: query
          required: false
          description: >
            If provided, returns only relationships where this source_id is one of the two parties.
          schema:
            type: string
      responses:
        "200":
          description: List of source relationships
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SourceRelationshipsResponse"

  /mnfi/metadata:
    get:
      operationId: getMnfiMetadata
      tags: [mnfi]
      summary: MNFI service metadata
      description: >
        Returns service identity, attribution, permission status, community class
        counts, county coverage, and cache statistics for the Michigan Natural
        Features Inventory (MNFI) service. Also seeds the MNFI entry in the FERNS
        source registry. Use this to populate 'About this data' panels in any
        application displaying MNFI community or county element data.
      responses:
        "200":
          description: MNFI service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MnfiMetadataResponse"

  /mnfi/communities:
    get:
      operationId: getMnfiCommunities
      tags: [mnfi]
      summary: List MNFI natural community types
      description: >
        Returns all Michigan Natural Features Inventory (MNFI) natural community
        types. Optionally filter by community class, group, or name substring
        (case-insensitive ILIKE match). All data is served from FERNS's local
        database seeded from the MNFI website — no upstream API call is made at
        query time. Returns community records sorted by class and name.
      parameters:
        - name: class
          in: query
          required: false
          description: Filter by community class (substring match, e.g. Terrestrial)
          schema:
            type: string
        - name: group
          in: query
          required: false
          description: Filter by community group (substring match, e.g. Forest)
          schema:
            type: string
        - name: name
          in: query
          required: false
          description: Filter by community name (substring match, e.g. oak savanna)
          schema:
            type: string
      responses:
        "200":
          description: List of MNFI natural community types (found or empty)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MnfiCommunitiesResponse"

  /mnfi/communities/{id}:
    get:
      operationId: getMnfiCommunity
      tags: [mnfi]
      summary: Get a single MNFI community by ID or slug
      description: >
        Returns the full profile for a single MNFI natural community type,
        including description, acreage estimates, and all characteristic plants
        grouped by life form. The {id} path parameter accepts either the numeric
        community_id (e.g. 1234) or the URL slug (e.g. southern-dry-chestnut-oak-forest).
        Plant lists are included inline — characteristic_plants.by_life_form groups
        species by life form (tree, shrub, herb, etc.).
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric community_id or URL slug for the community
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Community profile (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MnfiCommunityResponse"
        "404":
          description: No community found with the given id or slug
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /mnfi/communities/{id}/plants:
    get:
      operationId: getMnfiCommunityPlants
      tags: [mnfi]
      summary: Get characteristic plants for an MNFI community
      description: >
        Returns the characteristic plant list for an MNFI natural community,
        grouped by life form (tree, shrub, herb, graminoid, vine, etc.). The
        {id} path parameter accepts either the numeric community_id or the URL slug.
        This is the same plant data included inline in the community detail endpoint,
        exposed as a dedicated endpoint for applications that only need plant lists.
      parameters:
        - name: id
          in: path
          required: true
          description: Numeric community_id or URL slug for the community
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Plant list for this community grouped by life form (found or not-found — check found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MnfiCommunityPlantsResponse"
        "404":
          description: No community found with the given id or slug
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /mnfi/county-elements:
    get:
      operationId: getMnfiCountyElements
      tags: [mnfi]
      summary: Get MNFI significant natural features for a Michigan county
      description: >
        Returns MNFI Element Occurrence records for a given Michigan county.
        These records document where significant natural features (rare species,
        natural communities) have been observed. The county parameter is required;
        all 83 Michigan county names are accepted (case-insensitive). Optionally
        filter to only species or community element types with the type parameter.
        Data is served from FERNS's local database seeded from the MNFI county
        element download — no upstream API call at query time.
      parameters:
        - name: county
          in: query
          required: true
          description: >
            Michigan county name (e.g. Washtenaw, Leelanau). Case-insensitive.
            All 83 Michigan county names are accepted.
          schema:
            type: string
            minLength: 1
        - name: type
          in: query
          required: false
          description: >
            Filter by element type. Must be 'species' or 'community' when provided.
            When omitted, both types are returned.
          schema:
            type: string
            enum: [species, community]
      responses:
        "200":
          description: County element occurrences (found or empty — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MnfiCountyElementsResponse"
        "400":
          description: Missing county parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /natureserve/metadata:
    get:
      operationId: getNatureserveMetadata
      tags: [natureserve]
      summary: NatureServe service metadata
      description: >
        Returns service identity, attribution, permission status, cache statistics,
        and the full registry entry for the NatureServe Explorer service. Use this
        to populate 'About this data' panels in any application displaying NatureServe
        conservation status or ecosystem data. Also seeds the NatureServe entry in
        the FERNS source registry.
      responses:
        "200":
          description: NatureServe service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NatureserveMetadataResponse"

  /natureserve/species:
    get:
      operationId: getNatureserveSpecies
      tags: [natureserve]
      summary: Look up species conservation status from NatureServe Explorer
      description: >
        Queries NatureServe Explorer for conservation status data for a given
        scientific name, optionally scoped to a US state. Returns global rank
        (G-rank), national rank (N-rank), state rank (S-rank), IUCN category,
        federal status, state status, and a direct NatureServe Explorer URL for
        the species. Ranks follow NatureServe standard notation (e.g. G3, N2N3,
        S1). State rank is derived from NatureServe S-rank and reflects rarity
        status — it is not a formal statutory state listing. Results cached 30
        days per name+state combination.
      parameters:
        - name: name
          in: query
          required: true
          description: Scientific name to look up (e.g. Asclepias tuberosa)
          schema:
            type: string
            minLength: 1
        - name: state
          in: query
          required: false
          description: >
            Two-letter US state code to scope the state rank (e.g. MI, WI, OH).
            Defaults to MI (Michigan) when omitted. Must be a valid 2-letter code.
          schema:
            type: string
            default: MI
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from NatureServe Explorer
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Conservation status result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NatureserveSpeciesResponse"
        "400":
          description: Missing or invalid name or state parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: NatureServe Explorer API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /natureserve/ecosystems:
    get:
      operationId: getNatureserveEcosystems
      tags: [natureserve]
      summary: Look up ecological systems from NatureServe Explorer
      description: >
        Queries NatureServe Explorer for ecological systems matching a name or
        description. Returns a list of matching NatureServe ecological system
        records including NatureServe identifier, name, and classification details.
        Results cached 30 days per query string. Useful for identifying NatureServe
        ecosystem types corresponding to MNFI natural community types or other
        ecological classification systems.
      parameters:
        - name: name
          in: query
          required: true
          description: Ecosystem name or keyword to search (e.g. oak savanna, wet prairie)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from NatureServe Explorer
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Ecosystem search results (found or empty — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NatureserveEcosystemsResponse"
        "400":
          description: Missing or invalid name parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: NatureServe Explorer API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gobotany:
    get:
      operationId: getGobotany
      tags: [gobotany]
      summary: Look up a Go Botany species page URL
      description: >
        Constructs and validates the Go Botany (Native Plant Trust) species page URL
        for a given scientific name. The URL is constructed from the genus and species
        epithet and validated with a live HTTP GET request (HTTP 200 = found). Results
        are cached 7 days. The species parameter must be a binomial name (genus + epithet);
        partial names return 400.
      parameters:
        - name: species
          in: query
          required: true
          description: >
            Binomial scientific name (e.g. Acer rubrum). Genus and species epithet required.
            Both components must contain only letters.
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: URL lookup result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing or invalid species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gobotany/metadata:
    get:
      operationId: getGobotanyMetadata
      tags: [gobotany]
      summary: Go Botany service metadata
      description: >
        Returns service identity, URL construction strategy, attribution, and the
        full registry entry for the Go Botany (Native Plant Trust) service.
      responses:
        "200":
          description: Go Botany service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /google-images:
    get:
      operationId: getGoogleImages
      tags: [google-images]
      summary: Construct a Google Images search URL for a species
      description: >
        Constructs a Google Images search URL for a given scientific name. This
        endpoint always returns found=true and a direct Google Images URL —
        no validation is performed. The URL is constructed from the species
        parameter and is not cached. Useful for quickly providing a visual
        reference link without requiring an image database.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name to search for (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Google Images search URL for the species
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /google-images/metadata:
    get:
      operationId: getGoogleImagesMetadata
      tags: [google-images]
      summary: Google Images service metadata
      description: >
        Returns service identity, URL construction strategy, attribution, and the
        full registry entry for the Google Images species search service.
      responses:
        "200":
          description: Google Images service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /illinois-wildflowers:
    get:
      operationId: getIllinoisWildflowers
      tags: [illinois-wildflowers]
      summary: Look up an Illinois Wildflowers species page URL
      description: >
        Looks up the Illinois Wildflowers (illinoiswildflowers.info) page URL for a
        given scientific name. Illinois Wildflowers organizes some species across
        multiple sections; multiple URLs may be returned in the data.results array.
        Species list is imported from the site and served from FERNS's local database.
        Results cached per database import cycle.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: URL lookup result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /illinois-wildflowers/metadata:
    get:
      operationId: getIllinoisWildflowersMetadata
      tags: [illinois-wildflowers]
      summary: Illinois Wildflowers service metadata
      description: >
        Returns service identity, species count, import strategy, attribution, and
        the full registry entry for the Illinois Wildflowers service.
      responses:
        "200":
          description: Illinois Wildflowers service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /minnesota-wildflowers:
    get:
      operationId: getMinnesotaWildflowers
      tags: [minnesota-wildflowers]
      summary: Look up a Minnesota Wildflowers species page URL
      description: >
        Looks up the Minnesota Wildflowers (minnesotawildflowers.info) species page
        URL for a given scientific name. Species list is imported from the site and
        served from FERNS's local database — no live upstream call at query time.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: URL lookup result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /minnesota-wildflowers/metadata:
    get:
      operationId: getMinnesotaWildflowersMetadata
      tags: [minnesota-wildflowers]
      summary: Minnesota Wildflowers service metadata
      description: >
        Returns service identity, species count, import strategy, attribution, and
        the full registry entry for the Minnesota Wildflowers service.
      responses:
        "200":
          description: Minnesota Wildflowers service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /missouri-plants:
    get:
      operationId: getMissouriPlants
      tags: [missouri-plants]
      summary: Look up a Missouri Plants species page URL
      description: >
        Looks up the Missouri Plants (missouriplants.com) species page URL for a
        given scientific name. Species list is imported from the site and served from
        FERNS's local database — no live upstream call at query time.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: URL lookup result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /missouri-plants/metadata:
    get:
      operationId: getMissouriPlantsMetadata
      tags: [missouri-plants]
      summary: Missouri Plants service metadata
      description: >
        Returns service identity, species count, import strategy, attribution, and
        the full registry entry for the Missouri Plants service.
      responses:
        "200":
          description: Missouri Plants service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /prairie-moon:
    get:
      operationId: getPrairieMoon
      tags: [prairie-moon]
      summary: Look up a Prairie Moon Nursery species page URL
      description: >
        Looks up the Prairie Moon Nursery (prairiemoon.com) species catalog URL for
        a given scientific name. Species list is imported from the Prairie Moon
        sitemap and served from FERNS's local database. Returns the direct product
        page URL when found. No live upstream call at query time.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: URL lookup result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /prairie-moon/metadata:
    get:
      operationId: getPrairieMoonMetadata
      tags: [prairie-moon]
      summary: Prairie Moon Nursery service metadata
      description: >
        Returns service identity, indexed species count, sitemap import strategy,
        attribution, and the full registry entry for the Prairie Moon Nursery service.
      responses:
        "200":
          description: Prairie Moon service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /usda-plants:
    get:
      operationId: getUsdaPlants
      tags: [usda-plants]
      summary: Construct a USDA PLANTS search URL for a species
      description: >
        Constructs a USDA PLANTS Database search URL for a given scientific name.
        Direct species profile URLs require a USDA symbol code (e.g. ASYT) that cannot
        be derived from the scientific name alone, so FERNS provides a search URL
        instead. The response always returns found=false with a search_url in the data
        object. Applications should use the search_url to link users to USDA PLANTS.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: USDA PLANTS search URL (found is always false — profile URLs require a symbol code not derivable from scientific name)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /usda-plants/metadata:
    get:
      operationId: getUsdaPlantsMetadata
      tags: [usda-plants]
      summary: USDA PLANTS Database service metadata
      description: >
        Returns service identity, URL construction strategy, attribution, and the
        full registry entry for the USDA PLANTS Database service.
      responses:
        "200":
          description: USDA PLANTS service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /lady-bird-johnson:
    get:
      operationId: getLadyBirdJohnson
      tags: [lady-bird-johnson]
      summary: Construct a Lady Bird Johnson Wildflower Center search URL for a species
      description: >
        Constructs a Lady Bird Johnson Wildflower Center (wildflower.org) search URL
        for a given scientific name. Direct species profile URLs require an internal
        LBJWC plant ID not derivable from scientific names, so FERNS provides a search
        URL scoped by genus and species instead. The response returns found=false with
        a search_url in the data object — use search_url to link users to the LBJWC.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Lady Bird Johnson search URL (found is always false — profile URLs require an internal plant ID)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /lady-bird-johnson/metadata:
    get:
      operationId: getLadyBirdJohnsonMetadata
      tags: [lady-bird-johnson]
      summary: Lady Bird Johnson Wildflower Center service metadata
      description: >
        Returns service identity, URL construction strategy, attribution, and the
        full registry entry for the Lady Bird Johnson Wildflower Center service.
      responses:
        "200":
          description: Lady Bird Johnson service metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /botanical-refs:
    get:
      operationId: getBotanicalRefs
      tags: [botanical-refs]
      summary: Look up a species across all botanical reference sources at once
      description: >
        Fans out a species query to all configured botanical reference sources
        simultaneously: Go Botany, Google Images, Missouri Plants, Minnesota
        Wildflowers, Illinois Wildflowers, Prairie Moon Nursery, USDA PLANTS, and
        Lady Bird Johnson Wildflower Center. Returns a unified response with one
        entry per source. Sources that cannot resolve a direct profile URL (USDA
        PLANTS, Lady Bird Johnson) return found=false with a search_url instead.
        Go Botany validation requires a live HTTP GET — expect slightly higher
        latency for this endpoint.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Per-source URL lookup results across all botanical reference sources
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalRefsResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /botanical-refs/sites:
    get:
      operationId: getBotanicalRefsSites
      tags: [botanical-refs]
      summary: List all botanical reference sites configured in FERNS
      description: >
        Returns metadata about all botanical reference sources that are aggregated
        by the /botanical-refs endpoint. Each entry includes the site ID, display
        name, URL lookup strategy, and direct query and metadata URLs for the
        individual source endpoint.
      responses:
        "200":
          description: List of configured botanical reference sources
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BotanicalRefsSitesResponse"

components:
  schemas:
    HealthStatus:
      type: object
      properties:
        status:
          type: string
      required:
        - status

    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
        message:
          type: string

    FernsProvenance:
      type: object
      description: >
        Provenance block present on every FERNS API response.
        Both derivation fields are required — general_summary for general audiences,
        technical_details for researchers who need to evaluate and reproduce the data.
      required:
        - source_id
        - fetched_at
        - method
        - upstream_url
        - general_summary
        - technical_details
      properties:
        source_id:
          type: string
          description: Stable identifier for this data source (e.g. bonap-napa)
        fetched_at:
          type: string
          format: date-time
          description: When this record was obtained from the source
        method:
          type: string
          description: "How the data was obtained: api_fetch | blob_import | llm_synthesis"
        upstream_url:
          type: string
          description: Where this data came from (API endpoint, file path, or registry entry)
        general_summary:
          type: string
          description: Plain language description readable by a homeowner or community member
        technical_details:
          type: string
          description: >
            Research-grade description: methods, measurement protocols, algorithms,
            citations, and transformations — sufficient for a scientist to evaluate and reproduce
        matched_input:
          type: string
          description: >
            The normalized input that was actually used for this lookup (e.g., the name
            as queried). Present on endpoints that accept a name parameter.

    ColorKeyEntry:
      type: object
      required:
        - code
        - hex_approx
        - hex_is_approximate
        - name
        - description
        - layer
      properties:
        code:
          type: string
          description: Short string identifier for this color entry
        hex_approx:
          type: string
          description: Approximate hex color value derived from visual inspection of BONAP maps
        hex_is_approximate:
          type: boolean
          description: Always true — BONAP does not publish official hex values
        name:
          type: string
          description: BONAP's official status name for this color
        description:
          type: string
          description: Ecological meaning of this color
        layer:
          type: string
          enum: [state_background, county_fill]
          description: Whether this color applies to state backgrounds or county fills

    BonapAttribution:
      type: object
      required:
        - source_name
        - maintainer
        - organization
        - website
        - napa_url
        - citation_napa
        - citation_tdc
        - copyright_notice
      properties:
        source_name:
          type: string
        maintainer:
          type: string
        organization:
          type: string
        website:
          type: string
        napa_url:
          type: string
        citation_napa:
          type: string
        citation_tdc:
          type: string
        copyright_notice:
          type: string

    BonapMapData:
      type: object
      required:
        - map_type_served
        - genus
        - species_stripped
        - status
        - color_key_url
        - color_key_image_url
        - color_key
        - data_vintage
        - permission_granted
        - permission_status
        - attribution
        - cache_status
        - queried_at
      properties:
        map_url:
          type: string
          nullable: true
          description: >
            Direct URL to the PNG image on BONAP's server. Present when status is found.
            Null when not found. Applications display this via an img tag — do not proxy.
        map_type_served:
          type: string
          enum: [county_species, state_species]
        genus:
          type: string
          description: Normalized genus name as used in URL construction
        species:
          type: string
          nullable: true
          description: Normalized species epithet.
        species_stripped:
          type: boolean
          description: True if the caller provided a subspecific epithet that was stripped
        status:
          type: string
          enum: [found, not_found, unverified]
          description: >
            found — URL returned a valid image during cache population.
            not_found — BONAP returned a non-image response for this binomial.
            unverified — URL was returned from cache without re-verification.
        color_key_url:
          type: string
          description: URL of the BONAP map key reference page
        color_key_image_url:
          type: string
          description: >
            Direct URL to BONAP's authoritative composite color key GIF image.
            Display this image to show users the complete, pixel-perfect color key.
        color_key:
          type: array
          items:
            $ref: "#/components/schemas/ColorKeyEntry"
          description: Complete color key for interpreting BONAP maps
        data_vintage:
          type: string
          description: "NAPA maps: December 15, 2014. Color key documentation: February 8, 2024."
        permission_granted:
          type: boolean
          description: >
            Whether FERNS has received written permission from BONAP to reproduce
            and display map materials. Currently false. Applications must surface this field.
        permission_status:
          type: string
          description: >
            Human-readable permission status string. Matches the value from the metadata endpoint.
            Applications should display this when permission_granted is false.
        attribution:
          $ref: "#/components/schemas/BonapAttribution"
        cache_status:
          type: string
          enum: [hit, miss, bypassed]
        queried_at:
          type: string
          format: date-time
        note:
          type: string
          nullable: true
          description: >
            Human-readable diagnostic note. Non-null only when status is unverified — explains
            that BONAP's server did not return a definitive response during cache population and
            instructs the caller to retry with ?refresh=true. Null when status is found or not_found.

    BonapMapResponse:
      type: object
      description: Standard FERNS response envelope for BONAP map lookups
      required:
        - source_url
        - found
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: >
            Canonical BONAP page URL for this species or genus.
            Always constructed when possible and returned regardless of whether
            a map was found. Enables 'View on BONAP' links in applications.
        found:
          type: boolean
          description: Whether a BONAP map was found for this query
        data:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/BonapMapData"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    BonapMetadataResponse:
      type: object
      required:
        - service_id
        - service_name
        - data_vintage
        - permission_granted
        - permission_status
        - attribution
        - color_key
        - color_key_url
        - color_key_image_url
        - queried_at
      properties:
        service_id:
          type: string
        service_name:
          type: string
        data_vintage:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        attribution:
          $ref: "#/components/schemas/BonapAttribution"
        color_key:
          type: array
          items:
            $ref: "#/components/schemas/ColorKeyEntry"
        color_key_url:
          type: string
          description: URL of the BONAP map key reference page
        color_key_image_url:
          type: string
          description: >
            Direct URL to BONAP's authoritative composite color key GIF image.
            Display this image to show users the complete, pixel-perfect color key.
        queried_at:
          type: string
          format: date-time

    GbifSynonymRecord:
      type: object
      required:
        - key
        - canonicalName
        - scientificName
        - rank
        - taxonomicStatus
        - nameType
      properties:
        key:
          type: integer
        canonicalName:
          type: string
        scientificName:
          type: string
        rank:
          type: string
        taxonomicStatus:
          type: string
        nameType:
          type: string
        publishedIn:
          type: string
          nullable: true

    GbifVernacularRecord:
      type: object
      required:
        - vernacularName
        - language
      properties:
        vernacularName:
          type: string
        language:
          type: string
        country:
          type: string
          nullable: true
        source:
          type: string
          nullable: true

    GbifOccurrenceRecord:
      type: object
      required:
        - gbifID
        - decimalLatitude
        - decimalLongitude
        - country
        - basisOfRecord
        - occurrenceStatus
        - gbifOccurrenceUrl
      properties:
        gbifID:
          type: string
        decimalLatitude:
          type: number
        decimalLongitude:
          type: number
        country:
          type: string
        stateProvince:
          type: string
          nullable: true
        county:
          type: string
          nullable: true
        eventDate:
          type: string
          nullable: true
        year:
          type: integer
          nullable: true
        basisOfRecord:
          type: string
        institutionCode:
          type: string
          nullable: true
        datasetName:
          type: string
          nullable: true
        coordinateUncertaintyInMeters:
          type: number
          nullable: true
        occurrenceStatus:
          type: string
        gbifOccurrenceUrl:
          type: string

    GbifMatchData:
      type: object
      required:
        - matchType
        - matched_input
        - source_url
      properties:
        usageKey:
          type: integer
          nullable: true
          description: GBIF backbone usageKey. Null when matchType is NONE.
        canonicalName:
          type: string
          nullable: true
        scientificName:
          type: string
          nullable: true
        rank:
          type: string
          nullable: true
        status:
          type: string
          nullable: true
          description: ACCEPTED, SYNONYM, or DOUBTFUL
        accepted_usage_key:
          type: integer
          nullable: true
          description: Present when status is SYNONYM. FERNS addition.
        accepted_canonical_name:
          type: string
          nullable: true
          description: Present when status is SYNONYM. FERNS addition.
        confidence:
          type: integer
          nullable: true
        matchType:
          type: string
          enum: [EXACT, FUZZY, HIGHERRANK, NONE]
        kingdom:
          type: string
          nullable: true
        phylum:
          type: string
          nullable: true
        class_:
          type: string
          nullable: true
          description: GBIF 'class' field (JS reserved word renamed to class_)
        order_:
          type: string
          nullable: true
          description: GBIF 'order' field (JS reserved word renamed to order_)
        family:
          type: string
          nullable: true
        genus:
          type: string
          nullable: true
        species:
          type: string
          nullable: true
        kingdomKey:
          type: integer
          nullable: true
        phylumKey:
          type: integer
          nullable: true
        classKey:
          type: integer
          nullable: true
        orderKey:
          type: integer
          nullable: true
        familyKey:
          type: integer
          nullable: true
        genusKey:
          type: integer
          nullable: true
        speciesKey:
          type: integer
          nullable: true
        source_url:
          type: string
          nullable: true
          description: https://www.gbif.org/species/{usageKey} when usageKey is known
        matched_input:
          type: string
        matched_at:
          type: string
          format: date-time
          nullable: true
        cache_status:
          type: string
          enum: [hit, miss, bypassed]
          nullable: true

    GbifMatchResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
        found:
          type: boolean
        data:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/GbifMatchData"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    GbifReconcileData:
      type: object
      required:
        - usage_key
        - synonyms
        - synonym_count
        - vernacular_names
        - vernacular_name_count
        - synonyms_fetched_at
        - vernacular_fetched_at
      properties:
        usage_key:
          type: integer
        resolved_from_synonym_key:
          type: integer
          nullable: true
          description: The original synonym usageKey if auto-resolution occurred, null otherwise
        synonyms:
          type: array
          items:
            $ref: "#/components/schemas/GbifSynonymRecord"
        synonym_count:
          type: integer
        vernacular_names:
          type: array
          items:
            $ref: "#/components/schemas/GbifVernacularRecord"
        vernacular_name_primary:
          type: string
          nullable: true
        vernacular_name_count:
          type: integer

        synonyms_fetched_at:
          type: string
          format: date-time
        vernacular_fetched_at:
          type: string
          format: date-time

    GbifReconcileResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
        found:
          type: boolean
        data:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/GbifReconcileData"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    GbifOccurrencesData:
      type: object
      required:
        - usage_key
        - geography_mode
        - geography_params
        - occurrence_count
        - recent_occurrences
        - occurrence_last_fetched
        - source_url
      properties:
        usage_key:
          type: integer
        geography_mode:
          type: string
          enum: [countries, continent, bbox]
        geography_params:
          type: string
          description: Serialized geography parameters for display
        occurrence_count:
          type: integer
        occurrence_count_us:
          type: integer
          nullable: true
        recent_occurrences:
          type: array
          items:
            $ref: "#/components/schemas/GbifOccurrenceRecord"
        occurrence_last_fetched:
          type: string
          format: date-time
        source_url:
          type: string
          nullable: true
          description: GBIF occurrence search URL for this taxon and geography
        cache_status:
          type: string
          enum: [hit, miss, bypassed]
          nullable: true

    GbifOccurrencesResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
        found:
          type: boolean
        data:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/GbifOccurrencesData"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    GbifSearchCandidate:
      type: object
      required:
        - usageKey
        - canonicalName
        - scientificName
        - rank
        - status
      properties:
        usageKey:
          type: integer
        canonicalName:
          type: string
        scientificName:
          type: string
        rank:
          type: string
        status:
          type: string
        family:
          type: string
          nullable: true
        vernacularName:
          type: string
          nullable: true
        source_url:
          type: string

    GbifSearchResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
        found:
          type: boolean
        data:
          type: object
          nullable: true
          properties:
            query:
              type: string
            candidates:
              type: array
              items:
                $ref: "#/components/schemas/GbifSearchCandidate"
            count:
              type: integer
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    GbifVocabularyEntry:
      type: object
      required:
        - code
        - label
        - description
      properties:
        code:
          type: string
        label:
          type: string
        description:
          type: string

    GbifMetadataResponse:
      type: object
      required:
        - service_id
        - service_name
        - permission_granted
        - permission_status
        - attribution
        - vocabularies
        - queried_at
        - provenance
      properties:
        service_id:
          type: string
        service_name:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        attribution:
          type: object
          properties:
            source_name:
              type: string
            website:
              type: string
            license:
              type: string
            citation:
              type: string
            api_base_url:
              type: string
        vocabularies:
          type: object
          properties:
            basisOfRecord:
              type: array
              items:
                $ref: "#/components/schemas/GbifVocabularyEntry"
            matchType:
              type: array
              items:
                $ref: "#/components/schemas/GbifVocabularyEntry"
            taxonomicStatus:
              type: array
              items:
                $ref: "#/components/schemas/GbifVocabularyEntry"
            occurrenceStatus:
              type: array
              items:
                $ref: "#/components/schemas/GbifVocabularyEntry"
        registry_entry:
          type: object
          description: Full registry entry for this GBIF service source
          properties:
            source_id:
              type: string
            name:
              type: string
            knowledge_type:
              type: string
            status:
              type: string
            description:
              type: string
            input_summary:
              type: string
            output_summary:
              type: string
            dependencies:
              type: array
              items:
                type: string
            update_frequency:
              type: string
            known_limitations:
              type: string
            metadata_url:
              type: string
            explorer_url:
              type: string
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    InatPlaceResult:
      type: object
      required:
        - id
        - display_name
        - place_type
        - place_type_name
        - inat_url
      properties:
        id:
          type: integer
          description: iNaturalist numeric place ID. Use this in taxon_id + place_id queries.
        display_name:
          type: string
          description: Human-readable place name as returned by iNaturalist
        place_type:
          type: integer
          description: iNaturalist numeric place type code
        place_type_name:
          type: string
          description: Human-readable place type (e.g. County, State, Nation)
        inat_url:
          type: string
          description: https://www.inaturalist.org/places/{id}

    InatPlaceData:
      type: object
      required:
        - query
        - results
        - resolved_at
        - cache_status
      properties:
        query:
          type: string
          description: The place name that was searched
        results:
          type: array
          description: Up to 5 matching places. Applications select the correct one.
          items:
            $ref: "#/components/schemas/InatPlaceResult"
        resolved_at:
          type: string
          format: date-time
          description: When this lookup was cached
        cache_status:
          type: string
          enum: [hit, miss, bypassed]

    InatPlaceResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: The iNaturalist autocomplete API URL that was called to resolve this query. Null if unavailable.
        found:
          type: boolean
          description: false if no places matched the query
        data:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/InatPlaceData"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    InatConservationStatus:
      type: object
      required:
        - authority
        - status
        - status_name
      properties:
        authority:
          type: string
          description: Assessing authority (e.g. IUCN)
        status:
          type: string
          description: Status code (e.g. LC, VU, EN)
        status_name:
          type: string
          description: Full status name (e.g. Least Concern)

    InatCommonName:
      type: object
      required:
        - name
        - locale
      properties:
        name:
          type: string
        locale:
          type: string
          description: BCP 47 locale code (e.g. en, fr, de)

    InatNativeStatusEntry:
      type: object
      required:
        - status
        - place_name
      properties:
        status:
          type: string
          description: >
            native, introduced, endemic, or similar values as recorded
            by iNaturalist community members for this place
        place_name:
          type: string
          description: Name of the place where this status applies

    InatSpeciesResponse:
      type: object
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: https://www.inaturalist.org/taxa/{id}
        found:
          type: boolean
        cache_status:
          type: string
          enum: [hit, miss, bypassed]
        queried_at:
          type: string
          format: date-time
          description: When this FERNS request was processed
        data:
          type: object
          nullable: true
          additionalProperties: true
          description: >
            Complete iNaturalist taxon record from GET /taxa/{id}, passed through
            with all original field names intact. Key fields include: id (taxon ID),
            name (scientific name), preferred_common_name, observations_count,
            default_photo (object with medium_url), wikipedia_url, wikipedia_summary,
            conservation_status, and taxon_names.
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    InatHistogramResponse:
      type: object
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - data
        - provenance
      properties:
        source_url:
          type: string
          description: https://www.inaturalist.org/observations?taxon_id={id}&place_id={ids}
        found:
          type: boolean
        cache_status:
          type: string
          enum: [hit, miss, bypassed]
        queried_at:
          type: string
          format: date-time
          description: When this FERNS request was processed
        data:
          type: object
          description: >
            Raw iNaturalist observations/histogram response. The month_of_year object
            inside results contains observation counts keyed by month number string
            ('1' through '12').
          additionalProperties: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    InatFieldValuesResponse:
      type: object
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - data
        - provenance
      properties:
        source_url:
          type: string
          description: https://www.inaturalist.org/observations?taxon_id={id}&place_id={ids}
        found:
          type: boolean
        cache_status:
          type: string
          enum: [hit, miss, bypassed]
        queried_at:
          type: string
          format: date-time
          description: When this FERNS request was processed
        data:
          type: object
          description: >
            Raw iNaturalist observations/popular_field_values response. The results array
            contains entries with controlled_attribute, controlled_value, and month_of_year
            fields. Stage labels include Flowers, Flower Buds, Fruits or Seeds,
            No Flowers or Fruits, Green Leaves, Colored Leaves, No Live Leaves,
            Breaking Leaf Buds.
          additionalProperties: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    InatObservationsData:
      type: object
      required:
        - observations_by_species_url
        - observations_by_place_url
        - api_observations_endpoint
        - queried_at
      properties:
        taxon_id:
          type: integer
          nullable: true
        place_id:
          type: integer
          nullable: true
        observations_by_species_url:
          type: string
          description: iNaturalist website URL for sightings of a species
        observations_by_place_url:
          type: string
          description: iNaturalist website URL for everything observed in a place
        api_observations_endpoint:
          type: string
          description: Base API endpoint for programmatic queries. Full docs at iNaturalist API docs.
        queried_at:
          type: string
          format: date-time

    InatObservationsResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
        found:
          type: boolean
        data:
          $ref: "#/components/schemas/InatObservationsData"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    InatAttribution:
      type: object
      required:
        - source_name
        - website
        - license
        - citation
      properties:
        source_name:
          type: string
        website:
          type: string
        license:
          type: string
        citation:
          type: string
        api_base_url:
          type: string

    InatMetadataResponse:
      type: object
      required:
        - service_id
        - service_name
        - permission_granted
        - permission_status
        - attribution
        - queried_at
        - provenance
      properties:
        service_id:
          type: string
        service_name:
          type: string
        permission_granted:
          type: boolean
          description: True if FERNS has permission to use and expose this data. iNaturalist is open access — no permission request required.
        permission_status:
          type: string
        attribution:
          $ref: "#/components/schemas/InatAttribution"
        registry_entry:
          type: object
          description: Full registry entry for this iNaturalist service source
          properties:
            source_id:
              type: string
            name:
              type: string
            knowledge_type:
              type: string
            status:
              type: string
            description:
              type: string
            input_summary:
              type: string
            output_summary:
              type: string
            dependencies:
              type: array
              items:
                type: string
            update_frequency:
              type: string
            known_limitations:
              type: string
            metadata_url:
              type: string
            explorer_url:
              type: string
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    SourceSummary:
      type: object
      required:
        - source_id
        - name
        - knowledge_type
        - status
        - description
        - input_summary
        - output_summary
        - dependencies
        - update_frequency
        - known_limitations
        - metadata_url
        - explorer_url
      properties:
        source_id:
          type: string
          description: Stable identifier for this service (e.g. bonap-napa, gbif)
        name:
          type: string
          description: Human-readable service name
        knowledge_type:
          type: string
          description: "source_wrapper | derived_synthesis | aggregation | system | vocabulary_reference"
        status:
          type: string
          description: "live | draft | deprecated"
        description:
          type: string
          description: Plain-English description readable by a homeowner or community member
        input_summary:
          type: string
          description: What you send to query this service
        output_summary:
          type: string
          description: What you get back
        dependencies:
          type: array
          items:
            type: string
          description: source_ids this service depends on
        update_frequency:
          type: string
          description: How often the underlying data changes (e.g. live, annual, static)
        known_limitations:
          type: string
          description: Brief honest statement of gaps and caveats
        metadata_url:
          type: string
          description: Link to this service's full /metadata endpoint
        explorer_url:
          type: string
          description: Link to this service's Source Explorer UI
        permission_granted:
          type: boolean
          nullable: true
          description: >
            Whether FERNS has verified that use of this source is explicitly permitted.
            true = permission confirmed; null = not yet evaluated.
        permission_status:
          type: string
          description: >
            Plain-English description of the permission situation for this source.
            Covers license type, access model, and any restrictions on use.
        general_summary:
          type: string
          description: >
            One-paragraph plain-English description of this source suitable for
            non-technical users, agents, and routing decisions.
        technical_details:
          type: string
          description: >
            Technical description of how this source works, including data model,
            method, upstream URL, caching behavior, and known edge cases.

    SourcesIndexResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
        found:
          type: boolean
        data:
          type: object
          required:
            - sources
          properties:
            sources:
              type: array
              items:
                $ref: "#/components/schemas/SourceSummary"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    SourcesMetadataResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
        found:
          type: boolean
        data:
          $ref: "#/components/schemas/SourceSummary"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MifloraSpeciesResponse:
      type: object
      description: >
        FERNS envelope for Michigan Flora species lookup.
        data contains the passthrough response from Michigan Flora with one additive
        enrichment: pimage_info is augmented with image_url and thumbnail_url fields
        constructed from the plant_id and image_id using the Michigan Flora static
        asset URL formula. All other source fields are returned unchanged.
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: >
            Michigan Flora species page URL (https://michiganflora.net/species/{plant_id}).
            Null when species not found.
        found:
          type: boolean
          description: Whether the species was found in Michigan Flora
        cache_status:
          type: string
          enum: [hit, miss, error]
        queried_at:
          type: string
          format: date-time
        data:
          nullable: true
          description: >
            Passthrough response from Michigan Flora. Contains search_records (array from
            flora_search_sp — may include subspecies and varieties), spec_text (taxonomic
            details and description), synonyms (raw response — either {synonyms:[...]} or
            {message:'No synonyms found'}), and pimage_info (primary image metadata).
            pimage_info is additively enriched by FERNS with image_url and thumbnail_url
            (constructed absolute URLs). All other source fields are returned unchanged.
            The st field uses the literal string 'NULL' for unknown/absent status.
            The c field is always a string; '*' means adventive (non-native).
            Null when found is false.
          type: object
          additionalProperties: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MifloraCountiesResponse:
      type: object
      description: >
        FERNS envelope for Michigan Flora county occurrence data.
        data is the raw passthrough response from the Michigan Flora county endpoint.
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: Michigan Flora species page URL for this species.
        found:
          type: boolean
          description: Whether county records were found for this species
        cache_status:
          type: string
          enum: [hit, miss, error]
        queried_at:
          type: string
          format: date-time
        data:
          nullable: true
          description: >
            Raw passthrough response from the Michigan Flora county API endpoint.
            Array of county records for all 83 Michigan counties. Each record includes
            county name, FIPS code, and occurrence status. All source fields unchanged.
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MifloraImageRecord:
      type: object
      description: >
        A single image record from the Michigan Flora allimage_info endpoint,
        enriched with constructed absolute image_url and thumbnail_url fields.
      required:
        - image_id
        - image_url
        - thumbnail_url
      properties:
        image_id:
          description: Michigan Flora internal image ID
          oneOf:
            - type: integer
            - type: string
        image_name:
          type: string
          nullable: true
          description: Short descriptive name for the image (e.g. "flowers", "fruit")
        caption:
          type: string
          nullable: true
          description: Caption text from Michigan Flora for this image
        photographer:
          type: string
          nullable: true
          description: Photographer credit for this image
        image_url:
          type: string
          description: >
            Absolute URL to the full-size image on Michigan Flora's server.
            Constructed as: https://michiganflora.net/static/species_images/_pid_{plant_id}/{image_id}.jpg
        thumbnail_url:
          type: string
          description: >
            Absolute URL to the thumbnail image on Michigan Flora's server.
            Constructed as: https://michiganflora.net/static/species_images/_pid_{plant_id}/thumb_{image_id}.jpg

    MifloraImagesResponse:
      type: object
      description: >
        FERNS envelope for Michigan Flora image gallery.
        data is the array of image records from allimage_info, each enriched with
        constructed absolute image_url and thumbnail_url. Cached permanently (no TTL).
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: Michigan Flora species page URL. Null when species not found.
        found:
          type: boolean
          description: Whether images were found for this species in Michigan Flora
        cache_status:
          type: string
          enum: [hit, miss, error]
        queried_at:
          type: string
          format: date-time
        data:
          nullable: true
          description: >
            Array of image records from Michigan Flora allimage_info, each enriched with
            image_url and thumbnail_url. Null when found is false.
          type: array
          items:
            $ref: "#/components/schemas/MifloraImageRecord"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MifloraAttribution:
      type: object
      required:
        - source_name
        - website
        - license
        - citation
        - api_base_url
      properties:
        source_name:
          type: string
        website:
          type: string
        license:
          type: string
        citation:
          type: string
        api_base_url:
          type: string

    MifloraMetadataResponse:
      type: object
      required:
        - service_id
        - service_name
        - permission_granted
        - permission_status
        - attribution
        - queried_at
        - provenance
      properties:
        service_id:
          type: string
        service_name:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        attribution:
          $ref: "#/components/schemas/MifloraAttribution"
        registry_entry:
          type: object
          description: Full registry entry for the Michigan Flora service
          properties:
            source_id:
              type: string
            name:
              type: string
            knowledge_type:
              type: string
            status:
              type: string
            description:
              type: string
            input_summary:
              type: string
            output_summary:
              type: string
            dependencies:
              type: array
              items:
                type: string
            update_frequency:
              type: string
            known_limitations:
              type: string
            metadata_url:
              type: string
            explorer_url:
              type: string
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    CoefficientEntry:
      type: object
      required:
        - value
        - short_label
        - ecological_meaning
        - scientific_description
        - is_native
      properties:
        value:
          type: string
          description: "C-value: '0'–'10' for native species, or '*' for non-native/adventive"
        short_label:
          type: string
          description: Short human-readable label for this C-value level
        ecological_meaning:
          type: string
          description: Plain-language ecological meaning for this C-value
        scientific_description:
          type: string
          description: Scientific description of this C-value level
        is_native:
          type: boolean
          description: "false for '*' (non-native); true for 0–10"

    CoefficientResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          enum: [miss, hit, error]
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
          nullable: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/CoefficientEntry"

    CoefficientAllResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - provenance
        - data
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          enum: [miss, hit, error]
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
          nullable: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: array
          items:
            $ref: "#/components/schemas/CoefficientEntry"

    WetlandIndicatorEntry:
      type: object
      required:
        - code
        - w_value
        - full_name
        - occurrence_range
        - ecological_meaning
        - scientific_description
      properties:
        code:
          type: string
          description: "WIS code: OBL, FACW, FAC, FACU, or UPL"
          enum: [OBL, FACW, FAC, FACU, UPL]
        w_value:
          type: integer
          description: "Coefficient of Wetness (W): -5, -3, 0, 3, or 5"
        full_name:
          type: string
          description: Full name of the WIS category
        occurrence_range:
          type: string
          description: Wetland occurrence frequency range (e.g., '>99% in wetlands')
        ecological_meaning:
          type: string
          description: Plain-language ecological meaning for this WIS code
        scientific_description:
          type: string
          description: Scientific description including W-value and authority

    WetlandIndicatorResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          enum: [miss, hit, error]
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
          nullable: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/WetlandIndicatorEntry"

    WetlandIndicatorAllResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - provenance
        - data
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          enum: [miss, hit, error]
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
          nullable: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: array
          items:
            $ref: "#/components/schemas/WetlandIndicatorEntry"

    WucolsEntry:
      type: object
      required:
        - code
        - full_name
        - eto_range
        - eto_percentage_high
        - irrigation_description
        - scientific_description
      properties:
        code:
          type: string
          description: "WUCOLS code: VL, L, M, or H"
          enum: [VL, L, M, H]
        full_name:
          type: string
          description: Full name of the WUCOLS category
        eto_range:
          type: string
          description: "ETo percentage range (e.g., '10–30% of ETo')"
        eto_percentage_low:
          type: integer
          nullable: true
          description: "Lower bound of ETo percentage (null for VL which is <10%)"
        eto_percentage_high:
          type: integer
          description: Upper bound of ETo percentage
        irrigation_description:
          type: string
          description: Plain-language description of irrigation need
        scientific_description:
          type: string
          description: Scientific description including ETo methodology and authority

    WucolsResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          enum: [miss, hit, error]
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
          nullable: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/WucolsEntry"

    WucolsAllResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - provenance
        - data
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          enum: [miss, hit, error]
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
          nullable: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: array
          items:
            $ref: "#/components/schemas/WucolsEntry"

    VocabularyMetadataResponse:
      type: object
      required:
        - service_id
        - service_name
        - permission_granted
        - permission_status
        - queried_at
        - provenance
      properties:
        service_id:
          type: string
        service_name:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        registry_entry:
          type: object
          description: Full registry entry for this vocabulary source
          properties:
            source_id:
              type: string
            name:
              type: string
            knowledge_type:
              type: string
            status:
              type: string
            description:
              type: string
            input_summary:
              type: string
            output_summary:
              type: string
            dependencies:
              type: array
              items:
                type: string
            update_frequency:
              type: string
            known_limitations:
              type: string
            metadata_url:
              type: string
            explorer_url:
              type: string
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    S2CSpecies:
      type: object
      required:
        - botanical_name
      properties:
        botanical_name:
          type: string
          description: Botanical (scientific) name of the species as used in the S2C program
        neat_and_tidy:
          type: boolean
          description: >
            True if this species is designated for 'Neat & Tidy' formal or managed garden settings.
            Only tracked for 2024 onward.
        sweet_and_simple:
          type: boolean
          description: >
            True if this species is designated as 'Sweet & Simple' for beginner growers.
            Only tracked from 2026 onward.

    S2CYearSummary:
      type: object
      required:
        - year
        - species_count
        - source_note
      properties:
        year:
          type: integer
          description: Program year (January–March workshops)
        species_count:
          type: integer
          description: Number of species offered this program year
        source_note:
          type: string
          description: Description of the data source and any caveats for this year

    S2CYearsData:
      type: object
      required:
        - available_years
        - years
      properties:
        available_years:
          type: array
          items:
            type: integer
          description: List of available program years
        years:
          type: array
          items:
            $ref: "#/components/schemas/S2CYearSummary"

    S2CYearsResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - source_url
        - provenance
        - data
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          nullable: true
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          $ref: "#/components/schemas/S2CYearsData"

    S2CSpeciesListData:
      type: object
      required:
        - year
        - species_count
        - source_note
        - species
      properties:
        year:
          type: integer
        species_count:
          type: integer
        source_note:
          type: string
        species:
          type: array
          items:
            $ref: "#/components/schemas/S2CSpecies"

    S2CSpeciesResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - source_url
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          nullable: true
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/S2CSpeciesListData"
            - type: "null"

    UniversalFqaDatabaseDetail:
      type: object
      required:
        - id
        - region
        - year
        - citation
        - total_species
        - native_species
        - non_native_species
        - species
      properties:
        id:
          type: integer
          description: Universal FQA database ID
        region:
          type: string
          description: Region name as provided by universalfqa.org
        year:
          type: string
          description: Publication year string as provided by universalfqa.org
        citation:
          type: string
          description: Full citation string as provided by universalfqa.org
        total_species:
          type: integer
          description: Total number of species in this database
        native_species:
          type: integer
          description: Number of native species
        non_native_species:
          type: integer
          description: Number of non-native species
        total_mean_c:
          type: number
          nullable: true
          description: Mean C-value across all species (native and non-native)
        native_mean_c:
          type: number
          nullable: true
          description: Mean C-value across native species only
        species:
          type: array
          description: All species records in this database
          items:
            $ref: "#/components/schemas/UniversalFqaSpeciesRecord"

    UniversalFqaDatabaseDetailData:
      type: object
      required:
        - id
        - region
        - year
        - citation
        - total_species
        - native_species
        - non_native_species
        - species
      allOf:
        - $ref: "#/components/schemas/UniversalFqaDatabaseDetail"

    UniversalFqaDatabaseDetailResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - source_url
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          nullable: true
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/UniversalFqaDatabaseDetail"
            - type: "null"

    UniversalFqaDatabaseEntry:
      type: object
      required:
        - id
        - region
        - year
        - citation
      properties:
        id:
          type: integer
          description: Universal FQA database ID
        region:
          type: string
          description: Region name as provided by universalfqa.org
        year:
          type: string
          description: Publication year string as provided by universalfqa.org
        citation:
          type: string
          description: >
            Full citation string as provided by universalfqa.org.
            Contains institutional author, year, database name, and sometimes methodology notes.
            Together with region, provides all information needed to select a database.

    UniversalFqaSpeciesRecord:
      type: object
      required:
        - scientific_name
        - family
        - acronym
        - native
        - c
        - w
        - physiognomy
        - duration
        - common_name
      properties:
        scientific_name:
          type: string
          description: Scientific name of the species as listed in this database
        family:
          type: string
          description: Plant family
        acronym:
          type: string
          description: Abbreviated acronym used in this database
        native:
          type: string
          description: >
            Nativity string as provided by the source database.
            Typically "native" or "non-native" but exact values depend on the database.
        c:
          nullable: true
          description: >
            Coefficient of Conservatism (C-value) for this species in this database.
            Integer 0–10 for native species; null for non-native or unassigned species.
            Some databases use string representations. Always check the source database citation
            for the methodology used to assign C-values.
        w:
          nullable: true
          description: >
            Coefficient of Wetness (W-value). Numeric, -5 (obligate wetland) to +5 (obligate upland).
            Null if not assigned.
        physiognomy:
          type: string
          description: Plant physiognomic type (e.g. Forb, Shrub, Tree, Grass, Sedge, Rush, Fern, Bryophyte, Vine)
        duration:
          type: string
          description: Life duration (e.g. Annual, Perennial, Biennial)
        common_name:
          type: string
          description: Common name in the source database

    UniversalFqaDatabasesData:
      type: object
      required:
        - databases
      properties:
        databases:
          type: array
          items:
            $ref: "#/components/schemas/UniversalFqaDatabaseEntry"

    UniversalFqaDatabasesResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - source_url
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          nullable: true
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/UniversalFqaDatabasesData"
            - type: "null"

    UniversalFqaSpeciesData:
      type: object
      required:
        - database_id
        - queried_name
        - found
        - species
      properties:
        database_id:
          type: integer
        queried_name:
          type: string
        found:
          type: boolean
        species:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/UniversalFqaSpeciesRecord"
            - type: "null"

    UniversalFqaSpeciesResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - source_url
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          nullable: true
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/UniversalFqaSpeciesData"
            - type: "null"

    UniversalFqaAssessmentSummary:
      type: object
      required:
        - id
        - name
        - date
        - site
        - practitioner
      properties:
        id:
          type: integer
          description: Assessment ID
        name:
          type: string
          description: Assessment name as entered by the practitioner
        date:
          type: string
          description: Date of the assessment (string as provided by source)
        site:
          type: string
          description: Site name as entered by the practitioner
        practitioner:
          type: string
          description: Name of the practitioner who conducted the assessment

    UniversalFqaAssessmentsData:
      type: object
      required:
        - database_id
        - assessments
      properties:
        database_id:
          type: integer
        assessments:
          type: array
          items:
            $ref: "#/components/schemas/UniversalFqaAssessmentSummary"

    UniversalFqaAssessmentsResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - source_url
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          nullable: true
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/UniversalFqaAssessmentsData"
            - type: "null"

    UniversalFqaDbInfo:
      type: object
      properties:
        region:
          type: string
          nullable: true
        year:
          type: string
          nullable: true
        citation:
          type: string
          nullable: true

    UniversalFqaMetrics:
      type: object
      description: >
        Computed Floristic Quality Assessment metrics for this site assessment.
        All numeric values. Null means the source did not provide a value for this assessment.
      properties:
        total_mean_c:
          type: number
          nullable: true
        native_mean_c:
          type: number
          nullable: true
        total_fqi:
          type: number
          nullable: true
        native_fqi:
          type: number
          nullable: true
        adjusted_fqi:
          type: number
          nullable: true
        pct_c0:
          type: number
          nullable: true
        pct_c1_3:
          type: number
          nullable: true
        pct_c4_6:
          type: number
          nullable: true
        pct_c7_10:
          type: number
          nullable: true
        native_tree_mean_c:
          type: number
          nullable: true
        native_shrub_mean_c:
          type: number
          nullable: true
        native_herbaceous_mean_c:
          type: number
          nullable: true
        total_species:
          type: integer
          nullable: true
        native_species:
          type: integer
          nullable: true
        native_species_pct:
          type: number
          nullable: true
        non_native_species:
          type: integer
          nullable: true
        non_native_species_pct:
          type: number
          nullable: true
        mean_wetness:
          type: number
          nullable: true
        native_mean_wetness:
          type: number
          nullable: true
        tree_count:
          type: integer
          nullable: true
        tree_pct:
          type: number
          nullable: true
        shrub_count:
          type: integer
          nullable: true
        shrub_pct:
          type: number
          nullable: true
        vine_count:
          type: integer
          nullable: true
        vine_pct:
          type: number
          nullable: true
        forb_count:
          type: integer
          nullable: true
        forb_pct:
          type: number
          nullable: true
        grass_count:
          type: integer
          nullable: true
        grass_pct:
          type: number
          nullable: true
        sedge_count:
          type: integer
          nullable: true
        sedge_pct:
          type: number
          nullable: true
        rush_count:
          type: integer
          nullable: true
        rush_pct:
          type: number
          nullable: true
        fern_count:
          type: integer
          nullable: true
        fern_pct:
          type: number
          nullable: true
        bryophyte_count:
          type: integer
          nullable: true
        bryophyte_pct:
          type: number
          nullable: true
        annual_count:
          type: integer
          nullable: true
        annual_pct:
          type: number
          nullable: true
        perennial_count:
          type: integer
          nullable: true
        perennial_pct:
          type: number
          nullable: true
        biennial_count:
          type: integer
          nullable: true
        biennial_pct:
          type: number
          nullable: true
        native_annual_count:
          type: integer
          nullable: true
        native_annual_pct:
          type: number
          nullable: true
        native_perennial_count:
          type: integer
          nullable: true
        native_perennial_pct:
          type: number
          nullable: true
        native_biennial_count:
          type: integer
          nullable: true
        native_biennial_pct:
          type: number
          nullable: true

    UniversalFqaAssessmentData:
      type: object
      required:
        - id
        - metrics
        - species
      properties:
        id:
          type: integer
        site_name:
          type: string
          nullable: true
        date:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        county:
          type: string
          nullable: true
        state:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        fqa_db:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/UniversalFqaDbInfo"
            - type: "null"
        practitioner:
          type: string
          nullable: true
        latitude:
          type: string
          nullable: true
        longitude:
          type: string
          nullable: true
        weather_notes:
          type: string
          nullable: true
        duration_notes:
          type: string
          nullable: true
        community_type:
          type: string
          nullable: true
        other_notes:
          type: string
          nullable: true
        visibility:
          type: string
          nullable: true
        metrics:
          $ref: "#/components/schemas/UniversalFqaMetrics"
        species:
          type: array
          items:
            $ref: "#/components/schemas/UniversalFqaSpeciesRecord"

    UniversalFqaAssessmentResponse:
      type: object
      required:
        - found
        - cache_status
        - queried_at
        - source_url
        - provenance
      properties:
        found:
          type: boolean
        cache_status:
          type: string
          nullable: true
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - $ref: "#/components/schemas/UniversalFqaAssessmentData"
            - type: "null"

    UniversalFqaMetadataResponse:
      type: object
      required:
        - source_id
        - name
        - permission_granted
        - permission_status
        - attribution
        - registry_entry
        - provenance
      properties:
        source_id:
          type: string
        name:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        attribution:
          type: object
          additionalProperties: true
        registry_entry:
          type: object
          additionalProperties: true
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    LcscgGuide:
      type: object
      required:
        - guide_id
        - title
        - subtitle
        - season
        - habitat_type
        - authors
        - license
        - attribution_text
        - harvest_notes
        - version
        - field_museum_url
        - cloudinary_folder
        - status
        - imported_at
      properties:
        guide_id:
          type: integer
          description: Field Museum Guide ID (1271–1282)
        title:
          type: string
        subtitle:
          type: string
        season:
          type: string
          enum: [spring, summer, fall, all]
        habitat_type:
          type: string
          enum: [woodland, wetland, prairie, grasses_and_kin, asters_and_goldenrods, woody_plants]
        authors:
          type: string
        license:
          type: string
        attribution_text:
          type: string
        harvest_notes:
          type: string
        version:
          type: string
        field_museum_url:
          type: string
        cloudinary_folder:
          type: string
        status:
          type: string
        imported_at:
          type: string
          format: date-time

    LcscgSeedGroupDetail:
      type: object
      required:
        - name
        - description
        - images
      properties:
        name:
          type: string
          description: Seed dispersal category name
        description:
          type: string
          description: Harvest technique description for this seed group
        images:
          type: array
          items:
            type: string
          description: Image filenames for this seed group

    LcscgSpeciesRecord:
      type: object
      required:
        - id
        - guide_id
        - species_id
        - scientific_name
        - common_name
        - family
        - photo_date
        - description
        - seed_group_names
        - seed_group_details
        - image_filenames
        - image_urls
        - page_number
        - imported_at
      properties:
        id:
          type: integer
        guide_id:
          type: integer
          description: Field Museum Guide ID (1271–1282)
        species_id:
          type: string
          description: Per-guide species identifier string
        scientific_name:
          type: string
          description: Scientific name per Flora of the Chicago Region (Wilhelm & Rericha, 2017)
        common_name:
          type: string
        family:
          type: string
        photo_date:
          type: string
          description: >
            Reference photograph date (M-D-YY format). Approximates seed collection timing;
            varies by microclimate, proximity to Lake Michigan, slope, and sun/shade.
        description:
          type: string
          description: Authors' harvest notes for this species
        seed_group_names:
          type: array
          items:
            type: string
          description: >
            Seed dispersal category names (e.g., Elaiosomes, Ballistic, Fluffy, Milkweed,
            Berries, Mama's Boys, Shakers, Beaks, Coneheads, Crumbly Coneheads,
            Shattering, Hitchhikers, Do Not Collect)
        seed_group_details:
          type: array
          items:
            $ref: "#/components/schemas/LcscgSeedGroupDetail"
        image_filenames:
          type: array
          items:
            type: string
          description: Original image filenames from the guide
        image_urls:
          type: array
          items:
            type: ["string", "null"]
          description: Cloudinary CDN URLs for guide photographs (null for unresolved filenames)
        page_number:
          type: integer
          description: Page number within the guide
        imported_at:
          type: string
          format: date-time
        guide_title:
          type: string
          description: Present in /lcscg/species search results
          nullable: true
        guide_season:
          type: string
          description: Present in /lcscg/species search results
          nullable: true
        guide_habitat_type:
          type: string
          description: Present in /lcscg/species search results
          nullable: true
        guide_cloudinary_folder:
          type: string
          description: Present in /lcscg/species search results
          nullable: true

    LcscgGuidesResponse:
      type: object
      required:
        - found
        - queried_at
        - source_url
        - provenance
        - data
      properties:
        found:
          type: boolean
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: object
          nullable: true
          properties:
            guide_count:
              type: integer
            guides:
              type: array
              items:
                $ref: "#/components/schemas/LcscgGuide"

    LcscgGuideResponse:
      type: object
      required:
        - found
        - queried_at
        - source_url
        - provenance
      properties:
        found:
          type: boolean
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          oneOf:
            - type: object
              properties:
                guide:
                  $ref: "#/components/schemas/LcscgGuide"
                species_count:
                  type: integer
                species:
                  type: array
                  items:
                    $ref: "#/components/schemas/LcscgSpeciesRecord"
            - type: "null"

    LcscgSpeciesResponse:
      type: object
      required:
        - found
        - queried_at
        - source_url
        - provenance
        - data
      properties:
        found:
          type: boolean
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: object
          nullable: true
          properties:
            queried_name:
              type: string
            result_count:
              type: integer
            records:
              type: array
              items:
                $ref: "#/components/schemas/LcscgSpeciesRecord"

    LcscgMetadataResponse:
      type: object
      required:
        - service_id
        - service_name
        - permission_granted
        - permission_status
        - guide_count
        - species_count
        - registry_entry
        - provenance
      properties:
        service_id:
          type: string
        service_name:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        guide_count:
          type: integer
        species_count:
          type: integer
        registry_entry:
          type: object
          additionalProperties: true
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    SourceRelationship:
      type: object
      required:
        - id
        - source_id_a
        - source_id_b
        - relationship_type
        - scope
        - severity
        - description
        - technical_note
        - created_at
        - updated_at
      properties:
        id:
          type: integer
        source_id_a:
          type: string
          description: One of the two source IDs (lexically smaller of the pair)
        source_id_b:
          type: string
          description: The other source ID (lexically larger of the pair)
        relationship_type:
          type: string
          enum: [overlap, conflict, complements, supersedes]
          description: Nature of the relationship between the two sources
        scope:
          type: string
          description: >
            Domain in which the relationship applies. One of: taxonomy, occurrence_counts,
            c_values, conservation_ranks, geographic_coverage, terminology, seed_harvest,
            community_classification, occurrence.
        severity:
          type: string
          enum: [blocking, cautionary, informational]
          description: >
            blocking — must account for this before combining these sources;
            cautionary — awareness required, may cause errors if ignored;
            informational — useful context, no required action.
        description:
          type: string
          description: Plain-English description of the relationship for agents and non-technical users
        technical_note:
          type: string
          description: Implementation-level detail for developers and researchers
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    SourceRelationshipsResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
        found:
          type: boolean
        data:
          type: object
          required:
            - relationships
            - relationship_count
          properties:
            relationships:
              type: array
              items:
                $ref: "#/components/schemas/SourceRelationship"
            relationship_count:
              type: integer
            filtered_by_source_id:
              type: string
              nullable: true
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MnfiMetadataResponse:
      type: object
      required:
        - service_id
        - service_name
        - found
      properties:
        service_id:
          type: string
          description: Stable identifier for the MNFI service
        service_name:
          type: string
        found:
          type: boolean
        community_count:
          type: integer
          description: Total number of natural community types loaded into FERNS
        county_element_count:
          type: integer
          description: Total number of county element occurrence records loaded
        plant_count:
          type: integer
          description: Total number of characteristic plant records loaded
        class_summary:
          type: array
          description: Community counts grouped by community class
          items:
            type: object
            properties:
              community_class:
                type: string
              count:
                type: integer
        counties_with_data:
          type: array
          description: List of Michigan counties with at least one element occurrence
          items:
            type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        attribution:
          type: string
        registry_entry:
          type: object
          additionalProperties: true

    MnfiCommunitiesResponse:
      type: object
      required:
        - found
        - queried_at
        - data
      properties:
        found:
          type: boolean
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: object
          properties:
            community_count:
              type: integer
            filters:
              type: object
              properties:
                class:
                  type: string
                  nullable: true
                group:
                  type: string
                  nullable: true
                name:
                  type: string
                  nullable: true
            communities:
              type: array
              items:
                type: object
                additionalProperties: true

    MnfiCommunityResponse:
      type: object
      required:
        - found
        - queried_at
      properties:
        found:
          type: boolean
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          description: Community record with characteristic plants, or null if not found
          nullable: true
          type: object
          properties:
            community_id:
              type: integer
            name:
              type: string
            slug:
              type: string
            community_class:
              type: string
            community_group:
              type: string
            description:
              type: string
              nullable: true
            characteristic_plants:
              type: object
              properties:
                total_entries:
                  type: integer
                plant_list_url:
                  type: string
                by_life_form:
                  type: object
                  additionalProperties:
                    type: array
                    items:
                      type: object
                      properties:
                        common_name:
                          type: string
                        scientific_names:
                          type: array
                          items:
                            type: string

    MnfiCommunityPlantsResponse:
      type: object
      required:
        - found
        - queried_at
      properties:
        found:
          type: boolean
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: object
          properties:
            community_id:
              type: integer
            community_name:
              type: string
            slug:
              type: string
            plant_list_url:
              type: string
            total_entries:
              type: integer
            plants_imported:
              type: boolean
            by_life_form:
              type: object
              description: Plants grouped by life form (tree, shrub, herb, graminoid, vine, etc.)
              additionalProperties:
                type: array
                items:
                  type: object
                  properties:
                    common_name:
                      type: string
                    scientific_names:
                      type: array
                      items:
                        type: string

    MnfiCountyElementsResponse:
      type: object
      required:
        - found
        - queried_at
        - data
      properties:
        found:
          type: boolean
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: object
          properties:
            county:
              type: string
            element_count:
              type: integer
            filters:
              type: object
              properties:
                type:
                  type: string
                  nullable: true
            elements:
              type: array
              items:
                type: object
                additionalProperties: true

    NatureserveMetadataResponse:
      type: object
      required:
        - found
        - service_id
        - service_name
      properties:
        found:
          type: boolean
        source_url:
          type: string
        service_id:
          type: string
        service_name:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        attribution:
          type: string
        cache_stats:
          type: object
          properties:
            species_cached:
              type: integer
            ecosystems_cached:
              type: integer
            ttl_days:
              type: integer
        registry_entry:
          type: object
          additionalProperties: true
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    NatureserveSpeciesResponse:
      type: object
      required:
        - found
      properties:
        source_url:
          type: string
        found:
          type: boolean
        attribution:
          type: string
        data:
          type: object
          properties:
            scientific_name:
              type: string
              nullable: true
            common_name:
              type: string
              nullable: true
            global_rank:
              type: string
              nullable: true
              description: NatureServe G-rank (e.g. G4, G3G4)
            rounded_global_rank:
              type: string
              nullable: true
            national_rank:
              type: string
              nullable: true
              description: NatureServe N-rank for the US (e.g. N3, N4)
            rounded_national_rank:
              type: string
              nullable: true
            state_code:
              type: string
              description: Two-letter US state code this rank is scoped to
            state_rank:
              type: string
              nullable: true
              description: NatureServe S-rank for the queried state (e.g. S1, S2)
            rounded_state_rank:
              type: string
              nullable: true
            iucn_category:
              type: string
              nullable: true
            iucn_description:
              type: string
              nullable: true
            federal_status:
              type: string
              nullable: true
            federal_status_description:
              type: string
              nullable: true
            state_status:
              type: string
              nullable: true
            state_status_note:
              type: string
              nullable: true
              description: >
                Clarifying note when state_status is present: the value is
                derived from the NatureServe S-rank and reflects rarity status,
                not a formal statutory state listing.
            cites_description:
              type: string
              nullable: true
            cosewic_code:
              type: string
              nullable: true
            cosewic_description:
              type: string
              nullable: true
            natureserve_url:
              type: string
              nullable: true
            element_global_id:
              type: string
              nullable: true
            cache_status:
              type: string
              enum: [hit, miss, bypassed]
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    NatureserveEcosystemsResponse:
      type: object
      required:
        - found
      properties:
        source_url:
          type: string
        found:
          type: boolean
        attribution:
          type: string
        data:
          type: object
          properties:
            ecosystems:
              type: array
              items:
                type: object
                additionalProperties: true
            result_count:
              type: integer
            total_ecosystem_results:
              type: integer
            total_results_all_types:
              type: integer
            cache_status:
              type: string
              enum: [hit, miss, bypassed]
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    BotanicalWebRefResponse:
      type: object
      description: >
        Standard response shape for botanical web reference source lookups
        (gobotany, google-images, illinois-wildflowers, minnesota-wildflowers,
        missouri-plants, prairie-moon, usda-plants, lady-bird-johnson).
      required:
        - found
        - queried_at
      properties:
        found:
          type: boolean
          description: >
            True if a direct species page URL was resolved. False for sources
            that cannot resolve a profile URL (usda-plants, lady-bird-johnson),
            which return a search_url instead.
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          type: object
          description: Present when found=true (or when a search_url is returned). Null when not found.
          properties:
            species:
              type: string
              description: The species name as queried
            url:
              type: string
              nullable: true
              description: Direct species page URL. Null when only a search URL is available.
            search_url:
              type: string
              description: >
                Search URL when a direct profile URL cannot be constructed
                (usda-plants, lady-bird-johnson). Present instead of url for these sources.
            validation_method:
              type: string
              description: "How the URL was validated: http_get | species_list_lookup | direct_construction | not_resolvable"
            note:
              type: string
              description: Explanatory note when validation_method is not_resolvable

    BotanicalWebRefMetadataResponse:
      type: object
      description: Service metadata response for botanical web reference sources.
      required:
        - service_id
        - service_name
      properties:
        service_id:
          type: string
        service_name:
          type: string
        permission_granted:
          type: boolean
        permission_status:
          type: string
        url_strategy:
          type: string
          description: "URL construction strategy: direct_construction | sitemap_scrape | species_list_scrape"
        url_pattern:
          type: string
          description: URL template pattern used for direct construction sources
        validation:
          type: string
          description: "Validation method for constructed URLs: http_get | none | not_resolvable"
        indexed_species_count:
          type: integer
          description: Number of species indexed in FERNS's local database (scrape-based sources only)
        registry_entry:
          type: object
          additionalProperties: true
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    BotanicalRefsSiteEntry:
      type: object
      properties:
        id:
          type: string
          description: Source ID (e.g. gobotany, prairie-moon)
        name:
          type: string
          description: Human-readable source name
        strategy:
          type: string
          description: "URL lookup strategy: direct_construction | species_list_scrape | sitemap_scrape"
        query_url:
          type: string
          description: FERNS API URL for querying this source directly
        metadata_url:
          type: string
          description: FERNS API URL for this source's metadata endpoint

    BotanicalRefsSitesResponse:
      type: object
      required:
        - source_id
        - queried_at
        - data
      properties:
        source_id:
          type: string
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        data:
          type: object
          properties:
            site_count:
              type: integer
            sites:
              type: array
              items:
                $ref: "#/components/schemas/BotanicalRefsSiteEntry"

    BotanicalRefsSourceResult:
      type: object
      description: Per-source result within the /botanical-refs aggregated response.
      properties:
        found:
          type: boolean
        url:
          type: string
          nullable: true
        search_url:
          type: string
          description: Search URL when a direct profile URL cannot be resolved
        validation:
          type: string
        results:
          type: array
          description: Additional result entries (e.g. multiple Illinois Wildflowers sections)
          items:
            type: object
            additionalProperties: true
        note:
          type: string
          nullable: true
        http_status:
          type: integer
          nullable: true

    BotanicalRefsResponse:
      type: object
      description: Aggregated response from the /botanical-refs endpoint — one entry per source.
      required:
        - found
        - queried_at
        - data
      properties:
        found:
          type: boolean
          description: True if at least one source returned a direct species page URL
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
        provenance:
          type: object
          properties:
            source_id:
              type: string
            fetched_at:
              type: string
              format: date-time
            method:
              type: string
            sites_queried:
              type: integer
        data:
          type: object
          properties:
            species:
              type: string
            sites_found:
              type: integer
              description: Number of sources that returned a direct species page URL
            sites_search_only:
              type: integer
              description: Number of sources that returned a search URL but not a direct profile URL
            sites_total:
              type: integer
            results:
              type: object
              description: Keyed by source ID. Each value is a BotanicalRefsSourceResult.
              additionalProperties:
                $ref: "#/components/schemas/BotanicalRefsSourceResult"
