{"openapi":"3.0.0","info":{"title":"Living with Fire API v2","version":"2.0.0","description":"Simplified API for the Living with Fire plant database.\n\n## What's New in v2\n- **One-stop plant endpoint**: `GET /plants/{plantId}` returns plant metadata + images + all resolved attribute values in a single call\n- **Inline values on list**: `GET /plants?attributeIds=...` embeds resolved values directly on each plant\n- **Filter by plant IDs**: `GET /plants?plantIds=a,b,c` to fetch specific plants\n\n## Response Format\n```json\n{\n  \"data\": { ... },\n  \"meta\": { \"pagination\": { ... } }\n}\n```\n"},"servers":[{"url":"https://lwf-api.vercel.app/api/v2","description":"Production server"},{"url":"http://localhost:3003/api/v2","description":"Development server"}],"paths":{"/plants":{"get":{"summary":"List plants with optional inline values","description":"Returns paginated plants. Use `attributeIds` to embed resolved attribute values on each plant. Use `plantIds` to filter to specific plants.","tags":["v2 - Plants"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50},"description":"Max results per page"},{"name":"offset","in":"query","schema":{"type":"integer","default":0},"description":"Number of results to skip"},{"name":"plantIds","in":"query","schema":{"type":"string"},"description":"Comma-separated plant IDs to filter"},{"name":"attributeIds","in":"query","schema":{"type":"string"},"description":"Comma-separated attribute IDs — include resolved values per plant"},{"name":"genus","in":"query","schema":{"type":"string"},"description":"Filter by genus (partial match)"},{"name":"species","in":"query","schema":{"type":"string"},"description":"Filter by species (partial match)"},{"name":"commonName","in":"query","schema":{"type":"string"},"description":"Filter by common name (partial match)"},{"name":"search","in":"query","schema":{"type":"string"},"description":"Search across genus, species, common name"},{"name":"includeImages","in":"query","schema":{"type":"string","enum":["true","false"]},"description":"Include primary image per plant"}],"responses":{"200":{"description":"Paginated plant list, optionally with embedded values","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"genus":{"type":"string"},"species":{"type":"string"},"commonName":{"type":"string","nullable":true},"values":{"type":"array","description":"Only present when attributeIds is provided","items":{"type":"object"}}}}},"meta":{"type":"object","properties":{"pagination":{"type":"object","properties":{"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"hasMore":{"type":"boolean"}}}}}}}}}}}}},"/plants/{plantId}":{"get":{"summary":"Get single plant with images and all values","description":"Returns plant metadata, images, and all resolved attribute values in one call. Use `attributeIds` to limit which values are included.","tags":["v2 - Plants"],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Plant ID"},{"name":"attributeIds","in":"query","schema":{"type":"string"},"description":"Comma-separated attribute IDs to limit which values are returned"}],"responses":{"200":{"description":"Plant with images and resolved values","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"genus":{"type":"string"},"species":{"type":"string"},"commonName":{"type":"string","nullable":true},"images":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"type":{"type":"string"},"url":{"type":"string"},"source":{"type":"string","nullable":true}}}},"values":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"attributeId":{"type":"string","format":"uuid"},"attributeName":{"type":"string"},"rawValue":{"type":"string"},"resolved":{"type":"object"}}}}}}}}}}},"404":{"description":"Plant not found"}}}},"/plants/{plantId}/values":{"get":{"summary":"Get all values for a plant","description":"Returns all attribute values with resolved display names. For a single-call alternative that includes plant info + images + values, see `GET /api/v2/plants/{plantId}`.","tags":["Plants","Values"],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"List of resolved values","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ResolvedValue"}}}}}}}}}},"/plants/{plantId}/images":{"get":{"summary":"Get images for a plant","description":"Returns all images for a plant with primary image highlighted","tags":["Plants","Plant Images"],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Plant images with primary image info","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"plantId":{"type":"string","format":"uuid"},"primary":{"$ref":"#/components/schemas/PlantImage","nullable":true},"images":{"type":"array","items":{"$ref":"#/components/schemas/PlantImage"}}}}}}}}},"404":{"description":"Plant not found"}}}},"/plants/{plantId}/attributes/{attributeId}/values":{"post":{"summary":"Save attribute value for a plant","description":"Creates a new value for the given plant and attribute. For single-select attributes, replaces any existing value. Cannot be used on calculated attributes.","tags":["Plants","Values"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"attributeId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string","description":"The value ID (e.g. \"01\", \"02\") or raw value"},"sourceId":{"type":"string","format":"uuid"},"sourceValue":{"type":"string"},"notes":{"type":"string"},"metadata":{"type":"object"}},"required":["value"]}}}},"responses":{"201":{"description":"Value created with resolved display name"},"400":{"description":"Validation error (e.g. calculated attribute, invalid value)"},"401":{"description":"Unauthorized"},"404":{"description":"Plant or attribute not found"}}}},"/plants/{plantId}/risk-reduction":{"get":{"summary":"Get risk reduction data for a plant","description":"Generates risk reduction text and character score based on plant attribute values","tags":["Plants"],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Risk reduction analysis","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"plantId":{"type":"string","format":"uuid"},"plant":{"type":"object","properties":{"genus":{"type":"string"},"species":{"type":"string"},"commonName":{"type":"string","nullable":true}}},"characterScore":{"type":"number","nullable":true},"placement":{"type":"object","properties":{"code":{"type":"string","nullable":true},"meaning":{"type":"string","nullable":true}}},"riskReductionText":{"type":"string","nullable":true},"triggeredRules":{"type":"array","items":{"type":"string"}}}}}}}}},"404":{"description":"Plant not found"}}}},"/attributes":{"get":{"summary":"List all attributes","description":"Returns paginated list of attributes with filtering. Same as v1.","tags":["v2 - Attributes"],"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}},{"name":"name","in":"query","schema":{"type":"string"},"description":"Filter by name (partial match)"},{"name":"search","in":"query","schema":{"type":"string"},"description":"Search attributes"}],"responses":{"200":{"description":"Paginated attribute list"}}}},"/attributes/hierarchical":{"get":{"summary":"Get attributes in parent-child hierarchy","description":"Returns all attributes organized as a tree. Same as v1.","tags":["v2 - Attributes"],"responses":{"200":{"description":"Hierarchical attribute tree"}}}},"/attributes/{attributeId}":{"get":{"summary":"Get single attribute","description":"Returns attribute details by ID. Same as v1.","tags":["v2 - Attributes"],"parameters":[{"name":"attributeId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Attribute details"},"404":{"description":"Attribute not found"}}}},"/sources":{"get":{"summary":"List sources","tags":["Sources"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"},{"$ref":"#/components/parameters/searchParam"},{"name":"region","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of sources"}}},"post":{"summary":"Create source","tags":["Sources"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"url":{"type":"string"},"address":{"type":"string"},"phone":{"type":"string"},"region":{"type":"string"},"targetLocation":{"type":"string"},"topicsAddressed":{"type":"string"},"attribution":{"type":"string"},"notes":{"type":"string"},"refCode":{"type":"string"},"fileLink":{"type":"string"}},"required":["name"]}}}},"responses":{"201":{"description":"Source created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/sources/{sourceId}":{"get":{"summary":"Get source by ID","tags":["Sources"],"parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Source details"},"404":{"description":"Source not found"}}},"put":{"summary":"Update source","tags":["Sources"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Source updated"},"404":{"description":"Source not found"}}},"delete":{"summary":"Delete source","tags":["Sources"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"sourceId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Source deleted"},"404":{"description":"Source not found"}}}},"/nurseries":{"get":{"summary":"List nurseries","tags":["Nurseries"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"},{"$ref":"#/components/parameters/searchParam"}],"responses":{"200":{"description":"Paginated list of nurseries"}}},"post":{"summary":"Create nursery","tags":["Nurseries"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":"string"},"phone":{"type":"string"},"url":{"type":"string"},"notes":{"type":"string"}},"required":["name"]}}}},"responses":{"201":{"description":"Nursery created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/nurseries/{nurseryId}":{"get":{"summary":"Get nursery by ID","tags":["Nurseries"],"parameters":[{"name":"nurseryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Nursery details"},"404":{"description":"Nursery not found"}}},"put":{"summary":"Update nursery","tags":["Nurseries"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"nurseryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Nursery updated"},"404":{"description":"Nursery not found"}}},"delete":{"summary":"Delete nursery","tags":["Nurseries"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"nurseryId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Nursery deleted"},"404":{"description":"Nursery not found"}}}},"/values":{"get":{"summary":"List all values","description":"Paginated list of all values with resolved display names","tags":["Values"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"}],"responses":{"200":{"description":"Paginated list of resolved values"}}}},"/values/bulk":{"get":{"summary":"Get values in bulk by plant and attribute IDs","description":"Efficiently fetch multiple values for given attribute IDs and optionally filter by plant IDs. By default returns raw value IDs. Add `?resolve=true` to include resolved display names (same as `/plants/{plantId}/values`). Each value always includes `attributeName`. When filtering by IDs, default limit is 5,000 rows. Without filters, returns all values.","tags":["Values"],"parameters":[{"name":"attributeIds","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated attribute IDs (optional)"},{"name":"plantIds","in":"query","required":false,"schema":{"type":"string"},"description":"Comma-separated plant IDs (optional)"},{"name":"resolve","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]},"description":"When true, includes resolved display names for each value"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":50000,"default":5000},"description":"Maximum rows to return (default: 5000)"}],"responses":{"200":{"description":"Map of plantId -> attributeId -> values array. Each value includes attributeName, and resolved object when ?resolve=true."}}}},"/values/{valueId}":{"get":{"summary":"Get value by ID","tags":["Values"],"parameters":[{"name":"valueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Value details with resolved display name"},"404":{"description":"Value not found"}}},"put":{"summary":"Update value","tags":["Values"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"valueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"value":{"type":"string"},"sourceId":{"type":"string","format":"uuid"},"sourceValue":{"type":"string"},"urls":{"type":"array","items":{"type":"string"}},"notes":{"type":"string"},"metadata":{"type":"object"}}}}}},"responses":{"200":{"description":"Value updated"},"404":{"description":"Value not found"}}},"delete":{"summary":"Delete value","tags":["Values"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"valueId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Value deleted"},"404":{"description":"Value not found"}}}},"/attribute-sources":{"get":{"summary":"List attribute-source relationships","description":"Get which sources inform which attributes, with value notes and allowed values","tags":["Attribute Sources"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"},{"name":"attributeId","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by attribute ID"},{"name":"sourceId","in":"query","schema":{"type":"string","format":"uuid"},"description":"Filter by source ID"}],"responses":{"200":{"description":"Paginated list of attribute-source relationships","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AttributeSource"}},"meta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}}}}}}}}}},"/attribute-sources/{id}":{"get":{"summary":"Get attribute-source by ID","tags":["Attribute Sources"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Attribute-source relationship details"},"404":{"description":"Not found"}}}},"/plant-images":{"get":{"summary":"List plant images","description":"Paginated list of all plant images with plant info","tags":["Plant Images"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"},{"name":"needsVerification","in":"query","schema":{"type":"string","enum":["true","false"]},"description":"Filter by verification status"},{"name":"source","in":"query","schema":{"type":"string"},"description":"Filter by image source (e.g. trefle, manual)"},{"name":"minScore","in":"query","schema":{"type":"integer","minimum":0,"maximum":100},"description":"Minimum match confidence score"}],"responses":{"200":{"description":"Paginated list of plant images","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PlantImage"}},"meta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}}}}}}}}}},"/plant-images/stats":{"get":{"summary":"Get plant image statistics","description":"Returns counts of images by source, verification status, confidence scores, etc.","tags":["Plant Images"],"responses":{"200":{"description":"Image statistics with totals and breakdown by source"}}}},"/plant-images/{id}":{"get":{"summary":"Get plant image by ID","tags":["Plant Images"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Plant image details with plant info"},"404":{"description":"Image not found"}}},"delete":{"summary":"Delete plant image","tags":["Plant Images"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Image deleted"},"404":{"description":"Image not found"}}}},"/plant-images/{id}/verify":{"patch":{"summary":"Verify or update a plant image","description":"Mark an image as verified and optionally update its URL or type","tags":["Plant Images"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"verified":{"type":"boolean","description":"Whether the image is verified correct"},"imageUrl":{"type":"string","format":"uri","description":"Optional URL override"},"imageType":{"type":"string","description":"Optional type override"}},"required":["verified"]}}}},"responses":{"200":{"description":"Image verified/updated"},"404":{"description":"Image not found"}}}},"/key-terms":{"get":{"summary":"List key terms","description":"Glossary of fire-related terminology","tags":["Key Terms"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"},{"$ref":"#/components/parameters/searchParam"},{"name":"term","in":"query","schema":{"type":"string"},"description":"Filter by term name"}],"responses":{"200":{"description":"Paginated list of key terms","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/KeyTerm"}},"meta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}}}}}}}}},"post":{"summary":"Create key term","tags":["Key Terms"],"security":[{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"term":{"type":"string"},"definition":{"type":"string"},"sortOrder":{"type":"integer"}},"required":["term"]}}}},"responses":{"201":{"description":"Key term created"},"400":{"description":"Validation error"},"401":{"description":"Unauthorized"}}}},"/key-terms/{keyTermId}":{"get":{"summary":"Get key term by ID","tags":["Key Terms"],"parameters":[{"name":"keyTermId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Key term details"},"404":{"description":"Key term not found"}}},"put":{"summary":"Update key term","tags":["Key Terms"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"keyTermId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Key term updated"},"404":{"description":"Key term not found"}}},"delete":{"summary":"Delete key term","tags":["Key Terms"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"keyTermId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Key term deleted"},"404":{"description":"Key term not found"}}}},"/filter-presets":{"get":{"summary":"List filter presets","description":"Paginated list of saved filter presets ordered by sortOrder then name","tags":["Filter Presets"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"},{"$ref":"#/components/parameters/searchParam"},{"name":"name","in":"query","schema":{"type":"string"},"description":"Filter by preset name"}],"responses":{"200":{"description":"Paginated list of filter presets","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/FilterPreset"}},"meta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}}}}}}}}},"post":{"summary":"Create filter preset","tags":["Filter Presets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"filters":{"type":"object","description":"FilterState object"},"columns":{"type":"array","items":{"type":"string"}},"isDefault":{"type":"boolean"},"showOnHomePage":{"type":"boolean"},"showInGenerator":{"type":"boolean"},"sortOrder":{"type":"integer"}},"required":["name","filters"]}}}},"responses":{"201":{"description":"Filter preset created"},"400":{"description":"Validation error"}}}},"/filter-presets/reorder":{"put":{"summary":"Bulk reorder filter presets","tags":["Filter Presets"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"orders":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"sortOrder":{"type":"integer"}},"required":["id","sortOrder"]}}},"required":["orders"]}}}},"responses":{"200":{"description":"Presets reordered"},"400":{"description":"Validation error"}}}},"/filter-presets/{presetId}":{"get":{"summary":"Get filter preset by ID","tags":["Filter Presets"],"parameters":[{"name":"presetId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Filter preset details"},"404":{"description":"Filter preset not found"}}},"put":{"summary":"Update filter preset","tags":["Filter Presets"],"parameters":[{"name":"presetId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"filters":{"type":"object"},"columns":{"type":"array","items":{"type":"string"}},"isDefault":{"type":"boolean"},"showOnHomePage":{"type":"boolean"},"showInGenerator":{"type":"boolean"},"sortOrder":{"type":"integer"}}}}}},"responses":{"200":{"description":"Filter preset updated"},"404":{"description":"Filter preset not found"}}},"delete":{"summary":"Delete filter preset","tags":["Filter Presets"],"parameters":[{"name":"presetId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Filter preset deleted"},"404":{"description":"Filter preset not found"}}}},"/resources":{"get":{"summary":"List resource sections","description":"Paginated list of resource sections with links and subsections","tags":["Resources"],"parameters":[{"$ref":"#/components/parameters/limitParam"},{"$ref":"#/components/parameters/offsetParam"},{"$ref":"#/components/parameters/searchParam"},{"name":"title","in":"query","schema":{"type":"string"},"description":"Filter by section title"}],"responses":{"200":{"description":"Paginated list of resource sections","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ResourceSection"}},"meta":{"type":"object","properties":{"pagination":{"$ref":"#/components/schemas/PaginationMeta"}}}}}}}}}},"post":{"summary":"Create resource section","tags":["Resources"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"subsections":{"type":"array","items":{"type":"object"}},"links":{"type":"array","items":{"type":"object"}},"sortOrder":{"type":"integer"}},"required":["title"]}}}},"responses":{"201":{"description":"Resource section created"},"400":{"description":"Validation error"}}}},"/resources/reorder":{"put":{"summary":"Bulk reorder resource sections","tags":["Resources"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"orders":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"sortOrder":{"type":"integer"}},"required":["id","sortOrder"]}}},"required":["orders"]}}}},"responses":{"200":{"description":"Sections reordered"},"400":{"description":"Validation error"}}}},"/resources/{sectionId}":{"get":{"summary":"Get resource section by ID","tags":["Resources"],"parameters":[{"name":"sectionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Resource section details"},"404":{"description":"Resource section not found"}}},"put":{"summary":"Update resource section","tags":["Resources"],"parameters":[{"name":"sectionId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"subsections":{"type":"array","items":{"type":"object"}},"links":{"type":"array","items":{"type":"object"}},"sortOrder":{"type":"integer"}}}}}},"responses":{"200":{"description":"Resource section updated"},"404":{"description":"Resource section not found"}}},"delete":{"summary":"Delete resource section","tags":["Resources"],"parameters":[{"name":"sectionId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Resource section deleted"},"404":{"description":"Resource section not found"}}}},"/risk-reduction-snippets":{"get":{"summary":"List risk reduction snippets","description":"Returns all editable text snippets used in risk reduction notes, ordered by sortOrder","tags":["Risk Reduction Snippets"],"responses":{"200":{"description":"List of risk reduction snippets","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RiskReductionSnippet"}}}}}}}}}},"/risk-reduction-snippets/{key}":{"get":{"summary":"Get risk reduction snippet by key","tags":["Risk Reduction Snippets"],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"description":"Snippet key, e.g. remove_debris"}],"responses":{"200":{"description":"Snippet details"},"404":{"description":"Snippet not found"}}},"put":{"summary":"Update risk reduction snippet","tags":["Risk Reduction Snippets"],"security":[{"AdminToken":[]}],"parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","description":"Updated snippet text"},"description":{"type":"string","description":"Admin description"}},"required":["text"]}}}},"responses":{"200":{"description":"Snippet updated"},"401":{"description":"Unauthorized"},"404":{"description":"Snippet not found"}}}},"/research/noxious-weeds":{"get":{"summary":"List noxious weed plants with research status","description":"Returns all plants flagged as noxious weeds with their research review status and plant structure values","tags":["Research"],"security":[{"AdminToken":[]}],"responses":{"200":{"description":"List of noxious weed plants with review status","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"reviewed":{"type":"integer"},"pending":{"type":"integer"}}},"plantStructureAttributes":{"type":"array","items":{"type":"object"}}}}}}}}}},"/research/plants/{plantId}":{"get":{"summary":"Get research data for a plant","description":"Returns full research record including Oregon description, AI recommendations, and current structure values","tags":["Research"],"security":[{"AdminToken":[]}],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Plant research data"},"404":{"description":"Plant not found"}}}},"/research/plants/{plantId}/fetch-description":{"post":{"summary":"Fetch Oregon noxious weed description","description":"Scrapes the Oregon Department of Agriculture website for plant description data","tags":["Research"],"security":[{"AdminToken":[]}],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Description fetched and saved"},"404":{"description":"Plant not found"}}}},"/research/plants/{plantId}/save-description":{"post":{"summary":"Manually save plant description","description":"Manually save or edit the Oregon description for a plant","tags":["Research"],"security":[{"AdminToken":[]}],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"oregonUrl":{"type":"string"},"oregonDescription":{"type":"string"}},"required":["oregonDescription"]}}}},"responses":{"200":{"description":"Description saved"},"404":{"description":"Plant not found"}}}},"/research/plants/{plantId}/generate-recommendations":{"post":{"summary":"Generate AI recommendations for plant structure","description":"Analyzes the Oregon description using pattern matching to recommend plant structure boolean values","tags":["Research"],"security":[{"AdminToken":[]}],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Recommendations generated"},"400":{"description":"No description available - fetch description first"},"404":{"description":"Plant not found"}}}},"/research/plants/{plantId}/review":{"put":{"summary":"Submit research review","description":"Submit approved plant structure values and mark the plant as reviewed","tags":["Research"],"security":[{"AdminToken":[]}],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"approvedValues":{"type":"object","additionalProperties":{"type":"boolean"},"description":"Map of attributeId -> boolean value"},"deleteAttributeIds":{"type":"array","items":{"type":"string"},"description":"Attribute IDs whose values should be deleted"},"reviewNotes":{"type":"string"}},"required":["approvedValues"]}}}},"responses":{"200":{"description":"Review submitted and values saved"},"404":{"description":"Plant not found"}}}},"/admin/update-invasive-calculated":{"post":{"summary":"Recalculate all invasive scores","description":"Recalculates the Invasive (Calculated) attribute for all plants","tags":["Admin"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Invasive scores recalculated with count of plants updated"}}}},"/admin/update-wildlife-sum":{"post":{"summary":"Recalculate all wildlife benefit sums","description":"Recalculates the Wildlife Sum (Calculated) attribute for all plants","tags":["Admin"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"Wildlife sums recalculated with count of plants updated"}}}},"/admin/recalculate-all":{"post":{"summary":"Recalculate all calculated fields","description":"Recalculates all calculated attributes (invasive, wildlife sum, character score, risk reduction, Idaho zone tier, value sum) for all plants","tags":["Admin"],"security":[{"BearerAuth":[]}],"responses":{"200":{"description":"All calculated values recalculated"}}}},"/admin/calculated-values/{plantId}":{"get":{"summary":"Get calculated values for a plant","description":"Returns all calculated attribute values for a specific plant","tags":["Admin"],"security":[{"BearerAuth":[]}],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Calculated values for the plant"}}}},"/status":{"get":{"summary":"v2 health check","tags":["v2 - System"],"responses":{"200":{"description":"API status with version and timestamp"}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"BetterAuth session cookie or API key for authenticated requests"},"ApiKey":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"Scoped API key (lwf_sk_... format) for programmatic access. Create keys via POST /api/v1/api-keys."}},"schemas":{"ApiError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["BAD_REQUEST","VALIDATION_ERROR","UNAUTHORIZED","FORBIDDEN","NOT_FOUND","CONFLICT","INTERNAL_ERROR","DATABASE_ERROR"],"description":"Error code for programmatic handling"},"message":{"type":"string","description":"Human-readable error message"},"details":{"type":"object","additionalProperties":true,"description":"Additional error details"}},"required":["code","message"]}}},"PaginationMeta":{"type":"object","properties":{"total":{"type":"integer","description":"Total number of records"},"limit":{"type":"integer","description":"Number of records per page"},"offset":{"type":"integer","description":"Number of records skipped"},"hasMore":{"type":"boolean","description":"Whether more records exist"}}},"Plant":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Unique plant identifier"},"genus":{"type":"string","description":"Plant genus"},"species":{"type":"string","description":"Plant species"},"subspeciesVarieties":{"type":"string","nullable":true},"commonName":{"type":"string","nullable":true},"urls":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Reference URLs, optionally pipe-delimited: \"Display Text|https://url.com\""},"notes":{"type":"string","nullable":true},"lastUpdated":{"type":"string","nullable":true}},"required":["id","genus","species"]},"Attribute":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"parentAttributeId":{"type":"string","format":"uuid","nullable":true},"valueType":{"type":"string","enum":["text","boolean","integer","decimal"]},"selectionType":{"type":"string","enum":["single","multi"]},"valuesAllowed":{"type":"array","nullable":true,"items":{"type":"object","properties":{"id":{"type":"string"},"displayName":{"type":"string"},"description":{"type":"string","nullable":true}}}},"valueUnits":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"isCalculated":{"type":"boolean","description":"Whether this attribute is auto-calculated"},"calculatedFrom":{"type":"array","items":{"type":"string","format":"uuid"},"nullable":true,"description":"Attribute IDs used for calculation"},"calculationLogic":{"type":"string","nullable":true},"calculationDescription":{"type":"string","nullable":true}},"required":["id","name","valueType"]},"ResolvedValue":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"attributeId":{"type":"string","format":"uuid"},"attributeName":{"type":"string"},"plantId":{"type":"string","format":"uuid"},"rawValue":{"type":"string","nullable":true},"resolved":{"type":"object","properties":{"id":{"type":"string"},"value":{"type":"string"},"description":{"type":"string","nullable":true},"raw":{"type":"string"},"type":{"type":"string","enum":["enum","boolean","integer","number","text","reference"]}}},"sourceId":{"type":"string","format":"uuid","nullable":true},"sourceValue":{"type":"string","nullable":true},"urls":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Reference URLs for this value"},"notes":{"type":"string","nullable":true},"metadata":{"type":"object","nullable":true,"description":"Additional metadata like citation info"}}},"AttributeSource":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"attributeId":{"type":"string","format":"uuid"},"sourceId":{"type":"string","format":"uuid"},"attributeName":{"type":"string","nullable":true},"sourceName":{"type":"string","nullable":true},"sourceRefCode":{"type":"string","nullable":true},"valuesAllowed":{"type":"object","nullable":true},"valueNotes":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}}},"PlantImage":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"plantId":{"type":"string","format":"uuid"},"imageUrl":{"type":"string"},"imageType":{"type":"string","nullable":true,"description":"habit, flower, leaf, bark, fruit, other"},"source":{"type":"string","nullable":true,"description":"Image source: trefle, manual, usda, etc."},"copyright":{"type":"string","nullable":true},"isPrimary":{"type":"boolean"},"matchScore":{"type":"number","nullable":true,"description":"0-100 confidence score"},"needsVerification":{"type":"boolean","nullable":true}}},"Source":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"region":{"type":"string","nullable":true},"targetLocation":{"type":"string","nullable":true},"topicsAddressed":{"type":"string","nullable":true},"attribution":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true},"refCode":{"type":"string","nullable":true},"fileLink":{"type":"string","nullable":true}},"required":["id","name"]},"Nursery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"address":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"url":{"type":"string","nullable":true},"notes":{"type":"string","nullable":true}},"required":["id","name"]},"KeyTerm":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"term":{"type":"string","description":"The term or phrase being defined"},"definition":{"type":"string","nullable":true,"description":"Definition of the term"},"sortOrder":{"type":"integer","nullable":true,"description":"Custom sort order"},"updatedAt":{"type":"string","nullable":true,"description":"Last update timestamp"},"updatedBy":{"type":"string","nullable":true,"description":"Clerk user ID of last editor"}},"required":["id","term"]},"FilterPreset":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","nullable":true},"filters":{"type":"object","description":"FilterState object defining the preset filters"},"columns":{"type":"array","items":{"type":"string"},"nullable":true,"description":"Selected column keys for report view"},"isDefault":{"type":"boolean"},"showOnHomePage":{"type":"boolean","description":"Show as quick pick on home page"},"showInGenerator":{"type":"boolean","description":"Show in generate dropdown"},"sortOrder":{"type":"integer"},"createdAt":{"type":"string"},"updatedAt":{"type":"string","nullable":true}},"required":["id","name","filters"]},"ResourceSection":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string","nullable":true},"subsections":{"type":"array","nullable":true,"items":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string","nullable":true},"links":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":"string"},"description":{"type":"string","nullable":true},"duration":{"type":"string","nullable":true}}}}}}},"links":{"type":"array","nullable":true,"items":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":"string"},"description":{"type":"string","nullable":true},"duration":{"type":"string","nullable":true}}}},"sortOrder":{"type":"integer"},"createdAt":{"type":"string"},"updatedAt":{"type":"string","nullable":true}},"required":["id","title"]},"RiskReductionSnippet":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Unique key identifier, e.g. remove_debris, prune_base"},"text":{"type":"string","description":"The snippet content text"},"description":{"type":"string","nullable":true,"description":"Admin description of when this snippet is used"},"sortOrder":{"type":"integer"},"updatedAt":{"type":"string","nullable":true},"updatedBy":{"type":"string","nullable":true}},"required":["id","key","text"]}},"parameters":{"limitParam":{"name":"limit","in":"query","description":"Number of records to return (max 1000)","schema":{"type":"integer","minimum":1,"maximum":1000,"default":50}},"offsetParam":{"name":"offset","in":"query","description":"Number of records to skip","schema":{"type":"integer","minimum":0,"default":0}},"searchParam":{"name":"search","in":"query","description":"Search term to filter results","schema":{"type":"string"}}}}}