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.
      iNaturalist is an acknowledged external source: EC holds and describes its
      interface contract but does not host the API. Every path in this tag carries
      a path-level servers override pointing at https://api.inaturalist.org/v1 —
      consumers who call iNat directly should use that server. EC's MCP tools call
      iNat via these paths and validate responses against the schemas defined here.
  - name: miflora
    description: Michigan Flora — Vascular Plants of Michigan
  - name: coefficient
    description: Coefficient of Conservatism — Floristic Quality Assessment C-Value Reference
  - name: wetland-indicator-status
    description: Wetland Indicator Status & Coefficient of Wetness — USDA NRCS NWPL Reference
  - name: wucols-water-use
    description: WUCOLS — Water Use Classifications of Landscape Species
  - name: seeds-to-community-washtenaw
    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: ann-arbor-npn
    description: Ann Arbor Native Plant Nursery — Michigan Native Plant Species Database (nativeplant.com)
  - name: wildtype-native-plants
    description: WildType Native Plants — Michigan Native Plant Cultural Guide
  - name: registry
    description: FERNS Service Registry
  - name: trust-groups
    description: FERNS Trust Layer — curated source groups with tiered quality rankings
paths:
  /healthz:
    get:
      operationId: healthCheck
      tags: [health]
      summary: Health check
      description: >
        Returns server health status wrapped in the FERNS Response Envelope.
        Smoke-test consumer for the FernsEnvelope schema component — the inner
        data payload is a HealthStatus.
      responses:
        "200":
          description: Healthy
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $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 base epithet, all lowercase, single word (e.g. tuberosa).
            Required when map_type=county_species or map_type=state_species — omitting species returns 400.
            Must not contain spaces or infraspecific rank markers (subsp., var., f., etc.);
            BONAP does not publish subspecies-level maps and the API will return 400 for such inputs.
          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). Wrapped in FernsEnvelope.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/BonapMapData"
        "400":
          description: Invalid input (missing genus, missing species, invalid map_type)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: >
            BONAP could not be reached or returned an inconclusive response.
            The map existence could not be verified. Result is not cached.
          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, licenses and license notes,
        and the full color key array. Use this to populate 'About this data' panels
        in any application displaying BONAP maps.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      responses:
        "200":
          description: Service metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/BonapMetadataData"

  /gbif/species/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/occurrence/search:
    get:
      operationId: getGbifOccurrences
      tags: [gbif]
      summary: Search GBIF occurrence records (GBIF native parameters passthrough)
      description: >
        True passthrough to GBIF occurrence/search. Accepts GBIF's native query parameters
        and forwards them verbatim to https://api.gbif.org/v1/occurrence/search. One upstream
        call; GBIF's response (including count, offset, limit, endOfRecords, results) is
        returned in data unchanged. Cached 7 days by MD5 hash of canonicalized query params.
        BREAKING CHANGE from previous version: accepts taxonKey (GBIF native) instead of
        usageKey (old FERNS alias). Geography is now expressed using GBIF native params
        (country, continent, decimalLatitude/Longitude) rather than FERNS geography modes.
      parameters:
        - name: taxonKey
          in: query
          required: false
          description: GBIF backbone taxon key (integer). The GBIF native parameter name for taxon filtering.
          schema:
            type: integer
        - name: country
          in: query
          required: false
          description: >
            ISO 3166-1 alpha-2 country code (e.g. US). Repeatable for multiple countries.
          schema:
            type: string
        - name: continent
          in: query
          required: false
          description: >
            GBIF continent value. One of AFRICA, ANTARCTICA, ASIA, EUROPE,
            NORTH_AMERICA, OCEANIA, SOUTH_AMERICA.
          schema:
            type: string
            enum: [AFRICA, ANTARCTICA, ASIA, EUROPE, NORTH_AMERICA, OCEANIA, SOUTH_AMERICA]
        - name: hasCoordinate
          in: query
          required: false
          description: Filter to records with coordinates (true/false).
          schema:
            type: boolean
        - name: hasGeospatialIssue
          in: query
          required: false
          description: Include/exclude records with geospatial issues (false recommended).
          schema:
            type: boolean
        - name: basisOfRecord
          in: query
          required: false
          description: >
            Filter by basis of record. One of PRESERVED_SPECIMEN, HUMAN_OBSERVATION,
            MACHINE_OBSERVATION, MATERIAL_CITATION, LIVING_SPECIMEN, FOSSIL_SPECIMEN, OCCURRENCE.
          schema:
            type: string
        - name: year
          in: query
          required: false
          description: Filter by year (single value or range, e.g. 2020 or 2010,2020).
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Number of results to return (GBIF default 20, max 300).
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 300
        - name: offset
          in: query
          required: false
          description: Zero-based offset for pagination.
          schema:
            type: integer
            default: 0
            minimum: 0
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from GBIF.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: GBIF occurrence search result wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifOccurrencesResponse"
        "502":
          description: GBIF API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/species/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 the verbatim GBIF species/search response (count, offset, limit,
        endOfRecords, results). Cached 7 days per normalized query string.
        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
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from GBIF.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: GBIF species search result wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifSearchResponse"
        "400":
          description: Missing query parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: GBIF API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/species/{usageKey}:
    get:
      operationId: getGbifSpecies
      tags: [gbif]
      summary: Get GBIF species record by usageKey
      description: >
        Returns the verbatim GBIF species record for the given usageKey.
        Mirrors GBIF's GET /v1/species/{usageKey}. Cached 30 days per usageKey.
        Use this to resolve an acceptedUsageKey from a SYNONYM match result.
      parameters:
        - name: usageKey
          in: path
          required: true
          description: GBIF backbone usage key (integer)
          schema:
            type: integer
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from GBIF.
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: GBIF species record wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifSpeciesResponse"
        "400":
          description: Invalid usageKey
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: GBIF API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/species/{usageKey}/synonyms:
    get:
      operationId: getGbifSpeciesSynonyms
      tags: [gbif]
      summary: Get all taxonomic synonyms for a GBIF taxon
      description: >
        Returns the verbatim GBIF synonyms response for the accepted taxon
        identified by the given usage key. Mirrors GBIF's GET /v1/species/{usageKey}/synonyms.
        Cached 30 days per (usageKey, limit, offset). Pagination is surfaced in the
        envelope pagination field (has_more, total).
      parameters:
        - name: usageKey
          in: path
          required: true
          description: GBIF backbone usage key (integer)
          schema:
            type: integer
        - name: limit
          in: query
          required: false
          description: Number of results to return (1–1000, default 100)
          schema:
            type: integer
            default: 100
            minimum: 1
            maximum: 1000
        - name: offset
          in: query
          required: false
          description: Zero-based offset for pagination (default 0)
          schema:
            type: integer
            default: 0
            minimum: 0
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from GBIF
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: GBIF synonyms response wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifSynonymsResponse"
        "400":
          description: Invalid usageKey
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: GBIF API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gbif/species/{usageKey}/vernacularNames:
    get:
      operationId: getGbifSpeciesVernacularNames
      tags: [gbif]
      summary: Get all vernacular (common) names for a GBIF taxon
      description: >
        Returns the verbatim GBIF vernacular names response for the taxon
        identified by the given usage key. Mirrors GBIF's GET /v1/species/{usageKey}/vernacularNames.
        Cached 30 days per (usageKey, limit, offset). Pagination is surfaced in the
        envelope pagination field (has_more, total).
      parameters:
        - name: usageKey
          in: path
          required: true
          description: GBIF backbone usage key (integer)
          schema:
            type: integer
        - name: limit
          in: query
          required: false
          description: Number of results to return (1–1000, default 100)
          schema:
            type: integer
            default: 100
            minimum: 1
            maximum: 1000
        - name: offset
          in: query
          required: false
          description: Zero-based offset for pagination (default 0)
          schema:
            type: integer
            default: 0
            minimum: 0
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from GBIF
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: GBIF vernacular names response wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GbifVernacularNamesResponse"
        "400":
          description: Invalid usageKey
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: GBIF API error
          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, licenses and license notes, 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"

  /places/autocomplete:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatPlacesAutocomplete
      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"

  /observations/histogram:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatObservationsHistogram
      tags: [inat]
      summary: Passthrough for iNaturalist observations/histogram
      description: >
        Live 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). No EC-side cache.
      parameters:
        - name: taxon_id
          in: query
          required: false
          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: term_id
          in: query
          required: false
          description: >
            Controlled annotation term ID to filter by (e.g. 12 for Flowers and Fruits,
            36 for Leaves). When provided, only observations annotated with this term
            are counted. Cache key incorporates this value.
          schema:
            type: integer
        - name: term_value_id
          in: query
          required: false
          description: >
            Controlled annotation value ID to filter by. Requires term_id.
            Cache key incorporates this value.
          schema:
            type: integer
        - 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"

  /observations/popular_field_values:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatObservationsPopularFieldValues
      tags: [inat]
      summary: Passthrough for iNaturalist observations/popular_field_values
      description: >
        Live 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. No EC-side cache.
      parameters:
        - name: taxon_id
          in: query
          required: false
          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"

  /observations:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatObservations
      tags: [inat]
      summary: Paged slim observation records from iNaturalist
      description: >
        Calls the iNaturalist API live (no cache) and returns a curated subset of
        observation fields — not the full raw record. Each result includes: id, uri,
        observed_on, quality_grade, taxon_name, common_name, place_guess, location
        (lat,lng), observer login, and the first photo URL + attribution. Callers
        who need the complete record should follow the uri field to iNaturalist directly.
        Supports pagination via page and per_page. The iNaturalist API enforces a hard
        ceiling of 10,000 accessible observations (page × per_page ≤ 10,000).
      parameters:
        - name: taxon_id
          in: query
          required: false
          description: iNaturalist taxon ID to filter by
          schema:
            type: integer
        - name: place_id
          in: query
          required: false
          description: Comma-separated iNaturalist place IDs (e.g. 2649 or 2649,986)
          schema:
            type: string
        - name: quality_grade
          in: query
          required: false
          description: "Filter by quality grade: research, needs_id, or casual"
          schema:
            type: string
            enum: [research, needs_id, casual]
        - name: per_page
          in: query
          required: false
          description: Number of results per page (default 30, max 200)
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 30
        - name: page
          in: query
          required: false
          description: Page number (1-indexed, default 1)
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: d1
          in: query
          required: false
          description: Start date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: d2
          in: query
          required: false
          description: End date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: lat
          in: query
          required: false
          description: Center latitude for radius-based geographic filter. Requires lng and radius.
          schema:
            type: number
        - name: lng
          in: query
          required: false
          description: Center longitude for radius-based geographic filter. Requires lat and radius.
          schema:
            type: number
        - name: radius
          in: query
          required: false
          description: Radius in km around the lat/lng center point.
          schema:
            type: number
        - name: nelat
          in: query
          required: false
          description: Northeast corner latitude for bounding box filter.
          schema:
            type: number
        - name: nelng
          in: query
          required: false
          description: Northeast corner longitude for bounding box filter.
          schema:
            type: number
        - name: swlat
          in: query
          required: false
          description: Southwest corner latitude for bounding box filter.
          schema:
            type: number
        - name: swlng
          in: query
          required: false
          description: Southwest corner longitude for bounding box filter.
          schema:
            type: number
      responses:
        "200":
          description: Paged slim observation records
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatObservationSummaryResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /observations/species_counts:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatObservationsSpeciesCounts
      tags: [inat]
      summary: Passthrough for iNaturalist observations/species_counts
      description: >
        Live passthrough for iNaturalist's GET /observations/species_counts endpoint.
        Returns species ranked by observation count. Supports filters for place,
        quality grade, iconic taxon, nativity, phenology annotations, month window,
        and geographic constraints (place_id, lat/lng radius, or bounding box).
        No cache — every call is live. Results are wrapped in the standard FERNS
        provenance envelope.
      parameters:
        - name: place_id
          in: query
          required: false
          description: Comma-separated iNaturalist place IDs (e.g. 2649 or 2649,986)
          schema:
            type: string
        - name: quality_grade
          in: query
          required: false
          description: "Filter by quality grade: research, needs_id, or casual"
          schema:
            type: string
            enum: [research, needs_id, casual]
        - name: iconic_taxon_name
          in: query
          required: false
          description: "Filter to this iconic taxon group (e.g. Plantae, Aves, Fungi, Mammalia, Reptilia, Amphibia, Actinopterygii, Mollusca, Arachnida, Insecta, Animalia)"
          schema:
            type: string
        - name: native
          in: query
          required: false
          description: Only include taxa native to the observed location
          schema:
            type: boolean
        - name: introduced
          in: query
          required: false
          description: Only include taxa introduced at the observed location
          schema:
            type: boolean
        - name: term_id
          in: query
          required: false
          description: "Controlled annotation term ID (e.g. 12 for Flowers and Fruits)"
          schema:
            type: integer
        - name: term_value_id
          in: query
          required: false
          description: Controlled annotation value ID. Requires term_id.
          schema:
            type: integer
        - name: month
          in: query
          required: false
          description: "Comma-separated month numbers to filter by (e.g. 4,5,6 for Apr–Jun)"
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Number of results (default 500, max 500)
          schema:
            type: integer
            minimum: 1
            maximum: 500
            default: 500
        - name: page
          in: query
          required: false
          description: Page number (1-indexed, default 1)
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: d1
          in: query
          required: false
          description: Start date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: d2
          in: query
          required: false
          description: End date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: lat
          in: query
          required: false
          description: Center latitude for radius-based geographic filter. Requires lng and radius.
          schema:
            type: number
        - name: lng
          in: query
          required: false
          description: Center longitude for radius-based geographic filter. Requires lat and radius.
          schema:
            type: number
        - name: radius
          in: query
          required: false
          description: Radius in km around the lat/lng center point.
          schema:
            type: number
        - name: nelat
          in: query
          required: false
          description: Northeast corner latitude for bounding box filter.
          schema:
            type: number
        - name: nelng
          in: query
          required: false
          description: Northeast corner longitude for bounding box filter.
          schema:
            type: number
        - name: swlat
          in: query
          required: false
          description: Southwest corner latitude for bounding box filter.
          schema:
            type: number
        - name: swlng
          in: query
          required: false
          description: Southwest corner longitude for bounding box filter.
          schema:
            type: number
      responses:
        "200":
          description: Raw iNaturalist species_counts response in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatSpeciesCountsResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /controlled_terms:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatControlledTerms
      tags: [inat]
      summary: All iNaturalist controlled annotation terms
      description: >
        Returns the full list of iNaturalist controlled annotation terms and their
        possible values (e.g. term_id=12 "Flowers and Fruits" with values
        "Flowering", "Fruiting", etc). The controlled terms list is essentially
        static (iNat changes it extremely rarely). Cached permanently in the DB;
        use refresh=true to force a re-fetch. Results are wrapped in the standard
        FERNS provenance envelope.
      parameters:
        - name: refresh
          in: query
          required: false
          description: Bypass cache and re-fetch from iNaturalist
          schema:
            type: boolean
      responses:
        "200":
          description: Raw iNaturalist controlled_terms response in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatControlledTermsResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /controlled_terms/for_taxon:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatControlledTermsForTaxon
      tags: [inat]
      summary: iNaturalist controlled terms applicable to a specific taxon
      description: >
        Returns the controlled annotation terms that are applicable to a specific
        taxon (e.g. Flowers and Fruits applies to plants but not birds).
        No EC-side cache. Results are wrapped in the standard FERNS provenance envelope.
      parameters:
        - name: taxon_id
          in: query
          required: true
          description: iNaturalist taxon ID
          schema:
            type: integer
        - name: refresh
          in: query
          required: false
          description: Bypass cache and re-fetch from iNaturalist
          schema:
            type: boolean
      responses:
        "200":
          description: Raw iNaturalist controlled_terms/for_taxon response in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatControlledTermsForTaxonResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /taxa/autocomplete:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatTaxaAutocomplete
      tags: [inat]
      summary: Fast partial-name taxon search with photo thumbnail
      description: >
        Live passthrough for iNaturalist's /taxa/autocomplete endpoint. Returns
        matching taxa with photos, observation counts, and common names. Intended
        for type-ahead / search-as-you-type UIs. No cache — every call is live.
        Results are wrapped in the standard FERNS provenance envelope.
      parameters:
        - name: q
          in: query
          required: true
          description: Partial or full scientific or common name to search for
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Results per page (default 10, max 10 for autocomplete)
          schema:
            type: integer
            minimum: 1
            maximum: 10
        - name: is_active
          in: query
          required: false
          description: Filter to active taxa only (default true)
          schema:
            type: boolean
        - name: rank
          in: query
          required: false
          description: "Taxonomic rank filter (e.g. species, genus, family)"
          schema:
            type: string
        - name: locale
          in: query
          required: false
          description: "Locale code for common names (e.g. en, es, de)"
          schema:
            type: string
        - name: all_names
          in: query
          required: false
          description: Include all name variants in search (not just preferred)
          schema:
            type: boolean
        - name: preferred_place_id
          in: query
          required: false
          description: iNaturalist place ID to prioritize common names for that place
          schema:
            type: integer
      responses:
        "200":
          description: Raw iNaturalist taxa/autocomplete response in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatTaxaAutocompleteResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /taxa/{id}:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatTaxaById
      tags: [inat]
      summary: Full iNaturalist taxon record by numeric ID
      description: >
        Returns the full iNaturalist taxon record for a known taxon ID, including
        Wikipedia summary, listed_taxa (nativity per place — up to 41 entries for
        common species), children, conservation_status, and default_photo.
        No EC-side cache. Results are wrapped in the standard FERNS provenance envelope.
      parameters:
        - name: id
          in: path
          required: true
          description: iNaturalist taxon ID (integer)
          schema:
            type: integer
        - name: refresh
          in: query
          required: false
          description: Bypass cache and re-fetch from iNaturalist
          schema:
            type: boolean
      responses:
        "200":
          description: Full iNaturalist taxon record in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatTaxonByIdResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /places/{id}:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatPlacesById
      tags: [inat]
      summary: iNaturalist place record by numeric ID
      description: >
        Returns the full iNaturalist place record for a known place ID, including
        centroid, bounding box, admin hierarchy, display name, and place type.
        No EC-side cache. Results are wrapped in the standard FERNS provenance envelope.
      parameters:
        - name: id
          in: path
          required: true
          description: iNaturalist place ID (integer)
          schema:
            type: integer
        - name: admin_level
          in: query
          required: false
          description: "Admin level filter (e.g. 0=country, 1=state, 2=county)"
          schema:
            type: integer
        - name: refresh
          in: query
          required: false
          description: Bypass cache and re-fetch from iNaturalist
          schema:
            type: boolean
      responses:
        "200":
          description: Full iNaturalist place record in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatPlaceByIdResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /places/nearby:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatPlacesNearby
      tags: [inat]
      summary: iNaturalist places within a bounding box
      description: >
        Live passthrough for iNaturalist's /places/nearby endpoint. Returns
        standard (admin) and community places within the given bounding box.
        No cache — bounding box inputs produce a combinatorially large key space.
        Results are wrapped in the standard FERNS provenance envelope.
      parameters:
        - name: nelat
          in: query
          required: true
          description: Northeast corner latitude of the bounding box
          schema:
            type: number
        - name: nelng
          in: query
          required: true
          description: Northeast corner longitude of the bounding box
          schema:
            type: number
        - name: swlat
          in: query
          required: true
          description: Southwest corner latitude of the bounding box
          schema:
            type: number
        - name: swlng
          in: query
          required: true
          description: Southwest corner longitude of the bounding box
          schema:
            type: number
        - name: name
          in: query
          required: false
          description: Optional name filter to narrow results
          schema:
            type: string
        - name: per_page
          in: query
          required: false
          description: Results per page
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Raw iNaturalist places/nearby response in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatPlacesNearbyResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /observations/{id}/taxon_summary:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatObservationsTaxonSummary
      tags: [inat]
      summary: Wikipedia summary + nativity + conservation status for a taxon at an observation location
      description: >
        Calls GET /observations/{id}/taxon_summary on the iNaturalist API. Returns a
        Wikipedia summary (500-600 chars), listed_taxon (establishment_means for the
        observation's place — native/introduced/endemic), and conservation_status.
        The observation_id is required; it determines which location context is used
        for nativity data. Live call, no cache.
      parameters:
        - name: id
          in: path
          required: true
          description: iNaturalist observation ID (integer)
          schema:
            type: integer
      responses:
        "200":
          description: Taxon summary in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatTaxonSummaryResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /identifications/similar_species:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatIdentificationsSimilarSpecies
      tags: [inat]
      summary: Species commonly confused with a given taxon
      description: >
        Live passthrough for iNaturalist GET /identifications/similar_species.
        Returns taxa that are frequently confused with the given taxon_id, ranked
        by co-confusion count. Optionally filtered by place and geography. No cache.
      parameters:
        - name: taxon_id
          in: query
          required: true
          description: iNaturalist taxon ID to find similar species for
          schema:
            type: integer
        - name: place_id
          in: query
          required: false
          description: Comma-separated iNaturalist place IDs (e.g. 2649 or 2649,986)
          schema:
            type: string
        - name: quality_grade
          in: query
          required: false
          schema:
            type: string
            enum: [research, needs_id, casual]
        - name: lat
          in: query
          required: false
          schema:
            type: number
        - name: lng
          in: query
          required: false
          schema:
            type: number
        - name: radius
          in: query
          required: false
          schema:
            type: number
        - name: nelat
          in: query
          required: false
          schema:
            type: number
        - name: nelng
          in: query
          required: false
          schema:
            type: number
        - name: swlat
          in: query
          required: false
          schema:
            type: number
        - name: swlng
          in: query
          required: false
          schema:
            type: number
      responses:
        "200":
          description: Similar species list in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatSimilarSpeciesResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /identifications/species_counts:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatIdentificationsSpeciesCounts
      tags: [inat]
      summary: Species ranked by identification activity
      description: >
        Live passthrough for iNaturalist GET /identifications/species_counts. Returns
        species ranked by identification engagement (distinct from raw observation
        count). Supports taxon_of=identification|community to select which taxon is
        counted. No cache.
      parameters:
        - name: taxon_id
          in: query
          required: false
          schema:
            type: integer
        - name: place_id
          in: query
          required: false
          description: Comma-separated iNaturalist place IDs (e.g. 2649 or 2649,986)
          schema:
            type: string
        - name: quality_grade
          in: query
          required: false
          schema:
            type: string
            enum: [research, needs_id, casual]
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
        - name: d1
          in: query
          required: false
          description: Start date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: d2
          in: query
          required: false
          description: End date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: order
          in: query
          required: false
          description: "Sort order: desc (default) or asc"
          schema:
            type: string
            enum: [asc, desc]
        - name: order_by
          in: query
          required: false
          description: "Sort field: count (default) or id"
          schema:
            type: string
        - name: taxon_of
          in: query
          required: false
          description: "Which taxon to count: identification (default) or community"
          schema:
            type: string
            enum: [identification, community]
      responses:
        "200":
          description: Identification-based species counts in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatIdentSpeciesCountsResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /identifications/recent_taxa:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatIdentificationsRecentTaxa
      tags: [inat]
      summary: Taxa recently identified in a place
      description: >
        Live passthrough for iNaturalist GET /identifications/recent_taxa. Returns
        taxa that have recently been identified in a given place. Useful for
        "what's being documented right now" queries. No cache.
      parameters:
        - name: place_id
          in: query
          required: false
          description: Comma-separated iNaturalist place IDs (e.g. 2649 or 2649,986)
          schema:
            type: string
        - name: taxon_id
          in: query
          required: false
          schema:
            type: integer
        - name: quality_grade
          in: query
          required: false
          schema:
            type: string
            enum: [research, needs_id, casual]
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
        - name: d1
          in: query
          required: false
          description: Start date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: d2
          in: query
          required: false
          description: End date filter (YYYY-MM-DD)
          schema:
            type: string
      responses:
        "200":
          description: Recently identified taxa in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatRecentTaxaResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /identifications:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatIdentifications
      tags: [inat]
      summary: Paged list of individual iNaturalist identification records
      description: >
        Live passthrough for iNaturalist GET /identifications. Returns individual
        identification records (who made an identification, of what taxon, on which
        observation). A single observation can have multiple identifications. Useful
        for understanding the community identification process. No cache. Supports
        pagination via page and per_page.
      parameters:
        - name: taxon_id
          in: query
          required: false
          schema:
            type: integer
        - name: place_id
          in: query
          required: false
          description: Comma-separated iNaturalist place IDs (e.g. 2649 or 2649,986)
          schema:
            type: string
        - name: quality_grade
          in: query
          required: false
          schema:
            type: string
            enum: [research, needs_id, casual]
        - name: per_page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
        - name: page
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
        - name: d1
          in: query
          required: false
          description: Start date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: d2
          in: query
          required: false
          description: End date filter (YYYY-MM-DD)
          schema:
            type: string
        - name: user_id
          in: query
          required: false
          description: Filter by identifier user ID
          schema:
            type: integer
        - name: user_login
          in: query
          required: false
          description: Filter by identifier user login
          schema:
            type: string
        - name: order
          in: query
          required: false
          description: "Sort order: desc (default) or asc"
          schema:
            type: string
            enum: [asc, desc]
        - name: order_by
          in: query
          required: false
          description: "Sort field: created_at (default) or id"
          schema:
            type: string
      responses:
        "200":
          description: Paged identification records in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatIdentificationsResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /identifications/{id}:
    servers:
      - url: https://api.inaturalist.org/v1
        description: iNaturalist API v1
    get:
      operationId: getInatIdentificationsById
      tags: [inat]
      summary: Single iNaturalist identification record by ID
      description: >
        Live passthrough for iNaturalist GET /identifications/{id}. Returns the full
        identification record for a known identification ID. No cache.
      parameters:
        - name: id
          in: path
          required: true
          description: iNaturalist identification ID (integer)
          schema:
            type: integer
      responses:
        "200":
          description: Single identification record in FERNS envelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InatIdentificationResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: iNaturalist API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /inat/metadata:
    get:
      operationId: getInatMetadata
      tags: [inat]
      summary: iNaturalist service metadata
      description: >
        Returns service identity, attribution, licenses and license notes, 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/locs_sp:
    get:
      operationId: getMifloraCounties
      tags: [miflora]
      summary: Get county-level occurrence records for a Michigan Flora species by plant_id
      description: >
        Returns county-level occurrence records for all 83 Michigan counties for
        the given plant_id. Mirrors the Michigan Flora locs_sp endpoint verbatim.
        data.locations is the county list. Use flora_search_sp to resolve a
        scientific name to a plant_id first.
        Cached permanently (no TTL) — Michigan Flora data does not change;
        use ?refresh=true to force a re-fetch from upstream.
      parameters:
        - name: id
          in: query
          required: true
          description: Michigan Flora plant_id (positive integer, from flora_search_sp)
          schema:
            type: integer
            minimum: 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/allimage_info:
    get:
      operationId: getMifloraImages
      tags: [miflora]
      summary: Get the full photo gallery for a Michigan Flora species by plant_id
      description: >
        Returns all image records from the Michigan Flora allimage_info endpoint for a
        given plant_id. data is the array of image records, each including image_id,
        image_name, caption, photographer, image_url, and thumbnail_url.
        Results are cached permanently (no TTL) — Michigan Flora image data does not change.
        Use ?refresh=true to force a re-fetch.
        Use flora_search_sp to resolve a scientific name to a plant_id first.
      parameters:
        - name: id
          in: query
          required: true
          description: Michigan Flora plant_id (positive integer, from flora_search_sp)
          schema:
            type: integer
            minimum: 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/flora_search_sp:
    get:
      operationId: getMifloraFloraSearch
      tags: [miflora]
      summary: Search Michigan Flora species by scientific name
      description: >
        Mirrors the Michigan Flora flora_search_sp endpoint verbatim. Returns an array of
        matching species records for the given scientific_name. Each record includes plant_id,
        scientific_name, family_name, native status (na: N=native, A=adventive), C-value,
        wetland indicator code, physiognomy, and common names. data is the full records array —
        not a wrapped object. The plant_id from the matching record is required for locs_sp,
        allimage_info, spec_text, synonyms, and pimage_info endpoints.
        Cached permanently (no TTL) — use ?refresh=true to force a re-fetch.
      parameters:
        - name: scientific_name
          in: query
          required: true
          description: Scientific name to search (e.g. Quercus rubra)
          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 result (check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraFloraSearchResponse"
        "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/spec_text:
    get:
      operationId: getMifloraSpecText
      tags: [miflora]
      summary: Get botanical description text for a Michigan Flora species by plant_id
      description: >
        Mirrors the Michigan Flora spec_text endpoint. Returns the botanical description
        HTML text for the given plant_id. Use flora_search_sp to resolve a scientific name
        to a plant_id first. Cached permanently (no TTL) — use ?refresh=true to re-fetch.
      parameters:
        - name: id
          in: query
          required: true
          description: Michigan Flora plant_id (positive integer, from flora_search_sp)
          schema:
            type: integer
            minimum: 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: Botanical description text
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraSpecTextResponse"
        "400":
          description: Missing or invalid id parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Michigan Flora API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /miflora/synonyms:
    get:
      operationId: getMifloraSynonyms
      tags: [miflora]
      summary: Get taxonomic synonyms for a Michigan Flora species by plant_id
      description: >
        Mirrors the Michigan Flora synonyms endpoint. Returns an array of taxonomic
        synonyms for the given plant_id, or an empty array if none exist.
        Use flora_search_sp to resolve a scientific name to a plant_id first.
        Cached permanently (no TTL) — use ?refresh=true to re-fetch.
      parameters:
        - name: id
          in: query
          required: true
          description: Michigan Flora plant_id (positive integer, from flora_search_sp)
          schema:
            type: integer
            minimum: 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: Synonym list (may be empty if no synonyms exist)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraSynonymsResponse"
        "400":
          description: Missing or invalid id parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: Michigan Flora API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /miflora/pimage_info:
    get:
      operationId: getMifloraPImageInfo
      tags: [miflora]
      summary: Get the primary image for a Michigan Flora species by plant_id
      description: >
        Mirrors the Michigan Flora pimage_info endpoint. Returns the primary (featured)
        image record for the given plant_id, enriched with constructed absolute image_url
        and thumbnail_url fields. Use flora_search_sp to resolve a scientific name to a
        plant_id first. Cached permanently (no TTL) — use ?refresh=true to re-fetch.
      parameters:
        - name: id
          in: query
          required: true
          description: Michigan Flora plant_id (positive integer, from flora_search_sp)
          schema:
            type: integer
            minimum: 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: Primary image record (found may be false if no image exists)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MifloraPImageInfoResponse"
        "400":
          description: Missing or invalid id 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, licenses and license notes, 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/list:
    get:
      operationId: getCoefficientOfConservatismList
      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, licenses and license notes, 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-status:
    get:
      operationId: getWetlandIndicatorStatusByCode
      tags: [wetland-indicator-status]
      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-status/w:
    get:
      operationId: getWetlandIndicatorStatusByW
      tags: [wetland-indicator-status]
      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-status/list:
    get:
      operationId: getWetlandIndicatorStatusList
      tags: [wetland-indicator-status]
      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-status/metadata:
    get:
      operationId: getWetlandIndicatorMetadata
      tags: [wetland-indicator-status]
      summary: Wetland Indicator Status service metadata
      description: >
        Returns service identity, licenses and license notes, 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-water-use:
    get:
      operationId: getWucolsWaterUseByCode
      tags: [wucols-water-use]
      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-water-use/list:
    get:
      operationId: getWucolsWaterUseList
      tags: [wucols-water-use]
      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-water-use/metadata:
    get:
      operationId: getWucolsWaterUseMetadata
      tags: [wucols-water-use]
      summary: WUCOLS service metadata
      description: >
        Returns service identity, licenses and license notes, 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"

  /seeds-to-community-washtenaw/seed-availability:
    get:
      operationId: getSeedsToCommunityWashtenawSeedAvailability
      tags: [seeds-to-community-washtenaw]
      summary: Get seed availability 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.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: year
          in: query
          required: true
          description: Program year (2023–2026).
          schema:
            type: integer
            enum: [2023, 2024, 2025, 2026]
      responses:
        "200":
          description: Seed availability list for the given year, wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/S2CSeedAvailabilityData"
        "400":
          description: Missing or invalid year parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /seeds-to-community-washtenaw/species-information:
    get:
      operationId: getSeedsToCommunityWashtenawSpeciesInformation
      tags: [seeds-to-community-washtenaw]
      summary: Get per-species growing information for a Seeds to Community Washtenaw species
      description: >
        Returns rich per-species growing information for a given botanical name from the
        Seeds to Community Washtenaw species information dataset (~220 species).
        Lookup is case-insensitive on the botanical name.
        Data includes growth habit, germination code, stratification notes, bloom color,
        height, stature, light requirements, moisture preferences, and additional planting notes.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: species
          in: query
          required: true
          description: Botanical name of the species, e.g. Aquilegia canadensis.
          schema:
            type: string
      responses:
        "200":
          description: Per-species growing information wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/S2CSpeciesInformationData"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Species not found in the information dataset
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /seeds-to-community-washtenaw/years:
    get:
      operationId: getSeedsToCommunityWashtenawYears
      tags: [seeds-to-community-washtenaw]
      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
        /seeds-to-community-washtenaw/seed-availability?year=.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      responses:
        "200":
          description: Available program years, wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/S2CYearsData"

  /seeds-to-community-washtenaw/metadata:
    get:
      operationId: getSeedsToCommunityWashtenawMetadata
      tags: [seeds-to-community-washtenaw]
      summary: Seeds to Community Washtenaw source metadata
      description: >
        Returns the registry entry for the Seeds to Community Washtenaw source,
        wrapped in the FERNS Response Envelope. The data payload contains the
        source's descriptive fields (description, general_summary, technical_details,
        licenses, etc.). Also seeds the registry entry on first call.
      responses:
        "200":
          description: Source metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/SourceMetadataData"

  /lcscg/metadata:
    get:
      operationId: getLcscgMetadata
      tags: [lcscg]
      summary: Lake County Seed Collection Guides service metadata
      description: >
        Returns service identity, licenses and license notes, 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.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      responses:
        "200":
          description: Service metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /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.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      responses:
        "200":
          description: All available guides wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        required: [guides]
                        properties:
                          guides:
                            type: array
                            items:
                              $ref: "#/components/schemas/LcscgGuide"

  /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.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      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 wrapped in FernsEnvelope (found=false when no matching guide)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        nullable: true
                        properties:
                          guide:
                            $ref: "#/components/schemas/LcscgGuide"
                          species:
                            type: array
                            items:
                              $ref: "#/components/schemas/LcscgSpeciesRecord"
        "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.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      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 wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        required: [records]
                        properties:
                          records:
                            type: array
                            items:
                              $ref: "#/components/schemas/LcscgSpeciesRecord"
        "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"

  /v1/trust-groups:
    get:
      operationId: getTrustGroups
      tags: [trust-groups]
      summary: List all trust groups
      description: >
        Returns all trust groups with their metadata. Trust groups are curated
        collections of FERNS data sources organized into named, ordered tiers.
        Use GET /v1/trust-groups/{slug}/sources to retrieve the full tiered
        source list for a specific group.
      responses:
        "200":
          description: List of all trust groups
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrustGroupsListResponse"

  /v1/trust-groups/{slug}:
    get:
      operationId: getTrustGroup
      tags: [trust-groups]
      summary: Get a trust group with its tier list
      description: >
        Returns group metadata and its ordered list of tiers. Tiers are ordered
        by position (ascending); lower position = higher trust. To retrieve the
        full source entries assigned to each tier, use
        GET /v1/trust-groups/{slug}/sources.
      parameters:
        - name: slug
          in: path
          required: true
          description: Trust group slug (e.g. default, research-partners)
          schema:
            type: string
      responses:
        "200":
          description: Trust group detail with tier list, or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrustGroupDetailResponse"
        "404":
          description: Trust group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /v1/trust-groups/{slug}/sources:
    get:
      operationId: getTrustGroupSources
      tags: [trust-groups]
      summary: Get the tiered source list for a trust group
      description: >
        Returns all sources assigned to a trust group, organized into their
        respective tiers ordered by position (ascending). Each source entry is
        identical in shape to entries returned by GET /v1/sources. This is the
        primary endpoint for resolving which sources to query and in what
        priority order for a given trust context.
      parameters:
        - name: slug
          in: path
          required: true
          description: Trust group slug (e.g. default, research-partners)
          schema:
            type: string
      responses:
        "200":
          description: Tiered source list for the trust group, or not-found response
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TrustGroupSourcesResponse"
        "404":
          description: Trust group not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /universal-fqa/metadata:
    get:
      operationId: getUniversalFqaMetadata
      tags: [universal-fqa]
      summary: Universal FQA service metadata
      description: >
        Returns service identity, attribution, licenses and license notes, 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/get/database:
    get:
      operationId: getUniversalFqaDatabases
      tags: [universal-fqa]
      summary: List all Universal FQA regional databases
      description: >
        Returns the verbatim upstream row-indexed list of all regional FQA databases registered on universalfqa.org.
        See GET /universal-fqa/metadata (technical_details) for the row-position mapping.
      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/get/database/{id}:
    get:
      operationId: getUniversalFqaDatabase
      tags: [universal-fqa]
      summary: Get a full Universal FQA database with all species records
      description: >
        Returns the verbatim upstream row-indexed response for one FQA database by ID.
        See GET /universal-fqa/metadata (technical_details) for the row-position mapping.
      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/get/database/{id}/inventory:
    get:
      operationId: getUniversalFqaAssessments
      tags: [universal-fqa]
      summary: List public site assessments for a Universal FQA database
      description: >
        Returns the verbatim upstream row-indexed list of public site assessments for the specified FQA database.
        See GET /universal-fqa/metadata (technical_details) for the row-position mapping.
      parameters:
        - name: id
          in: path
          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/get/inventory/{id}:
    get:
      operationId: getUniversalFqaAssessment
      tags: [universal-fqa]
      summary: Get a single Universal FQA site assessment in full detail
      description: >
        Returns the verbatim upstream row-indexed response for one FQA site assessment by ID.
        See GET /universal-fqa/metadata (technical_details) for the row-position mapping.
      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, licenses, community class counts,
        county coverage, and the full registry entry for the Michigan Natural
        Features Inventory (MNFI). Use this to populate 'About this data' panels
        in any application displaying MNFI community, species, or county data.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      responses:
        "200":
          description: MNFI service metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /mnfi/communities:
    get:
      operationId: getMnfiCommunities
      tags: [mnfi]
      summary: List MNFI natural community types
      description: >
        Returns all 77 Michigan Natural Features Inventory (MNFI) natural community
        types. Optionally filter by community class, group, rank, or name substring
        (case-insensitive match). Data is served from FERNS's in-memory store —
        no upstream API call is made at query time.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      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: rank
          in: query
          required: false
          description: Filter by global or state rank (exact match, e.g. G2, S3)
          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 stubs wrapped in FernsEnvelope (found=false when no matches)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /mnfi/communities/{slug}:
    get:
      operationId: getMnfiCommunity
      tags: [mnfi]
      summary: Get a single MNFI community by slug
      description: >
        Returns the full profile for a single MNFI natural community type by its
        URL slug (e.g. prairie-fen, southern-dry-chestnut-oak-forest). Includes
        ecological description sections, characteristic plant list, rare species,
        similar communities, and links.
        Returns found=false with data=null when no community matches the slug.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: slug
          in: path
          required: true
          description: URL slug for the MNFI community (e.g. prairie-fen)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Community full record wrapped in FernsEnvelope (found=false when no community matches the slug)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /mnfi/species:
    get:
      operationId: getMnfiSpeciesList
      tags: [mnfi]
      summary: List MNFI tracked rare species
      description: >
        Returns a paginated list of MNFI tracked rare species. Optionally filter by
        name (scientific or common, substring match), kind (plant or animal),
        conservation status, global/state rank, or species category.
        Default page size is 50; maximum is 200. Returns a pagination envelope field.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: name
          in: query
          required: false
          description: Filter by scientific or common name (substring match)
          schema:
            type: string
        - name: kind
          in: query
          required: false
          description: Filter by kind — plant or animal
          schema:
            type: string
            enum: [plant, animal]
        - name: status
          in: query
          required: false
          description: Filter by federal or state status code (exact match, e.g. LE, E, T, SC)
          schema:
            type: string
        - name: rank
          in: query
          required: false
          description: Filter by global or state rank (exact match, e.g. G1, S2)
          schema:
            type: string
        - name: category
          in: query
          required: false
          description: Filter by species category (substring match, e.g. Flowering Plants)
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Page size (default 50, max 200)
          schema:
            type: integer
            minimum: 0
            maximum: 200
        - name: offset
          in: query
          required: false
          description: Page offset (default 0)
          schema:
            type: integer
            minimum: 0
      responses:
        "200":
          description: Paginated list of rare species wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
        "400":
          description: Invalid limit or offset parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /mnfi/species/{name}:
    get:
      operationId: getMnfiSpecies
      tags: [mnfi]
      summary: Get a single MNFI rare species by scientific name
      description: >
        Returns the rare species record for a single species identified by its
        scientific name (URL-encoded, case-insensitive, e.g. Cirsium+pitcheri).
        Returns found=false with data=null when no species matches.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: name
          in: path
          required: true
          description: URL-encoded scientific name (e.g. Cirsium+pitcheri or Cirsium%20pitcheri)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Rare species record wrapped in FernsEnvelope (found=false when no species matches)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /mnfi/species/{name}/communities:
    get:
      operationId: getMnfiSpeciesCommunities
      tags: [mnfi]
      summary: Get MNFI communities that list a rare species
      description: >
        Returns the natural community stubs for all MNFI communities whose rare-species
        list includes the given species (by scientific name). The join is on element_id
        (primary) or scientific name (fallback).
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: name
          in: path
          required: true
          description: URL-encoded scientific name of the rare species
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Community stubs wrapped in FernsEnvelope (found=false when no communities list this species)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /mnfi/species/{name}/counties:
    get:
      operationId: getMnfiSpeciesCounties
      tags: [mnfi]
      summary: Get Michigan counties where a rare species has been recorded
      description: >
        Returns the list of Michigan county names where the given rare species has
        been recorded in the MNFI county element data. The join is on element_id
        (primary) or scientific name (fallback).
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: name
          in: path
          required: true
          description: URL-encoded scientific name of the rare species
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: County list wrapped in FernsEnvelope (found=false when no counties recorded for this species)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /mnfi/counties:
    get:
      operationId: getMnfiCounties
      tags: [mnfi]
      summary: List all 83 Michigan counties
      description: >
        Returns the canonical authoritative list of all 83 Michigan counties. This
        list is static and does not depend on county element data coverage.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      responses:
        "200":
          description: Canonical county list wrapped in FernsEnvelope (always found=true)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /mnfi/counties/{county}:
    get:
      operationId: getMnfiCounty
      tags: [mnfi]
      summary: Get MNFI tracked rare elements for a Michigan county
      description: >
        Returns all tracked rare species and natural community occurrences for a
        given Michigan county (case-insensitive match). Optionally filter by kind
        (plant or animal), conservation status, or species category.
        Returns found=false when the county has no occurrence records.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: county
          in: path
          required: true
          description: Michigan county name (e.g. Washtenaw). Case-insensitive.
          schema:
            type: string
            minLength: 1
        - name: kind
          in: query
          required: false
          description: Filter species by kind — plant or animal
          schema:
            type: string
            enum: [plant, animal]
        - name: status
          in: query
          required: false
          description: Filter by conservation status code (exact match, e.g. LE, E, T)
          schema:
            type: string
        - name: category
          in: query
          required: false
          description: Filter by species category (substring match, e.g. Flowering Plants)
          schema:
            type: string
      responses:
        "200":
          description: County occurrence data wrapped in FernsEnvelope (found=false when county has no element records)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
        "400":
          description: Blank county path 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, licenses and license notes, 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/speciesSearch:
    get:
      operationId: getNatureserveSpeciesSearch
      tags: [natureserve]
      summary: Search NatureServe Explorer species conservation status by scientific name
      description: >
        Proxies NatureServe Explorer POST /api/data/speciesSearch; see GET /natureserve/metadata
        (technical_details) for field-level semantics.
      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 NatureServe Explorer
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Conservation status result wrapped in FernsEnvelope (found=false when no results)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NatureserveSpeciesSearchResponse"
        "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"

  /natureserve/search:
    get:
      operationId: getNatureserveSearch
      tags: [natureserve]
      summary: Search NatureServe by record type (ecosystems, species, communities)
      description: >
        Proxies NatureServe Explorer POST /api/data/search; see GET /natureserve/metadata
        (technical_details) for field-level semantics.
      parameters:
        - name: q
          in: query
          required: true
          description: Search text (e.g. oak savanna, tallgrass prairie, Platanthera leucophaea)
          schema:
            type: string
            minLength: 1
        - name: recordType
          in: query
          required: false
          description: >
            Type of records to return (default: ECOSYSTEM).
            ECOSYSTEM — ecological systems and community types;
            SPECIES — animal and plant species;
            COMMUNITY — plant communities;
            GROUP — element groups;
            ASSOCIATION — plant associations.
          schema:
            type: string
            enum: [ECOSYSTEM, SPECIES, COMMUNITY, GROUP, ASSOCIATION]
            default: ECOSYSTEM
        - name: limit
          in: query
          required: false
          description: Number of results per page (1–50, default 10)
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
        - name: page
          in: query
          required: false
          description: Zero-indexed page number (default 0)
          schema:
            type: integer
            minimum: 0
            default: 0
        - 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: Search results (found=false when no matching records returned)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NatureserveSearchResponse"
        "400":
          description: Missing or invalid q or recordType parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: NatureServe Explorer API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /natureserve/taxon/{uniqueId}:
    get:
      operationId: getNatureserveTaxon
      tags: [natureserve]
      summary: Get full NatureServe taxon record by uniqueId
      description: >
        Proxies NatureServe Explorer GET /api/data/taxon/{uniqueId}; see GET /natureserve/metadata
        (technical_details) for field-level semantics.
      parameters:
        - name: uniqueId
          in: path
          required: true
          description: Full NatureServe uniqueId (e.g. ELEMENT_GLOBAL.2.12345) — obtain from results of /natureserve/speciesSearch
          schema:
            type: string
        - 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: Full NatureServe taxon record wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NatureserveTaxonResponse"
        "404":
          description: Taxon not found for the given uniqueId
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: NatureServe Explorer API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /gobotany/url:
    get:
      operationId: getGobotanyUrl
      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"

  /gobotany/species-information:
    get:
      operationId: getGobotanySpeciesInformation
      tags: [gobotany]
      summary: Scrape and return species page text from Go Botany
      description: >
        Fetches and caches botanical prose text from the Go Botany (Native Plant Trust)
        species detail page. The URL is constructed directly from the binomial name.
        Sections returned include Facts, Habitat, and Characteristics. First call
        scrapes live; subsequent calls return the cached text. Use ?refresh=true to
        force a re-scrape.
      parameters:
        - name: species
          in: query
          required: true
          description: Binomial scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If "true", bypass cache and re-scrape the live page
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: Species page text result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SpeciesTextResponse"
        "400":
          description: Missing or malformed species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /google-images/search:
    get:
      operationId: getGoogleImagesSearch
      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.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      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, wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/GoogleImagesData"
        "400":
          description: Missing species parameter — returns a valid FernsEnvelope with found=false
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FernsEnvelope"

  /google-images/metadata:
    get:
      operationId: getGoogleImagesMetadata
      tags: [google-images]
      summary: Google Images source metadata
      description: >
        Returns the registry entry for the Google Images source, wrapped in the
        FERNS Response Envelope. The data payload contains the source's descriptive
        fields (description, general_summary, technical_details, licenses, etc.).
      responses:
        "200":
          description: Source metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/SourceMetadataData"

  /illinois-wildflowers/url:
    get:
      operationId: getIllinoisWildflowersUrl
      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:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        nullable: true
                        type: object
                        description: Present when found=true. Null when not found.
                        properties:
                          results:
                            type: array
                            description: One entry per Illinois Wildflowers section page for this species.
                            items:
                              type: object
                              required: [url, section]
                              properties:
                                url:
                                  type: string
                                  description: Direct species page URL for this section.
                                section:
                                  type: string
                                  description: Section name (e.g. prairie, woodland, wetland).
        "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"

  /illinois-wildflowers/species-information:
    get:
      operationId: getIllinoisWildflowersSpeciesInformation
      tags: [illinois-wildflowers]
      summary: Scrape and return species page text from Illinois Wildflowers
      description: >
        Fetches and caches botanical prose text from the Illinois Wildflowers
        (illinoiswildflowers.info) species detail page. The page URL is resolved
        from the imported species list. Sections extracted include Description,
        Cultivation, Range & Habitat, and Comments. First call scrapes live;
        subsequent calls return the cached text. Use ?refresh=true to force a
        re-scrape. Returns found=false when the species is not in the imported list.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If "true", bypass cache and re-scrape the live page
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: Species page text result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SpeciesTextResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /minnesota-wildflowers/url:
    get:
      operationId: getMinnesotaWildflowersUrl
      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"

  /minnesota-wildflowers/species-information:
    get:
      operationId: getMinnesotaWildflowersSpeciesInformation
      tags: [minnesota-wildflowers]
      summary: Scrape and return species page text from Minnesota Wildflowers
      description: >
        Fetches and caches botanical prose text from the Minnesota Wildflowers
        (minnesotawildflowers.info) species detail page. The page URL is resolved
        from the imported species list. Sections include quick-facts table rows
        and h4-delimited prose sections. First call scrapes live; subsequent calls
        return cached text. Use ?refresh=true to force a re-scrape. Returns
        found=false when the species is not in the imported list.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If "true", bypass cache and re-scrape the live page
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: Species page text result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SpeciesTextResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /missouri-plants/url:
    get:
      operationId: getMissouriPlantsUrl
      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"

  /missouri-plants/species-information:
    get:
      operationId: getMissouriPlantsSpeciesInformation
      tags: [missouri-plants]
      summary: Scrape and return species page text from Missouri Plants
      description: >
        Fetches and caches botanical prose text from the Missouri Plants
        (missouriplants.com) species detail page. The page URL is resolved
        from the imported species list. Sections are extracted from labeled
        norm-class paragraphs. First call scrapes live; subsequent calls
        return cached text. Use ?refresh=true to force a re-scrape. Returns
        found=false when the species is not in the imported list.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If "true", bypass cache and re-scrape the live page
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: Species page text result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SpeciesTextResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /prairie-moon/url:
    get:
      operationId: getPrairieMoonUrl
      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"

  /prairie-moon/species-information:
    get:
      operationId: getPrairieMoonSpeciesInformation
      tags: [prairie-moon]
      summary: Scrape and return species page text from Prairie Moon Nursery
      description: >
        Fetches and caches botanical prose text from the Prairie Moon Nursery
        (prairiemoon.com) species catalog page. The page URL is resolved from
        the imported sitemap-based species list. Sections include a product
        description prose block and structured growing details. First call
        scrapes live; subsequent calls return cached text. Use ?refresh=true
        to force a re-scrape. Returns found=false when the species is not in
        the imported list.
      parameters:
        - name: species
          in: query
          required: true
          description: Scientific name (e.g. Acer rubrum)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If "true", bypass cache and re-scrape the live page
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: Species page text result (found or not-found — check the found field)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SpeciesTextResponse"
        "400":
          description: Missing species parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /usda-plants/PlantSearch:
    get:
      operationId: getUsdaPlantsPlantSearch
      tags: [usda-plants]
      summary: USDA PLANTS autocomplete — resolve a scientific name to a PLANTS symbol
      description: >
        Calls the USDA PLANTS PlantSearch autocomplete API with the given searchText
        and returns the verbatim upstream array of { Text, Plant } objects.
        `searchText` is the upstream API's verbatim parameter name.
        Each Plant object includes Symbol, ScientificName, CommonName, Rank, and other
        fields. Use the Symbol from a matched result to call /usda-plants/PlantProfile.
        Results are not cached — each call hits the USDA PLANTS API directly.
      parameters:
        - name: searchText
          in: query
          required: true
          description: Scientific name autocomplete text (e.g. Asclepias tuberosa). Verbatim upstream parameter name.
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from USDA PLANTS
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: USDA PLANTS autocomplete result — verbatim upstream array wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsdaPlantsPlantSearchResponse"
        "400":
          description: Missing searchText parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: USDA PLANTS API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /usda-plants/PlantProfile:
    get:
      operationId: getUsdaPlantsProfile
      tags: [usda-plants]
      summary: Fetch the full USDA PLANTS profile for a known symbol
      description: >
        Fetches the PlantProfile for a known USDA symbol (e.g. ASTU for Asclepias
        tuberosa). Returns the verbatim upstream profile object as returned by the USDA
        PLANTS API — top-level keys include Id, Symbol, ScientificName,
        ScientificNameWithoutAuthor, CommonName, NativeStatuses, WetlandData, Synonyms,
        Ancestors, Characteristics, and others. data is the profile object directly with
        no symbol echo or profile nesting. Profiles are cached 30 days. Use ?refresh=true
        to bypass the cache. Use /usda-plants/PlantSearch?searchText= to resolve a
        scientific name to a symbol if the symbol is not already known.
      parameters:
        - name: symbol
          in: query
          required: true
          description: USDA PLANTS symbol (e.g. ASTU)
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If true, bypasses cache and fetches fresh from USDA PLANTS
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Full USDA PLANTS profile for the given symbol
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsdaPlantsProfileResponse"
        "400":
          description: Missing symbol parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: USDA PLANTS API error
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /usda-plants/plants-search-results:
    get:
      operationId: getUsdaPlantsSearch
      tags: [usda-plants]
      summary: Search the USDA PLANTS Database with a text query
      description: >
        Paginated USDA PLANTS search. FERNS exposes this as GET; the upstream is
        POST /plants-search-results with a JSON body. The EDP handles the
        GET-to-POST translation internally. Parameter names (Text, Field, pageNumber)
        match the upstream POST body field names. data is the verbatim upstream
        response object. Search results are not cached — each call hits the USDA
        PLANTS API directly.
      parameters:
        - name: Text
          in: query
          required: true
          description: Search text (e.g. Trillium, butterfly milkweed). Upstream POST body field name.
          schema:
            type: string
            minLength: 1
        - name: Field
          in: query
          required: true
          description: >
            Field to search. One of Scientific Name, Common Name, Symbol, Family.
            Upstream POST body field name.
          schema:
            type: string
            enum: [Scientific Name, Common Name, Symbol, Family]
        - name: pageNumber
          in: query
          required: false
          description: 1-based page number (default 1). Upstream POST body field name.
          schema:
            type: integer
            minimum: 1
            default: 1
      responses:
        "200":
          description: Verbatim upstream USDA PLANTS search response wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsdaPlantsSearchResponse"
        "400":
          description: Missing or invalid Text or Field parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "502":
          description: USDA PLANTS API error
          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, access method (api_fetch), attribution, permission
        status, 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/UsdaPlantsMetadataResponse"

  /lady-bird-johnson/url:
    get:
      operationId: getLadyBirdJohnsonUrl
      tags: [lady-bird-johnson]
      summary: Verify a Lady Bird Johnson Wildflower Center species profile URL from a USDA Plants symbol
      description: >
        Accepts a USDA Plants symbol (e.g. TRGI for Trillium grandiflorum) and returns a verified
        direct species profile URL at https://www.wildflower.org/plants/result.php?id_plant={SYMBOL}.
        Verification is performed via HTTP GET with redirect:manual and a browser-like User-Agent
        (required — the site returns 403 to generic agents). HTTP 200 = found; 3xx redirect = not_found;
        5xx/network = unverified (not cached). Results are cached in lbj_url_cache (90-day TTL when
        found, 30-day when not_found). Obtain USDA Plants symbols via /usda-plants.
      parameters:
        - name: usda_symbol
          in: query
          required: true
          description: USDA Plants symbol (e.g. TRGI for Trillium grandiflorum). Obtain via /usda-plants.
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Lady Bird Johnson profile URL verification result wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/LbjUrlCheckData"
        "400":
          description: Missing or invalid usda_symbol parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /lady-bird-johnson/species-information:
    get:
      operationId: getLadyBirdJohnsonSpeciesInformation
      tags: [lady-bird-johnson]
      summary: Scrape and return species page text from Lady Bird Johnson Wildflower Center
      description: >
        Fetches and caches botanical prose text from the Lady Bird Johnson Wildflower Center
        (wildflower.org/plants) species profile page identified by a USDA Plants symbol.
        Verification and scraping use a browser-like User-Agent (required — site returns 403
        to generic agents) with redirect:manual to detect not-found responses (3xx redirect).
        Sections extracted include all h3-delimited prose; "Find Seeds or Plants" and
        "Mr. Smarty Plants says" sections are excluded. First call fetches live; subsequent
        calls return the cached text. Use ?refresh=true to force a re-scrape. Returns
        found=false when the symbol is not found on the Wildflower Center site.
        Obtain USDA Plants symbols via /usda-plants.
      parameters:
        - name: usda_symbol
          in: query
          required: true
          description: USDA Plants symbol (e.g. TRGI for Trillium grandiflorum). Obtain via /usda-plants.
          schema:
            type: string
            minLength: 1
        - name: refresh
          in: query
          required: false
          description: If "true", bypass cache and re-scrape the live page
          schema:
            type: string
            enum: ["true", "false"]
      responses:
        "200":
          description: Species page text result (found or not-found — check the found field). Wrapped in FernsEnvelope.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/LbjSpeciesTextData"
        "400":
          description: Missing symbol 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 verification strategy, cache configuration, attribution, and the
        full registry entry for the Lady Bird Johnson Wildflower Center service.
      responses:
        "200":
          description: Lady Bird Johnson service metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/BotanicalWebRefMetadataResponse"

  /ann-arbor-npn/metadata:
    get:
      operationId: getAnnArborNpnMetadata
      tags: [ann-arbor-npn]
      summary: Ann Arbor Native Plant Nursery service metadata
      description: >
        Returns service identity, current species count, licenses and license notes, and the full
        registry entry for The Native Plant Nursery (nativeplant.com, Ann Arbor, MI —
        Greg Vaclavek). Includes known limitations, update frequency, and technical details.
      responses:
        "200":
          description: NPN service metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/NpnMetadataData"

  /ann-arbor-npn/species-list:
    get:
      operationId: getAnnArborNpnSpeciesList
      tags: [ann-arbor-npn]
      summary: All Ann Arbor Native Plant Nursery species (bulk)
      description: >
        Returns all ~130 species from The Native Plant Nursery database ordered by Latin
        name. Each record includes ecological attributes (light, moisture, height, flowering
        time, habitat, range within Michigan), nursery pricing, and Cloudinary image URLs
        with captions and kind (photograph / drawing). Returns an empty list if the import
        has not yet been run.
      responses:
        "200":
          description: NPN bulk species list wrapped in FernsEnvelope (found=false with empty array before import)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        required: [species]
                        properties:
                          species:
                            type: array
                            items:
                              $ref: "#/components/schemas/NpnSpeciesRecord"

  /ann-arbor-npn/species/{key}:
    get:
      operationId: getAnnArborNpnSpeciesByKey
      tags: [ann-arbor-npn]
      summary: Single NPN species by acronym, Latin name, synonym, or common name
      description: >
        Resolves any NPN name flavor to a species record via the npn_name_aliases index
        (case-insensitive). Accepted inputs: acronym (e.g. LOBSIP), Latin name
        (e.g. "Lobelia spicata"), Greg's Latin synonym, or any common name
        (semicolon/comma-delimited common names are each indexed separately).
        Returns HTTP 404 with found=false when the key does not appear in the alias index.
        The alias index is built during import; 404 is also returned before import has run.
      parameters:
        - name: key
          in: path
          required: true
          description: Acronym, Latin name, synonym, or common name (any capitalisation)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: NPN species record wrapped in FernsEnvelope (found=true)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/NpnSpeciesRecord"
        "400":
          description: Empty key parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Name not found in alias index wrapped in FernsEnvelope (found=false)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /ann-arbor-npn/name-groups:
    get:
      operationId: getAnnArborNpnNameGroups
      tags: [ann-arbor-npn]
      summary: NPN name groups — all species with every accepted lookup key
      description: >
        Returns all 130 NPN species as name groups served from the in-memory IDP.
        Each entry contains acronym, latin_name, latin_synonym_greg (nullable),
        common_names (string array parsed from the semicolon/comma-delimited common_name field),
        and all_accepted_keys (flat array of every lowercase alias that resolves to this species).
        Designed for cross-source name reconciliation against GBIF, USDA PLANTS, or iNaturalist.
        Replaces the former /ann-arbor-npn/alias-index endpoint.
      responses:
        "200":
          description: NPN alias index wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        required: [name_groups]
                        properties:
                          name_groups:
                            type: array
                            items:
                              $ref: "#/components/schemas/NpnNameGroup"

  /ann-arbor-npn/species/{key}/source-url:
    get:
      operationId: getAnnArborNpnSpeciesSourceUrl
      tags: [ann-arbor-npn]
      summary: Per-species nativeplant.com URL for a given key
      description: >
        Returns the nativeplant.com source URL for the species identified by the given key.
        Accepts the same name flavors as /ann-arbor-npn/species/{key}: acronym, Latin name,
        Latin synonym, or any common name. Returns 404 with found=false when not found.
        Data is served from the in-memory IDP snapshot.
      parameters:
        - name: key
          in: path
          required: true
          description: Acronym, Latin name, synonym, or common name (any capitalisation)
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Source URL wrapped in FernsEnvelope (found=true)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        required: [acronym, source_url]
                        properties:
                          acronym:
                            type: string
                          source_url:
                            type: string
        "400":
          description: Empty key parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Name not found in alias index wrapped in FernsEnvelope (found=false)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"

  /ann-arbor-npn/documentation:
    get:
      operationId: getAnnArborNpnDocumentation
      tags: [ann-arbor-npn]
      summary: Source documentation for the Ann Arbor Native Plant Nursery dataset
      description: >
        Returns the full source documentation for the Ann Arbor NPN dataset as Markdown in
        data.markdown. Covers: Greg Vaclavek's background, dataset contents, field descriptions,
        Michigan range vocabulary (SE/SW/NL/UP), alias index construction, Cloudinary image storage,
        and the migration from DB-backed scrape to static in-memory snapshot.
        Always returns 200 with found=true — the documentation file is static and always present.
      responses:
        "200":
          description: Documentation markdown wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        type: object
                        required: [markdown]
                        properties:
                          markdown:
                            type: string

  /wildtype-native-plants/plant-guide:
    get:
      operationId: getWildTypeNativePlantsPlantGuide
      tags: [wildtype-native-plants]
      summary: Get WildType Native Plants cultural guide entries
      description: >
        Returns all plant records from the WildType Native Plants cultural guide
        (249 Michigan native plant species), optionally filtered by category.
        Each record includes scientific name, common name, category, flower color,
        bloom time, sun and moisture preferences (boolean flags and summary strings),
        height, and an array of ecological notes (code + description pairs).
        Data is served from the in-memory IDP — no upstream network call.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: category
          in: query
          required: false
          description: >
            Filter results to a single plant category. Case-insensitive.
            Valid values: wildflowers, grasses/sedges/rushes, ferns, trees/shrubs/vines.
            Omit to return all 249 plant records across all categories.
          schema:
            type: string
            enum:
              - wildflowers
              - "grasses, sedges, rushes"
              - ferns
              - "trees, shrubs & vines"
      responses:
        "200":
          description: Plant guide records (all or filtered by category), wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/WildTypeNativePlantsPlantGuideData"
        "400":
          description: Invalid category value
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /wildtype-native-plants/plant-guide/{scientific_name}:
    get:
      operationId: getWildTypeNativePlantsPlantGuideByScientificName
      tags: [wildtype-native-plants]
      summary: Get a single WildType Native Plants record by scientific name
      description: >
        Returns the cultural guide record for a single species identified by scientific name.
        Lookup is case-insensitive exact match — no fuzzy matching or synonym resolution.
        Returns found=false with data=null when the name is not present in the guide.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      parameters:
        - name: scientific_name
          in: path
          required: true
          description: Scientific (Latin) name of the species, e.g. Asclepias tuberosa
          schema:
            type: string
            minLength: 1
      responses:
        "200":
          description: Single plant record wrapped in FernsEnvelope (found=false with data=null when not in guide)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        oneOf:
                          - $ref: "#/components/schemas/WildTypeNativePlantsPlantRecord"
                          - type: "null"
        "400":
          description: Empty scientific_name path parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /wildtype-native-plants/note-codes:
    get:
      operationId: getWildTypeNativePlantsNoteCodes
      tags: [wildtype-native-plants]
      summary: Get the full WildType Native Plants ecological note-codes legend
      description: >
        Returns all 24 ecological note codes used in the WildType cultural guide,
        each with its code abbreviation and full description. Use this to decode
        the notes array on any plant record returned by /plant-guide.
        Data is served from the in-memory IDP — no upstream network call.
        Response is wrapped in the FERNS Response Envelope (FernsEnvelope).
      responses:
        "200":
          description: Full note-codes legend wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/WildTypeNativePlantsNoteCodesData"

  /wildtype-native-plants/metadata:
    get:
      operationId: getWildTypeNativePlantsMetadata
      tags: [wildtype-native-plants]
      summary: WildType Native Plants source metadata
      description: >
        Returns the registry entry for the WildType Native Plants source,
        wrapped in the FERNS Response Envelope. The data payload contains the
        source's descriptive fields (description, general_summary, technical_details,
        licenses, etc.). Also seeds the registry entry on first call.
      responses:
        "200":
          description: Source metadata wrapped in FernsEnvelope
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/FernsEnvelope"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/SourceMetadataData"

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.
        Identity fields (source_id, fetched_at, method, upstream_url) are always present.
      required:
        - source_id
        - fetched_at
        - method
        - upstream_url
      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.

    EnvelopeMethod:
      type: string
      enum: [api_fetch, cache_hit, computed]
      description: >
        How FERNS obtained the data for this response.
        Coupled with cache_status — only specific pairs are valid:
        api_fetch+miss, cache_hit+hit, cache_hit+stale, computed+bypass, computed+hit.
        See replit.md "Refinement #7 — method and cache_status are coupled".

    EnvelopeCacheStatus:
      type: string
      enum: [hit, miss, stale, bypass]
      description: >
        Cache outcome for this response.
        Coupled with method — see EnvelopeMethod description for valid pairs.

    EnvelopeSourceType:
      type: string
      enum: [in-memory, pure-algorithm, single-source-proxy, multi-source-algorithm]
      description: >
        Classification of the FERNS source kind. Mirrors the SOURCE_KINDS enum
        in @workspace/api-envelope. Determines how the envelope is filled —
        see replit.md "How each source kind fills the envelope".

    Pagination:
      type: object
      description: >
        Pagination metadata for responses that represent one page of a larger set.
        Present (object) when the response could continue; null when the response
        is inherently whole. See replit.md "Top-level field definitions".
      required:
        - has_more
        - next
        - total
      properties:
        has_more:
          type: boolean
          description: True if more pages exist beyond this one.
        next:
          type: string
          nullable: true
          description: Opaque cursor or token for fetching the next page; null if no next page.
        total:
          type: integer
          nullable: true
          description: Total record count across all pages, if known; null when not provided by the source.

    Provenance:
      type: object
      description: >
        Per-response provenance — what FERNS did to obtain this payload.
        Holds only FERNS-produced facts about the act of fetching. Source-produced
        content lives in the envelope's data field. See replit.md
        "FERNS Response Envelope Contract v1 — Provenance field definitions".
      required:
        - source_id
        - source_url
        - method
        - cache_status
        - queried_at
        - derived_from
        - license
        - rights
      properties:
        source_id:
          type: string
          description: Stable identifier of the registered FERNS source (e.g. bonap-napa).
        source_url:
          type: string
          format: uri
          nullable: true
          description: >
            Absolute upstream URL FERNS contacted. Null for in-memory or pure-algorithm
            sources that contact no external system. On a cache hit, this is the
            original fetch URL (refinement #1) — not null.
        method:
          $ref: "#/components/schemas/EnvelopeMethod"
        cache_status:
          $ref: "#/components/schemas/EnvelopeCacheStatus"
        queried_at:
          type: string
          format: date-time
          description: When FERNS performed this lookup (UTC ISO-8601).
        derived_from:
          type: array
          nullable: true
          description: >
            List of contributing sources for multi-source-algorithm responses.
            Null for all other source kinds.
          items:
            type: object
            required:
              - source_id
              - queried_at
            properties:
              source_id:
                type: string
              queried_at:
                type: string
                format: date-time
        license:
          type: string
          description: License URI for the source data, or the literal string "unknown".
        rights:
          type: string
          description: Rights statement / attribution for the source.

    FernsEnvelope:
      type: object
      description: >
        The FERNS Response Envelope Contract v1 — every endpoint must produce
        this shape. The envelope holds only what is true of FERNS's act of
        obtaining the data; the data field holds only what the source produced.
        Authoritative contract: replit.md "FERNS Response Envelope Contract v1".
        Note: OpenAPI cannot express the full method/cache_status coupling table
        nor the source-kind-specific source_url/derived_from rules — those are
        enforced at runtime by the @workspace/api-envelope builder and by the
        forthcoming structural audit.
      required:
        - found
        - permission_granted
        - pagination
        - provenance
        - data
      properties:
        found:
          type: boolean
          description: >
            Did the source have the thing that was asked for? True = data is present.
            False = the lookup ran correctly but the source holds no record (honest absence,
            not an error).
        permission_granted:
          type: boolean
          description: Is the consumer cleared to use this data? Always present, per-endpoint.
        pagination:
          oneOf:
            - $ref: "#/components/schemas/Pagination"
            - type: "null"
          description: Pagination metadata, or null when the response is inherently whole.
        provenance:
          $ref: "#/components/schemas/Provenance"
        data:
          description: Verbatim payload from the source. Shape varies per endpoint.

    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_url
        - map_type_served
        - genus
        - species
      properties:
        map_url:
          type: string
          nullable: true
          description: >
            BONAP PNG map image URL. Null when found is false.
        map_type_served:
          type: string
          enum: [county_species, state_species]
        genus:
          type: string
          description: >
            Normalized genus used to query BONAP (first letter uppercase,
            remainder lowercase).
        species:
          type: string
          description: >
            Species epithet used to query BONAP. May differ from the requested
            species if a subspecies qualifier was stripped (BONAP does not
            publish subspecies-level maps).

    BonapMapResponse:
      type: object
      description: Standard FERNS response envelope for BONAP map lookups
      required:
        - found
        - provenance
      properties:
        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"

    BonapMetadataData:
      type: object
      description: Data payload returned in the /bonap/metadata FernsEnvelope data field.
      properties:
        service_id:
          type: string
        service_name:
          type: string
        data_vintage:
          type: string
        licenses:
          type: array
          items:
            type: string
        license_notes:
          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.
        registry_entry:
          type: object
          additionalProperties: true

    BonapMetadataResponse:
      description: >
        Legacy envelope schema kept for backwards-compat reference. Route response now uses allOf:FernsEnvelope.
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/BonapMetadataData"

    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
      additionalProperties: true
      description: >
        Verbatim GBIF species/match upstream response. matchType is NONE when no match
        was found. usageKey is absent or null when matchType is NONE.
        GBIF always returns HTTP 200 regardless of match outcome.

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

    GbifSynonymsData:
      type: object
      additionalProperties: true
      description: >
        Verbatim GBIF species/{usageKey}/synonyms upstream response.
        Includes count, offset, limit, endOfRecords, and results array.

    GbifSynonymsResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GbifSynonymsData"

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

    GbifVernacularNamesData:
      type: object
      additionalProperties: true
      description: >
        Verbatim GBIF species/{usageKey}/vernacularNames upstream response.
        Includes count, offset, limit, endOfRecords, and results array.

    GbifVernacularNamesResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GbifVernacularNamesData"

    GbifMatchResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GbifMatchData"

    GbifOccurrencesData:
      type: object
      additionalProperties: true
      description: >
        Verbatim GBIF occurrence/search upstream response.
        Includes count, offset, limit, endOfRecords, and results array.

    GbifOccurrencesResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GbifOccurrencesData"

    GbifSearchData:
      type: object
      additionalProperties: true
      description: >
        Verbatim GBIF species/search upstream response filtered to vernacular name matches.
        Includes count, offset, limit, endOfRecords, and results array.

    GbifSearchResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GbifSearchData"

    GbifSpeciesData:
      type: object
      additionalProperties: true
      description: >
        Verbatim GBIF species/{usageKey} upstream response.

    GbifSpeciesResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GbifSpeciesData"

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

    GbifMetadataData:
      type: object
      properties:
        description:
          type: string
        general_summary:
          type: string
        technical_details:
          type: string
        licenses:
          type: array
          items:
            type: string
        license_notes:
          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"
        metadata_url:
          type: string

    GbifMetadataResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/GbifMetadataData"

    InatPlaceResult:
      type: object
      required:
        - id
        - display_name
        - place_type
        - place_type_name
      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)

    InatPlaceData:
      type: object
      properties:
        results:
          type: array
          description: Up to 5 matching places. Applications select the correct one.
          items:
            $ref: "#/components/schemas/InatPlaceResult"
      additionalProperties: false

    InatPlaceResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/InatPlaceData"

    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:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatHistogramResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatFieldValuesResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatObservationSummaryRecord:
      type: object
      description: >
        Expanded field subset of a single iNaturalist observation. Follow the uri
        field for the complete record on iNaturalist. Note: nativity data (native,
        introduced, endemic status) is NOT present on observation records — it lives
        in listed_taxa on the taxon record, available via GET /inat/taxa/{id}.
      required:
        - id
        - uri
      properties:
        id:
          type: integer
        uuid:
          type: string
          nullable: true
          description: UUID of the observation
        uri:
          type: string
          description: Canonical URL to the full observation on iNaturalist
        observed_on:
          type: string
          nullable: true
          description: Date the observation was made (YYYY-MM-DD)
        quality_grade:
          type: string
          nullable: true
          description: "research | needs_id | casual"
        obscured:
          type: boolean
          nullable: true
          description: Whether the location has been obscured for this observation
        license_code:
          type: string
          nullable: true
          description: "License code for this observation (e.g. cc-by, cc-by-nc)"
        description:
          type: string
          nullable: true
          description: Observer-provided description text
        tags:
          type: array
          nullable: true
          description: Tags applied to this observation
          items:
            type: string
        taxon_name:
          type: string
          nullable: true
          description: Scientific name (shorthand from taxon.name)
        common_name:
          type: string
          nullable: true
          description: Preferred common name (shorthand from taxon.preferred_common_name)
        taxon:
          type: object
          nullable: true
          description: Taxon sub-object with key fields
          properties:
            id:
              type: integer
            rank:
              type: string
              nullable: true
            iconic_taxon_name:
              type: string
              nullable: true
            default_photo:
              type: object
              nullable: true
              additionalProperties: true
          additionalProperties: true
        place_guess:
          type: string
          nullable: true
          description: Human-readable location string provided by the observer
        location:
          type: string
          nullable: true
          description: "lat,lng decimal string"
        observer:
          type: string
          nullable: true
          description: iNaturalist login of the observer (shorthand from user.login)
        user:
          type: object
          nullable: true
          description: Observer user sub-object
          properties:
            id:
              type: integer
            name:
              type: string
              nullable: true
            login:
              type: string
              nullable: true
          additionalProperties: true
        photo_url:
          type: string
          nullable: true
          description: Medium-size URL of the first photo (shorthand)
        photo_attribution:
          type: string
          nullable: true
          description: License and attribution text for the first photo (shorthand)
        photos:
          type: array
          nullable: true
          description: Full photos array from iNaturalist
          items:
            type: object
            additionalProperties: true
        annotations:
          type: array
          nullable: true
          description: Controlled annotation values on this observation
          items:
            type: object
            additionalProperties: true
        ofvs:
          type: array
          nullable: true
          description: Observer field values (OFVs) on this observation
          items:
            type: object
            additionalProperties: true

    InatObservationSummaryData:
      type: object
      required:
        - total_results
        - page
        - per_page
        - total_pages
        - results
      properties:
        total_results:
          type: integer
          description: Total observations matching the query (iNat API limit is 10,000)
        page:
          type: integer
        per_page:
          type: integer
        total_pages:
          type: integer
        results:
          type: array
          items:
            $ref: "#/components/schemas/InatObservationSummaryRecord"

    InatObservationSummaryResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatControlledTermsResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatControlledTermsForTaxonResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatTaxaAutocompleteResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatTaxonByIdResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatPlaceByIdResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatPlacesNearbyResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatSpeciesCountsResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatTaxonSummaryResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatSimilarSpeciesResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatIdentSpeciesCountsResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatRecentTaxaResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatIdentificationsResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    InatIdentificationResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    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:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"

    SourceSummary:
      type: object
      required:
        - source_id
        - name
        - knowledge_type
        - status
        - description
        - input_summary
        - output_summary
        - dependencies
        - update_frequency
        - known_limitations
        - metadata_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
        licenses:
          type: array
          items:
            type: string
          description: >
            Whether FERNS has verified that use of this source is explicitly permitted.
            true = permission confirmed; null = not yet evaluated.
        license_notes:
          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"

    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"

    MifloraSpeciesRecord:
      type: object
      description: A single species record from the Michigan Flora flora_search_sp endpoint.
      required:
        - plant_id
        - scientific_name
        - common_name
      properties:
        plant_id:
          type: integer
          description: Michigan Flora internal plant ID
        scientific_name:
          type: string
          description: Scientific name of the species
        c:
          type: string
          nullable: true
          description: Coefficient of Conservatism (0–10, or null if not assigned)
        st:
          type: string
          nullable: true
          description: State status code (T=threatened, E=endangered, X=probably extirpated, etc.)
        w:
          type: number
          nullable: true
          description: Coefficient of Wetness (-5 to +5)
        wet:
          type: string
          nullable: true
          description: Wetland indicator status code (OBL/FACW/FAC/FACU/UPL)
        phys:
          type: string
          nullable: true
          description: Physiognomy code (e.g. Wt, Sh, Fo, Gr)
        na:
          type: string
          nullable: true
          description: Native status (N=native, A=adventive/non-native)
        family_name:
          type: string
          nullable: true
          description: Plant family name
        author:
          type: string
          nullable: true
          description: Taxonomic authority
        acronym:
          type: string
          nullable: true
          description: Short species code used in Michigan Flora
        common_name:
          type: array
          items:
            type: string
          description: Array of common names for this species

    MifloraFloraSearchResponse:
      type: object
      description: >
        FERNS envelope for Michigan Flora flora_search_sp. data is the verbatim array of
        species records from the upstream API — not a wrapped object. Cached permanently (no TTL).
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: Michigan Flora species page URL. Null when species not found.
        found:
          type: boolean
          description: Whether any species records were found for this name
        cache_status:
          type: string
          enum: [hit, miss, bypassed, error]
        queried_at:
          type: string
          format: date-time
        data:
          nullable: true
          description: >
            Array of matching species records from the Michigan Flora flora_search_sp
            endpoint. Verbatim upstream array. Null when found is false.
          type: array
          items:
            $ref: "#/components/schemas/MifloraSpeciesRecord"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MifloraSpecTextResponse:
      type: object
      description: >
        FERNS envelope for Michigan Flora spec_text. data.text is the raw HTML
        botanical description from Michigan Flora. Cached permanently (no TTL).
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: Michigan Flora species page URL.
        found:
          type: boolean
          description: Whether a description was found for this plant_id
        cache_status:
          type: string
          enum: [hit, miss, bypassed, error]
        queried_at:
          type: string
          format: date-time
        data:
          nullable: true
          type: object
          properties:
            text:
              type: string
              nullable: true
              description: Raw HTML botanical description from Michigan Flora
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MifloraSynonymRecord:
      type: object
      description: A single taxonomic synonym from the Michigan Flora synonyms endpoint.
      required:
        - synonym
      properties:
        synonym:
          type: string
          description: The synonym scientific name
        author:
          type: string
          nullable: true
          description: Taxonomic authority for the synonym

    MifloraSynonymsResponse:
      type: object
      description: >
        FERNS envelope for Michigan Flora synonyms. data is a bare array of synonym records
        (empty array if none exist) — no wrapper object, no plant_id field.
        Cached permanently (no TTL).
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: Michigan Flora species page URL.
        found:
          type: boolean
          description: Whether any synonyms were found for this plant_id
        cache_status:
          type: string
          enum: [hit, miss, bypassed, error]
        queried_at:
          type: string
          format: date-time
        data:
          nullable: true
          description: >
            Bare array of synonym records from the Michigan Flora synonyms endpoint.
            Empty array if no synonyms exist. Null when found is false.
          type: array
          items:
            $ref: "#/components/schemas/MifloraSynonymRecord"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    MifloraPImageInfoResponse:
      type: object
      description: >
        FERNS envelope for Michigan Flora pimage_info. data is the flat primary image
        record (image_id, image_name, caption, photographer, image_url, thumbnail_url)
        or null if no primary image exists — no wrapper object, no plant_id field.
        Cached permanently (no TTL).
      required:
        - source_url
        - found
        - cache_status
        - queried_at
        - data
        - provenance
      properties:
        source_url:
          type: string
          nullable: true
          description: Michigan Flora species page URL.
        found:
          type: boolean
          description: Whether a primary image was found for this plant_id
        cache_status:
          type: string
          enum: [hit, miss, bypassed, error]
        queried_at:
          type: string
          format: date-time
        data:
          nullable: true
          description: >
            Flat primary image record from the Michigan Flora pimage_info endpoint.
            Null when found is false or no image exists.
          $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
        - licenses
        - license_notes
        - attribution
        - queried_at
        - provenance
      properties:
        service_id:
          type: string
        service_name:
          type: string
        licenses:
          type: array
          items:
            type: string
        license_notes:
          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
        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
        - licenses
        - license_notes
        - queried_at
        - provenance
      properties:
        service_id:
          type: string
        service_name:
          type: string
        licenses:
          type: array
          items:
            type: string
        license_notes:
          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
        queried_at:
          type: string
          format: date-time
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    S2CSeedAvailabilityEntry:
      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"

    S2CSeedAvailabilityData:
      type: object
      description: Data payload for the seed-availability endpoint (FernsEnvelope data field).
      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/S2CSeedAvailabilityEntry"

    S2CSpeciesInformationData:
      type: object
      description: Per-species growing information from the Seeds to Community Washtenaw species information dataset.
      required:
        - botanical_name
      properties:
        botanical_name:
          type: string
          description: Botanical (scientific) name of the species as used in the S2C program
        common_name:
          type: string
          nullable: true
          description: Common name of the species
        special_collect:
          type: boolean
          nullable: true
          description: Whether this species requires special collection handling
        s2c_lists:
          type: string
          nullable: true
          description: S2C program list designations (e.g. Neat & Tidy, Sweet & Simple)
        start_seed_watch:
          type: string
          nullable: true
          description: Month code indicating when to begin seed watch monitoring
        growth_habit:
          type: string
          nullable: true
          description: Plant growth habit (e.g. forb, grass, shrub)
        germination_code:
          type: string
          nullable: true
          description: Germination treatment code for this species
        strat_notes:
          type: string
          nullable: true
          description: Stratification notes describing cold or warm stratification requirements
        planting_notes:
          type: string
          nullable: true
          description: Notes on planting depth, timing, or technique
        process_notes:
          type: string
          nullable: true
          description: Notes on seed processing or cleaning
        bloom_color:
          type: string
          nullable: true
          description: Primary bloom color
        height:
          type: string
          nullable: true
          description: Typical plant height range (e.g. 2'-3')
        stature:
          type: string
          nullable: true
          description: Descriptive stature category (e.g. Ankle, Knee, Waist, Chest, Head)
        compact_bloom_range:
          type: string
          nullable: true
          description: Compact bloom range string (e.g. JUL - AUG)
        bloom_months:
          type: string
          nullable: true
          description: Comma-separated list of bloom month abbreviations
        plant_spacing:
          type: string
          nullable: true
          description: Recommended plant spacing
        light:
          type: string
          nullable: true
          description: Light requirement codes (e.g. Sn, P, Sh)
        moisture:
          type: string
          nullable: true
          description: Moisture preference codes (e.g. D, M, Ms, W)
        species_comments:
          type: string
          nullable: true
          description: Additional comments or notes about the species

    UniversalFqaUpstreamResponse:
      type: object
      required: [status, data]
      properties:
        status:
          type: string
          enum: [success, error]
        data:
          type: array
          items:
            type: array
            items: {}
        message:
          type: string
          description: Present when status is "error"

    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/UniversalFqaUpstreamResponse"
            - 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/UniversalFqaUpstreamResponse"
            - 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/UniversalFqaUpstreamResponse"
            - 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/UniversalFqaUpstreamResponse"
            - type: "null"

    UniversalFqaMetadataResponse:
      type: object
      required:
        - source_id
        - name
        - licenses
        - license_notes
        - attribution
        - registry_entry
        - provenance
      properties:
        source_id:
          type: string
        name:
          type: string
        licenses:
          type: array
          items:
            type: string
        license_notes:
          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

    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"

    NatureserveMetadataData:
      type: object
      description: Registry descriptive content for the NatureServe Explorer service.
      properties:
        description:
          type: string
        general_summary:
          type: string
        technical_details:
          type: string
        licenses:
          type: array
          items:
            type: string
        license_notes:
          type: string
        cache_stats:
          type: object
          properties:
            species_cached:
              type: integer
            ecosystems_cached:
              type: integer
            ttl_days:
              type: object
              additionalProperties: true
        metadata_url:
          type: string
        website_url_patterns:
          type: object
          additionalProperties: true

    NatureserveMetadataResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/NatureserveMetadataData"

    NatureserveSpeciesSearchData:
      type: object
      description: >
        Verbatim NatureServe Explorer speciesSearch response. See GET /natureserve/metadata
        (technical_details) for field-level semantics.
      properties:
        resultsSummary:
          type: object
          additionalProperties: true
          description: Upstream resultsSummary object (totalResults, page, recordsPerPage, etc.)
        results:
          type: array
          description: Array of matching NatureServe species records (verbatim upstream)
          items:
            type: object
            additionalProperties: true

    NatureserveSpeciesSearchResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/NatureserveSpeciesSearchData"

    NatureserveSearchUpstreamData:
      type: object
      description: >
        Verbatim NatureServe Explorer search response. See GET /natureserve/metadata
        (technical_details) for field-level semantics.
      properties:
        resultsSummary:
          type: object
          additionalProperties: true
          description: Upstream resultsSummary object (totalResults, page, recordsPerPage, etc.)
        results:
          type: array
          description: Array of matching NatureServe records (ecosystems, species, communities, etc. — verbatim upstream)
          items:
            type: object
            additionalProperties: true

    NatureserveSearchResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/NatureserveSearchUpstreamData"

    NatureserveTaxonData:
      type: object
      additionalProperties: true
      description: >
        Verbatim NatureServe Explorer taxon record. See GET /natureserve/metadata
        (technical_details) for field-level semantics.

    NatureserveTaxonResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/NatureserveTaxonData"

    SpeciesTextResponse:
      type: object
      description: >
        Response shape for species page text scraping endpoints. Returned by
        /gobotany/species-information, /illinois-wildflowers/species-information,
        /minnesota-wildflowers/species-information, /missouri-plants/species-information,
        and /prairie-moon/species-information.
      required:
        - found
        - queried_at
        - cache_status
      properties:
        found:
          type: boolean
          description: True if the species page was found and text was extracted.
        queried_at:
          type: string
          format: date-time
        source_url:
          type: string
          description: The API endpoint URL that served this response.
        cache_status:
          type: string
          enum: [hit, miss, not_in_species_list]
          description: >
            "hit" — returned from cache. "miss" — live scrape performed and cached.
            "not_in_species_list" — species URL not in the imported species list;
            scrape skipped.
        fetch_error:
          type: string
          description: >
            Present only when a transient upstream error (network failure,
            timeout, 5xx) prevented the scrape. The result was NOT cached
            in this case and the next call will retry the live request.
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          type: object
          description: Present when found=true. Null when not found.
          properties:
            sections:
              type: object
              nullable: true
              additionalProperties:
                type: string
              description: >
                Labeled prose sections extracted from the page (e.g.
                Description, Cultivation, Facts, Habitat). Keys are section
                names, values are plain text content.
            full_text:
              type: string
              nullable: true
              description: >
                All sections concatenated as Label-colon-text blocks, separated by double newlines.

    LbjUrlCheckData:
      type: object
      description: Data payload for /lady-bird-johnson/url FernsEnvelope data field.
      properties:
        profile_url:
          type: string
          nullable: true
          description: Direct profile URL when found; null when not found or unverified.

    LbjUrlCheckResponse:
      description: >
        Legacy envelope schema kept for backwards-compat reference. Route response now uses allOf:FernsEnvelope.
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/LbjUrlCheckData"

    LbjSpeciesTextData:
      type: object
      description: Data payload for /lady-bird-johnson/species-information FernsEnvelope data field.
      properties:
        sections:
          type: object
          nullable: true
          additionalProperties:
            type: string
          description: >
            Labeled prose sections extracted from the page (h3-delimited).
            "Find Seeds or Plants" and "Mr. Smarty Plants says" sections are excluded.
        full_text:
          type: string
          nullable: true
          description: All sections concatenated as Label-colon-text blocks, separated by double newlines.

    LbjSpeciesTextResponse:
      description: >
        Legacy envelope schema kept for backwards-compat reference. Route response now uses allOf:FernsEnvelope.
        cache_status ("hit"/"miss"), queried_at, and source_url are in provenance per FERNS envelope contract v1.
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/LbjSpeciesTextData"

    BotanicalWebRefResponse:
      type: object
      description: >
        Response shape for botanical web reference source URL lookup endpoints
        (gobotany/url, minnesota-wildflowers/url, missouri-plants/url, prairie-moon/url).
        Illinois Wildflowers uses its own inline schema due to its multi-URL structure.
      required:
        - found
        - queried_at
      properties:
        found:
          type: boolean
          description: True if a direct species page URL was resolved.
        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. Null when not found.
          properties:
            url:
              type: string
              nullable: true
              description: Direct species page URL. Null when not found.

    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
        licenses:
          type: array
          items:
            type: string
        license_notes:
          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"

    UsdaPlantsPlantItem:
      type: object
      description: A single USDA PLANTS autocomplete result item (verbatim upstream shape).
      properties:
        Text:
          type: string
          description: Display text for the autocomplete result
        Plant:
          type: object
          description: Raw Plant object from the upstream PlantSearch API (verbatim)
          additionalProperties: true

    UsdaPlantsPlantSearchResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              type: array
              description: Verbatim upstream array of { Text, Plant } autocomplete items
              items:
                $ref: "#/components/schemas/UsdaPlantsPlantItem"

    UsdaPlantsProfileResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: >
                Verbatim upstream PlantProfile object. Top-level keys include Id, Symbol,
                ScientificName, ScientificNameWithoutAuthor, CommonName, NativeStatuses,
                WetlandData, Synonyms, Ancestors, Characteristics, and others.
                No symbol echo or profile nesting — the profile object is data directly.
              additionalProperties: true

    UsdaPlantsSearchResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              type: object
              description: Verbatim upstream POST /plants-search-results response object
              properties:
                PlantResults:
                  type: array
                  description: Array of matching plant records
                  items:
                    type: object
                    additionalProperties: true
                TotalResults:
                  type: integer
                  description: Total number of matching records across all pages
                FilterOptions:
                  type: object
                  description: Available filter facets from the upstream API
                  additionalProperties: true
                SelectedFilters:
                  type: object
                  description: Currently applied filters
                  additionalProperties: true
                Sources:
                  type: array
                  description: Source attribution array from upstream
                  items:
                    type: object
                    additionalProperties: true
                TotalImageCount:
                  type: integer
                  description: Total image count across all results
                Offset:
                  type: integer
                  description: Current result offset (0-based)
                ResultId:
                  type: string
                  description: Opaque result set identifier from upstream

    UsdaPlantsMetadataResponse:
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              type: object
              additionalProperties: true
              description: Registry entry descriptive payload for the USDA PLANTS source

    TrustGroupSummary:
      type: object
      required:
        - group_id
        - slug
        - name
        - created_at
        - updated_at
      properties:
        group_id:
          type: string
          format: uuid
          description: Stable internal identifier for the trust group
        slug:
          type: string
          description: URL-safe unique identifier (e.g. default, research-partners)
        name:
          type: string
          description: Human-readable display name
        owner_email:
          type: string
          nullable: true
          description: Email of the group owner, if set
        geographic_region:
          type: string
          nullable: true
          description: Geographic scope of the trust group, if set
        domain:
          type: string
          nullable: true
          description: Subject-matter domain of the trust group, if set
        description:
          type: string
          nullable: true
          description: Free-text description of the group's purpose
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    TrustTierSummary:
      type: object
      required:
        - tier_id
        - position
        - name
        - created_at
      properties:
        tier_id:
          type: string
          format: uuid
        position:
          type: integer
          description: Ascending sort order; lower = higher trust priority
        name:
          type: string
          description: Display name (e.g. Primary, Secondary, Tertiary)
        created_at:
          type: string
          format: date-time

    TrustTierWithSources:
      type: object
      required:
        - tier_id
        - position
        - name
        - sources
      properties:
        tier_id:
          type: string
          format: uuid
        position:
          type: integer
          description: Ascending sort order; lower = higher trust priority
        name:
          type: string
        sources:
          type: array
          items:
            $ref: "#/components/schemas/SourceSummary"

    TrustGroupsListResponse:
      type: object
      required:
        - source_url
        - found
        - data
        - provenance
      properties:
        source_url:
          type: string
        found:
          type: boolean
        data:
          type: object
          required:
            - groups
          properties:
            groups:
              type: array
              items:
                $ref: "#/components/schemas/TrustGroupSummary"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    TrustGroupDetailResponse:
      type: object
      required:
        - source_url
        - found
        - provenance
      properties:
        source_url:
          type: string
        found:
          type: boolean
        message:
          type: string
          description: Present when found is false
        data:
          nullable: true
          type: object
          required:
            - group
            - tiers
          properties:
            group:
              $ref: "#/components/schemas/TrustGroupSummary"
            tiers:
              type: array
              items:
                $ref: "#/components/schemas/TrustTierSummary"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    TrustGroupSourcesResponse:
      type: object
      required:
        - source_url
        - found
        - provenance
      properties:
        source_url:
          type: string
        found:
          type: boolean
        message:
          type: string
          description: Present when found is false
        data:
          nullable: true
          type: object
          required:
            - group
            - tiers
          properties:
            group:
              type: object
              required:
                - group_id
                - slug
                - name
              properties:
                group_id:
                  type: string
                  format: uuid
                slug:
                  type: string
                name:
                  type: string
            tiers:
              type: array
              items:
                $ref: "#/components/schemas/TrustTierWithSources"
        provenance:
          $ref: "#/components/schemas/FernsProvenance"

    NpnImage:
      type: object
      description: A single image associated with an NPN species record.
      required: [position, url, caption, kind]
      properties:
        position:
          type: integer
          description: 1-based position of this image in the species gallery
        url:
          type: string
          description: Cloudinary CDN URL (or original nativeplant.com URL as fallback)
        caption:
          type: string
          description: Image caption from the source page (may be empty string)
        kind:
          type: string
          enum: [photograph, drawing]
          description: >
            Inferred from caption text: "pen & ink" or "drawing" → drawing; else photograph

    NpnSpeciesRecord:
      type: object
      description: A single species record from The Native Plant Nursery database.
      required: [acronym, latin_name, common_name, images, source_url, scraped_at]
      properties:
        acronym:
          type: string
          description: Primary key — 4–7 character code (e.g. LOBSIP). Carex uses CX prefix.
        latin_name:
          type: string
        latin_synonym_greg:
          type: string
          nullable: true
          description: >
            Greg Vaclavek's Latin synonym. Known quirk: GENAND has "Closed Gentian"
            (a common name) in this field.
        common_name:
          type: string
          description: Raw common name string (may contain ; and , separators)
        light:
          type: string
          nullable: true
        moisture:
          type: string
          nullable: true
        height:
          type: string
          nullable: true
        flowering_time:
          type: string
          nullable: true
        habitat:
          type: string
          nullable: true
        notes:
          type: string
          nullable: true
        range_michigan:
          type: array
          items:
            type: string
          nullable: true
          description: County or region strings within Michigan
        npn_price_sizes:
          type: string
          nullable: true
        images:
          type: array
          items:
            $ref: "#/components/schemas/NpnImage"
        source_url:
          type: string
        scraped_at:
          type: string
          format: date-time

    NpnSpeciesResponse:
      type: object
      description: FERNS envelope for single NPN species lookup (found=true, HTTP 200).
      required: [found, provenance]
      properties:
        found:
          type: boolean
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true
          $ref: "#/components/schemas/NpnSpeciesRecord"

    NpnNotFoundResponse:
      type: object
      description: >
        FERNS envelope when a name key is not in the alias index (HTTP 404). found=false.
      required: [found, provenance]
      properties:
        found:
          type: boolean
          example: false
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          nullable: true

    NpnSpeciesBulkResponse:
      type: object
      description: FERNS envelope for bulk NPN species list.
      required: [found, provenance, data]
      properties:
        found:
          type: boolean
          description: false when the table is empty (import not yet run)
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: object
          required: [species]
          properties:
            species:
              type: array
              items:
                $ref: "#/components/schemas/NpnSpeciesRecord"

    NpnNameGroup:
      type: object
      description: Name group entry for a single NPN species.
      required: [acronym, latin_name, common_names, all_accepted_keys]
      properties:
        acronym:
          type: string
        latin_name:
          type: string
        latin_synonym_greg:
          type: string
          nullable: true
        common_names:
          type: array
          items:
            type: string
          description: Parsed segments (split on ; and , from common_name)
        all_accepted_keys:
          type: array
          items:
            type: string
          description: All lowercase aliases for this species

    NpnNamesResponse:
      type: object
      description: FERNS envelope for NPN name groups (cross-source reconciliation).
      required: [found, provenance, data]
      properties:
        found:
          type: boolean
        provenance:
          $ref: "#/components/schemas/FernsProvenance"
        data:
          type: object
          required: [name_groups]
          properties:
            name_groups:
              type: array
              items:
                $ref: "#/components/schemas/NpnNameGroup"

    GoogleImagesData:
      type: object
      description: Data payload for the /google-images endpoint (FernsEnvelope data field).
      required: [species, url]
      properties:
        species:
          type: string
          description: Scientific name as provided in the query.
        url:
          type: string
          description: Constructed Google Images search URL for the species.

    SourceMetadataData:
      type: object
      description: >
        Data payload for any source's /metadata endpoint (FernsEnvelope data field).
        Contains the source's registry descriptive payload.
      required: [source_id, name, knowledge_type, status, description]
      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
        licenses:
          type: array
          items:
            type: string
        license_notes:
          type: string
        general_summary:
          type: string
        technical_details:
          type: string

    NpnMetadataData:
      type: object
      description: Data payload for /ann-arbor-npn/metadata FernsEnvelope data field.
      properties:
        service_id:
          type: string
          example: ann-arbor-npn
        service_name:
          type: string
        licenses:
          type: array
          items:
            type: string
        license_notes:
          type: string
        registry_entry:
          type: object
          additionalProperties: true

    NpnMetadataResponse:
      description: >
        Legacy envelope schema kept for backwards-compat reference. Route response now uses allOf:FernsEnvelope.
      allOf:
        - $ref: "#/components/schemas/FernsEnvelope"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/NpnMetadataData"

    WildTypeNativePlantsNote:
      type: object
      description: A single ecological note attached to a WildType plant record (code + description pair).
      required: [code, description]
      properties:
        code:
          type: string
          description: >
            Short code abbreviation (e.g. LH, N, O). Some codes are multi-word phrases
            such as "nearly native" or "not a MI genotype" — these are verbatim source codes.
        description:
          type: string
          description: Full description of the ecological note code.

    WildTypeNativePlantsPlantRecord:
      type: object
      description: A single plant record from the WildType Native Plants cultural guide.
      required:
        - scientific_name
        - common_name
        - category
        - flower_color
        - bloom_time
        - sun_full
        - sun_part
        - sun_shade
        - sun_summary
        - height
        - moisture_dry
        - moisture_average
        - moisture_wet
        - moisture_summary
        - notes
      properties:
        scientific_name:
          type: string
          description: Scientific (Latin) name of the species.
        common_name:
          type: string
          description: Common name as used by WildType.
        category:
          type: string
          description: >
            Plant category. One of: wildflowers, grasses, sedges, rushes, ferns, trees, shrubs & vines.
          enum:
            - wildflowers
            - "grasses, sedges, rushes"
            - ferns
            - "trees, shrubs & vines"
        flower_color:
          type: string
          description: Flower color as a string (e.g. White, Pink, Red/Yellow). May be empty for non-flowering plants.
        bloom_time:
          type: string
          description: Bloom time as a string range (e.g. Jul-Aug, May-Jun). Preserved verbatim from the source.
        sun_full:
          type: boolean
          description: Tolerates full sun.
        sun_part:
          type: boolean
          description: Tolerates part sun / part shade.
        sun_shade:
          type: boolean
          description: Tolerates full shade.
        sun_summary:
          type: string
          description: Human-readable sun preference summary (e.g. full, part/shade, full/part/shade).
        height:
          type: string
          description: Height as a string range (e.g. 2'-3', 1'). Preserved verbatim from the source.
        moisture_dry:
          type: boolean
          description: Tolerates dry soil conditions.
        moisture_average:
          type: boolean
          description: Tolerates average soil moisture.
        moisture_wet:
          type: boolean
          description: Tolerates wet soil conditions.
        moisture_summary:
          type: string
          description: Human-readable moisture preference summary (e.g. dry/average, average/wet, wet).
        notes:
          type: array
          description: Ecological notes for this species. Empty array when no notes apply.
          items:
            $ref: "#/components/schemas/WildTypeNativePlantsNote"

    WildTypeNativePlantsPlantGuideData:
      type: object
      description: Data payload for the /wildtype-native-plants/plant-guide endpoint (FernsEnvelope data field).
      required: [total, category, plants]
      properties:
        total:
          type: integer
          description: Number of plant records returned (after any category filter is applied).
        category:
          type: string
          nullable: true
          description: The category filter applied, or null if no filter was specified.
        plants:
          type: array
          description: Plant records matching the request.
          items:
            $ref: "#/components/schemas/WildTypeNativePlantsPlantRecord"

    WildTypeNativePlantsNoteCodesData:
      type: object
      description: Data payload for the /wildtype-native-plants/note-codes endpoint (FernsEnvelope data field).
      required: [total, note_codes]
      properties:
        total:
          type: integer
          description: Total number of note codes in the legend.
        note_codes:
          type: array
          description: Full legend of ecological note codes used in the WildType cultural guide.
          items:
            $ref: "#/components/schemas/WildTypeNativePlantsNote"
