{"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"}}}},"/fire-assessment/{plantId}":{"get":{"summary":"Compute fire assessment for a plant","description":"Stateless computation of Character Score, placement recommendation, and risk reduction text from plant data attributes. Each criterion in the breakdown shows the source attribute and how its score was derived. Replaces the v1 risk-reduction endpoint for new integrations.","tags":["v2 - Fire Assessment"],"parameters":[{"name":"plantId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Fire assessment result","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"plantId":{"type":"string","format":"uuid"},"methodology":{"type":"string","example":"character_score_v1"},"characterScore":{"type":"number","description":"Sum of all applicable criteria scores"},"placement":{"type":"object","properties":{"code":{"type":"string","description":"Planting placement code","enum":["P0","P5","P10","P10S","P30","P50","P100"]},"meaning":{"type":"string","example":"Plant 10+ ft from building"},"minDistance":{"type":"integer","nullable":true,"example":10},"unit":{"type":"string","example":"ft"}}},"plantType":{"type":"object","properties":{"determined":{"type":"array","items":{"type":"string"},"description":"Plant type codes (T, S, V, Gr, G, A, B, P, E, D, Su)"},"derivedFrom":{"type":"object","description":"Which Plant Structure booleans were true"}}},"breakdown":{"type":"array","items":{"type":"object","properties":{"criterion":{"type":"string","example":"tree_debris"},"legacyCode":{"type":"string","example":"5"},"label":{"type":"string","example":"Tree Debris"},"score":{"type":"number","nullable":true},"maxScore":{"type":"number"},"source":{"type":"object","nullable":true,"properties":{"attributeId":{"type":"string","format":"uuid"},"attributeName":{"type":"string"},"rawValue":{"type":"string","nullable":true},"displayValue":{"type":"string","nullable":true}}},"applicable":{"type":"boolean"},"reason":{"type":"string","nullable":true},"appliesTo":{"type":"string","example":"trees_and_shrubs"}}}},"missingData":{"type":"array","items":{"type":"object"},"description":"Criteria that could not be scored (source attribute had no value)"},"riskReductionText":{"type":"string"},"triggeredRules":{"type":"array","items":{"type":"string"}}}}}}}}},"404":{"description":"Plant not found"}}}},"/fire-assessment/methodology":{"get":{"summary":"Get fire assessment scoring methodology","description":"Returns the scoring configuration: all criteria, plant type derivation rules, score ranges, source attribute mappings, and the placement table. Enables UIs to render the scoring worksheet dynamically.","tags":["v2 - Fire Assessment"],"responses":{"200":{"description":"Methodology definition","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"methodology":{"type":"string","example":"character_score_v1"},"version":{"type":"string","example":"1.0"},"description":{"type":"string"},"criteria":{"type":"array","items":{"type":"object"}},"plantTypeDerivation":{"type":"object"},"placementTable":{"type":"array","items":{"type":"object"}}}}}}}}}}}}},"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"}}}}}