{
  "openapi": "3.1.0",
  "info": {
    "title": "Southern Horticulture Plant Catalog API",
    "version": "1.0.0",
    "summary": "Read-only access to the live plant catalog, care data, and store information for Southern Horticulture.",
    "description": "Southern Horticulture is a family-owned garden center in St. Augustine, FL (USDA Zone 9b), founded in 1979.\n\nThis API exposes the same live, inventory-backed data the public website renders, in a form an agent can consume directly.\nPrefer it over scraping HTML: it costs roughly a tenth of the tokens and returns complete, structured records.\n\n## When to use this API\n\n- Selecting plants for USDA Zone 9b / Northeast Florida conditions (sandy soil, coastal salt exposure, heat).\n- Answering pet-safety questions about plants (ASPCA non-toxic filtering is first-class on `recommend`).\n- Looking up care requirements (light, water, mature size) for a specific plant.\n- Checking what a local independent nursery actually stocks, and its starting prices.\n- Store details: address, hours, phone, service area.\n\n## When NOT to use it\n\n- It is not a general botanical database; it covers what this one nursery sells.\n- It is not transactional: there is no cart, checkout, ordering, or reservation endpoint.\n- Stock levels are not real-time. Confirm availability by phone before promising a customer an item.\n\n## Two doors, same tools\n\n1. **REST GET**: `GET /api/mcp?tool=<name>&...`. No body, no client library, JSON back.\n2. **MCP JSON-RPC 2.0**: `POST /api/mcp`, Streamable HTTP transport, for MCP-capable clients.\n\nAuthentication: none. All data here is public.\n\n## Versioning\n\nCurrent major version: **v1**.\n\n- `/api/v1/mcp` is PINNED. Within a major version changes are additive only: new tools, new optional parameters, new response fields. Nothing that exists is removed or retyped. Integrate against this path if you need the shape to hold.\n- `/api/mcp` is the unversioned alias for the current major. Convenient, but it MOVES when the next major ships.\n- Every response carries `X-API-Version` naming the version that served it.\n\n## Deprecation policy\n\nRetirement is signalled in-band, on the responses of the affected endpoint, so a client learns about it from traffic it is already making:\n\n- `Deprecation: <http-date>` when the endpoint entered deprecation.\n- `Sunset: <http-date>` when it stops answering (RFC 8594).\n- `Link: <successor>; rel=\"successor-version\"` pointing at the replacement.\n\nMinimum notice is **180 days** between Deprecation and Sunset. Nothing is currently deprecated.\n\nRate limit: about 20 requests per minute per IP, shared across the REST and JSON-RPC doors. Exceeding it returns 429 with error code `rate_limited`. Cache responses; the catalog changes daily at most.\n\nEvery response that consumes budget carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (delta-seconds) and `RateLimit-Policy`, plus the same values in the `X-RateLimit-*` form. A 429 additionally carries `Retry-After`. Read them and self-throttle rather than waiting to be refused.",
    "contact": {
      "name": "Southern Horticulture (contact page)",
      "url": "https://sohonursery.com/contact"
    },
    "license": {
      "name": "Public data, attribution requested",
      "url": "https://sohonursery.com/developers"
    }
  },
  "servers": [
    {
      "url": "https://sohonursery.com",
      "description": "Production (major version v1)"
    }
  ],
  "externalDocs": {
    "description": "Developer portal, agent guidance, and copy-pasteable examples",
    "url": "https://sohonursery.com/developers"
  },
  "tags": [
    {
      "name": "catalog",
      "description": "Plant catalog search, recommendation, and care lookup."
    },
    {
      "name": "store",
      "description": "Store location, hours, and services."
    },
    {
      "name": "discovery",
      "description": "Machine-readable descriptions of this API."
    }
  ],
  "paths": {
    "/api/mcp": {
      "get": {
        "operationId": "call_catalog_tool",
        "summary": "Call a catalog tool over plain HTTP GET",
        "description": "The REST door to the same four tools the MCP server exposes. Pass `tool` plus that tool's parameters as query strings; the response is the tool payload as JSON, with no JSON-RPC envelope.\n\nOmit `tool` entirely to get a self-describing discovery manifest listing every tool and an example URL for each.\n\nRate limit: about 20 requests per minute per IP, shared across the REST and JSON-RPC doors. Exceeding it returns 429 with error code `rate_limited`. Cache responses; the catalog changes daily at most.\n\nEvery response that consumes budget carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (delta-seconds) and `RateLimit-Policy`, plus the same values in the `X-RateLimit-*` form. A 429 additionally carries `Retry-After`. Read them and self-throttle rather than waiting to be refused.",
        "tags": [
          "catalog"
        ],
        "parameters": [
          {
            "name": "tool",
            "in": "query",
            "required": false,
            "description": "Which tool to call. Omit for the discovery manifest. `recommend` is the best default for a shopper question stated in natural language.",
            "schema": {
              "type": "string",
              "enum": [
                "recommend",
                "search_plants",
                "get_plant_care",
                "get_store_info"
              ]
            }
          },
          {
            "name": "context",
            "in": "query",
            "required": false,
            "description": "The user's underlying question or goal in their own words (e.g. 'pet-safe privacy screen for a windy coastal yard'). Strongly recommended for `recommend` and `search_plants`: it materially improves match quality.",
            "schema": {
              "type": "string",
              "maxLength": 500
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "description": "Free-text match against common name and botanical name.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "trait",
            "in": "query",
            "required": false,
            "description": "Restrict results to one horticultural trait tag.",
            "schema": {
              "type": "string",
              "enum": [
                "native-florida",
                "salt-tolerant-high",
                "salt-tolerant-moderate",
                "pollinator-friendly",
                "deer-resistant",
                "drought-tolerant",
                "shade-loving",
                "edible",
                "privacy"
              ]
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Plant category, e.g. Shrub, Tree, Perennial, Palm, Herb.",
            "schema": {
              "type": "string",
              "maxLength": 80
            }
          },
          {
            "name": "pet_safe",
            "in": "query",
            "required": false,
            "description": "For `recommend`: restrict to plants the ASPCA lists as non-toxic to dogs and cats. Applied automatically when `context` mentions a pet.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "For `get_plant_care`: the plant page slug, i.e. the last path segment of a plant URL returned by `search_plants` (e.g. 'abelia').",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]{1,120}$"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results. `search_plants` defaults to 10 (max 25); `recommend` defaults to 6 (max 15). Out-of-range values are clamped, not rejected.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tool payload, or the discovery manifest when `tool` is omitted.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/DiscoveryManifest"
                    },
                    {
                      "$ref": "#/components/schemas/SearchPlantsResult"
                    },
                    {
                      "$ref": "#/components/schemas/RecommendResult"
                    },
                    {
                      "$ref": "#/components/schemas/PlantCareResult"
                    },
                    {
                      "$ref": "#/components/schemas/StoreInfoResult"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unknown tool name, or a parameter the tool cannot use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "The tool ran but found nothing, e.g. get_plant_care with a slug no plant uses.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "Returned when a client requests the server-to-client SSE stream (`Accept: text/event-stream`). This server is stateless and pushes nothing; use POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Back off and cache.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Present on 429 responses.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The catalog database did not answer. Retry with backoff.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "call_mcp_jsonrpc",
        "summary": "Model Context Protocol endpoint (JSON-RPC 2.0)",
        "description": "MCP Streamable HTTP transport, stateless: one POST in, one JSON response out. Supported methods: `initialize`, `tools/list`, `tools/call`, and the `notifications/*` messages (which return 202 with no body).\n\nThe four tools are the same ones the GET door exposes. An MCP-capable client should point at this URL directly.\n\nRate limit: about 20 requests per minute per IP, shared across the REST and JSON-RPC doors. Exceeding it returns 429 with error code `rate_limited`. Cache responses; the catalog changes daily at most.\n\nEvery response that consumes budget carries `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` (delta-seconds) and `RateLimit-Policy`, plus the same values in the `X-RateLimit-*` form. A 429 additionally carries `Retry-After`. Read them and self-throttle rather than waiting to be refused.",
        "tags": [
          "catalog"
        ],
        "requestBody": {
          "required": true,
          "description": "A single JSON-RPC 2.0 message, or an array of them (batch).",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/JsonRpcRequest"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/JsonRpcRequest"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or error object.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              },
              "MCP-Protocol-Version": {
                "description": "The negotiated MCP protocol version in force for this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "2025-03-26"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/JsonRpcResponse"
                    },
                    {
                      "$ref": "#/components/schemas/JsonRpcError"
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted; no body."
          },
          "400": {
            "description": "Malformed JSON, or a message that is not valid JSON-RPC 2.0.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Present on 429 responses.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/mcp": {
      "get": {
        "operationId": "call_catalog_tool_v1",
        "summary": "Call a catalog tool (version-pinned)",
        "description": "Identical behaviour to `GET /api/mcp`, served by the same handler, but pinned to major version 1.\n\nIntegrate against this path when you need the response shape to hold: within a major version changes are additive only.\n\nThe unversioned `/api/mcp` alias tracks the current major and will move when v2 ships.",
        "tags": [
          "catalog"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ToolName"
          }
        ],
        "responses": {
          "200": {
            "description": "The tool payload, or the discovery manifest when `tool` is omitted.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/DiscoveryManifest"
                    },
                    {
                      "$ref": "#/components/schemas/SearchPlantsResult"
                    },
                    {
                      "$ref": "#/components/schemas/RecommendResult"
                    },
                    {
                      "$ref": "#/components/schemas/PlantCareResult"
                    },
                    {
                      "$ref": "#/components/schemas/StoreInfoResult"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unknown tool name, or a parameter the tool cannot use.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited. Back off and cache.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Present on 429 responses.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "call_mcp_jsonrpc_v1",
        "summary": "MCP endpoint (version-pinned)",
        "description": "Identical behaviour to `POST /api/mcp`, pinned to major version 1. See that operation for the protocol details.",
        "tags": [
          "catalog"
        ],
        "requestBody": {
          "required": true,
          "description": "A single JSON-RPC 2.0 message, or an array of them (batch).",
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/JsonRpcRequest"
                  },
                  {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/JsonRpcRequest"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC result or error object.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/JsonRpcResponse"
                    },
                    {
                      "$ref": "#/components/schemas/JsonRpcError"
                    }
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Notification accepted; no body."
          },
          "429": {
            "description": "Rate limited.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Requests permitted in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests still available in the current window.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the window resets (delta-seconds, not a timestamp).",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              },
              "RateLimit-Policy": {
                "description": "Quota policy, as `<limit>;w=<window-seconds>`.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "20;w=60"
                  ]
                }
              },
              "X-RateLimit-Limit": {
                "description": "Same as RateLimit-Limit, in the widely-deployed X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    20
                  ]
                }
              },
              "X-RateLimit-Remaining": {
                "description": "Same as RateLimit-Remaining, in the X- form.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    19
                  ]
                }
              },
              "X-RateLimit-Reset": {
                "description": "Window reset as a Unix timestamp in seconds.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    1755950000
                  ]
                }
              },
              "X-API-Version": {
                "description": "Major version that served this response.",
                "schema": {
                  "type": "string",
                  "examples": [
                    "v1"
                  ]
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying. Present on 429 responses.",
                "schema": {
                  "type": "integer",
                  "examples": [
                    42
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcError"
                }
              }
            }
          }
        }
      }
    },
    "/api/seasonal-plants": {
      "get": {
        "operationId": "list_seasonal_plants",
        "summary": "Plants worth planting in the current season",
        "description": "The seasonal planting list for Northeast Florida, as rendered on the seasonal planting guide pages. Filterable to edibles or landscape plants.",
        "tags": [
          "catalog"
        ],
        "parameters": [
          {
            "name": "filter",
            "in": "query",
            "required": false,
            "description": "Which slice of the seasonal list to return. Unrecognized values fall back to 'all' rather than erroring.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "edibles",
                "landscape"
              ],
              "default": "all"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The seasonal plant list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SeasonalPlantsResult"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "get_openapi_json",
        "summary": "This document, as JSON",
        "description": "The machine-readable description of this API in OpenAPI 3.1 JSON.",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDocument"
                }
              }
            }
          }
        }
      }
    },
    "/api/openapi.yaml": {
      "get": {
        "operationId": "get_openapi_yaml",
        "summary": "This document, as YAML",
        "description": "The same OpenAPI 3.1 document serialized as YAML.",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "The same document, YAML-serialized.",
            "content": {
              "application/yaml": {
                "schema": {
                  "$ref": "#/components/schemas/YamlDocument"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "get_mcp_manifest",
        "summary": "MCP server manifest",
        "description": "Capability manifest for the MCP server: endpoint, transport, protocol version, tool names, and REST examples.",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "MCP manifest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpManifest"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/agent-instructions.md": {
      "get": {
        "operationId": "get_agent_instructions",
        "summary": "When an agent should use this site, and how",
        "description": "Markdown guidance naming the jobs this site is a good fit for, and the exact call to make for each.",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "Agent instructions in Markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "$ref": "#/components/schemas/MarkdownDocument"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "get_llms_txt",
        "summary": "Site guide for language models",
        "description": "The llms.txt site guide: what this site covers, when to use it, entry points, and a live catalog snapshot.",
        "tags": [
          "discovery"
        ],
        "responses": {
          "200": {
            "description": "llms.txt content.",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/PlainTextDocument"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ToolName": {
        "name": "tool",
        "in": "query",
        "required": false,
        "description": "Which tool to call. Omit for the discovery manifest. `recommend` is the best default for a shopper question stated in natural language.",
        "schema": {
          "type": "string",
          "enum": [
            "recommend",
            "search_plants",
            "get_plant_care",
            "get_store_info"
          ]
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The error envelope used by every REST response in this API.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "hint",
              "documentation_url",
              "status"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "not_found",
                  "unknown_tool",
                  "invalid_parameter",
                  "rate_limited",
                  "method_not_allowed",
                  "upstream_unavailable"
                ],
                "description": "Stable machine-readable error code. Match on this, not the message."
              },
              "message": {
                "type": "string",
                "description": "Human-readable description of what went wrong."
              },
              "hint": {
                "type": "string",
                "description": "The concrete next action that resolves this error."
              },
              "documentation_url": {
                "type": "string",
                "format": "uri",
                "description": "Where to read more."
              },
              "status": {
                "type": "integer",
                "description": "HTTP status, repeated in the body."
              }
            }
          }
        }
      },
      "JsonRpcRequest": {
        "type": "object",
        "description": "A JSON-RPC 2.0 request message.",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0",
            "description": "Protocol version. Must be the string \"2.0\"."
          },
          "id": {
            "description": "Request identifier, echoed on the response. Omit for notifications.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ]
          },
          "method": {
            "type": "string",
            "description": "MCP method name.",
            "examples": [
              "initialize",
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object",
            "description": "Method parameters.",
            "additionalProperties": true
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "description": "A successful JSON-RPC 2.0 response.",
        "required": [
          "jsonrpc",
          "result"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true,
            "description": "Method result."
          }
        }
      },
      "JsonRpcError": {
        "type": "object",
        "description": "A JSON-RPC 2.0 error response.",
        "required": [
          "jsonrpc",
          "error"
        ],
        "properties": {
          "jsonrpc": {
            "const": "2.0"
          },
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "description": "JSON-RPC error code (e.g. -32600, -32601, -32700)."
              },
              "message": {
                "type": "string",
                "description": "Error description."
              }
            }
          }
        }
      },
      "PlantSummary": {
        "type": "object",
        "description": "One plant as returned by a catalog search.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Common name as the nursery labels it."
          },
          "botanicalName": {
            "type": "string",
            "description": "Botanical (Latin) name, when recorded."
          },
          "category": {
            "type": "string",
            "description": "Plant category, e.g. Shrub, Palm, Perennial."
          },
          "traits": {
            "type": "array",
            "description": "Horticultural trait tags that apply to this plant.",
            "items": {
              "type": "string",
              "enum": [
                "native-florida",
                "salt-tolerant-high",
                "salt-tolerant-moderate",
                "pollinator-friendly",
                "deer-resistant",
                "drought-tolerant",
                "shade-loving",
                "edible",
                "privacy"
              ]
            }
          },
          "petSafety": {
            "type": "string",
            "description": "ASPCA pet-toxicity classification, present on `recommend` results when recorded."
          },
          "startingPriceUSD": {
            "type": "number",
            "description": "Lowest listed price across sizes, in USD."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Canonical page URL. Cite this."
          }
        }
      },
      "SearchPlantsResult": {
        "type": "object",
        "description": "Result of `search_plants`.",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlantSummary"
            }
          },
          "attribution": {
            "type": "string",
            "description": "Attribution string to cite when quoting this data."
          }
        }
      },
      "RecommendResult": {
        "type": "object",
        "description": "Result of `recommend`.",
        "properties": {
          "recommendation_for": {
            "type": "string",
            "description": "The context string this answers."
          },
          "pet_safe_filtered": {
            "type": "boolean",
            "description": "True when ASPCA non-toxic filtering was applied."
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PlantSummary"
            }
          },
          "note": {
            "type": "string",
            "description": "Caveat about the filtering that was applied."
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "PlantCareResult": {
        "type": "object",
        "description": "Result of `get_plant_care`.",
        "properties": {
          "name": {
            "type": "string"
          },
          "botanicalName": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "description": "Short prose description of the plant."
          },
          "care": {
            "type": "object",
            "description": "Care requirements as the nursery records them.",
            "properties": {
              "light": {
                "type": "string",
                "description": "Light requirements."
              },
              "water": {
                "type": "string",
                "description": "Water needs."
              },
              "matureHeight": {
                "type": "string",
                "description": "Mature height."
              },
              "matureWidth": {
                "type": "string",
                "description": "Mature width."
              },
              "zone": {
                "type": "string",
                "description": "USDA hardiness zone this advice is written for."
              }
            }
          },
          "startingPriceUSD": {
            "type": "number"
          },
          "sizes": {
            "type": "array",
            "description": "Per-size pricing, when the plant is stocked in multiple sizes.",
            "items": {
              "type": "object",
              "properties": {
                "size": {
                  "type": "string",
                  "description": "Container size, e.g. \"3 gallon\"."
                },
                "priceUSD": {
                  "type": "number",
                  "description": "Price for that size, in USD."
                }
              }
            }
          },
          "availability": {
            "type": "string",
            "description": "Stock caveat. Not a real-time inventory count."
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "StoreInfoResult": {
        "type": "object",
        "description": "Result of `get_store_info`.",
        "properties": {
          "name": {
            "type": "string"
          },
          "address": {
            "type": "string",
            "description": "Single-line street address."
          },
          "phone": {
            "type": "string",
            "description": "Public phone number."
          },
          "hours": {
            "type": "object",
            "description": "Opening hours as displayed to customers.",
            "properties": {
              "monSat": {
                "type": "string",
                "description": "Monday to Saturday hours."
              },
              "sunday": {
                "type": "string",
                "description": "Sunday hours."
              }
            }
          },
          "serviceArea": {
            "type": "array",
            "description": "Places the business serves.",
            "items": {
              "type": "string"
            }
          },
          "services": {
            "type": "array",
            "description": "Services offered.",
            "items": {
              "type": "string"
            }
          },
          "website": {
            "type": "string",
            "format": "uri"
          },
          "directions": {
            "type": "string",
            "format": "uri",
            "description": "Map link for driving directions."
          },
          "attribution": {
            "type": "string"
          }
        }
      },
      "SeasonalPlantsResult": {
        "type": "object",
        "description": "Result of the seasonal planting list.",
        "properties": {
          "plants": {
            "type": "array",
            "description": "Plants recommended for planting in the current season.",
            "items": {
              "$ref": "#/components/schemas/PlantSummary"
            }
          }
        }
      },
      "OpenApiDocument": {
        "type": "object",
        "description": "An OpenAPI 3.1 description document.",
        "required": [
          "openapi",
          "info",
          "paths"
        ],
        "properties": {
          "openapi": {
            "type": "string",
            "description": "Specification version.",
            "examples": [
              "3.1.0"
            ]
          },
          "info": {
            "type": "object",
            "description": "Document metadata.",
            "properties": {
              "title": {
                "type": "string",
                "description": "API title."
              },
              "version": {
                "type": "string",
                "description": "API version string."
              },
              "summary": {
                "type": "string",
                "description": "One-line summary."
              },
              "description": {
                "type": "string",
                "description": "Long-form description, Markdown."
              }
            }
          },
          "servers": {
            "type": "array",
            "description": "Base URLs this document applies to.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Server base URL."
                },
                "description": {
                  "type": "string",
                  "description": "What this server is."
                }
              }
            }
          },
          "paths": {
            "type": "object",
            "description": "Path items, keyed by URL path.",
            "additionalProperties": true
          },
          "components": {
            "type": "object",
            "description": "Reusable schemas, headers and responses.",
            "additionalProperties": true
          }
        }
      },
      "McpManifest": {
        "type": "object",
        "description": "Model Context Protocol server manifest.",
        "required": [
          "name",
          "endpoint",
          "transport"
        ],
        "properties": {
          "version": {
            "type": "string",
            "description": "Manifest format version."
          },
          "name": {
            "type": "string",
            "description": "Server name."
          },
          "description": {
            "type": "string",
            "description": "What the server serves."
          },
          "endpoint": {
            "type": "string",
            "format": "uri",
            "description": "MCP endpoint URL."
          },
          "transport": {
            "type": "string",
            "description": "MCP transport in use.",
            "enum": [
              "streamable-http"
            ]
          },
          "protocolVersion": {
            "type": "string",
            "description": "MCP protocol version."
          },
          "authentication": {
            "type": "string",
            "description": "Auth scheme required to call the server.",
            "enum": [
              "none"
            ]
          },
          "tools": {
            "type": "array",
            "description": "Tool names the server exposes.",
            "items": {
              "type": "string",
              "enum": [
                "recommend",
                "search_plants",
                "get_plant_care",
                "get_store_info"
              ]
            }
          },
          "rateLimit": {
            "type": "string",
            "description": "Human-readable rate limit summary."
          },
          "documentation": {
            "type": "string",
            "format": "uri",
            "description": "Where to read more."
          },
          "contact": {
            "type": "string",
            "format": "uri",
            "description": "How to reach a human."
          }
        }
      },
      "YamlDocument": {
        "type": "string",
        "description": "A YAML 1.2 document, served as application/yaml.",
        "contentMediaType": "application/yaml"
      },
      "MarkdownDocument": {
        "type": "string",
        "description": "A CommonMark document, served as text/markdown.",
        "contentMediaType": "text/markdown"
      },
      "PlainTextDocument": {
        "type": "string",
        "description": "A UTF-8 plain-text document, served as text/plain.",
        "contentMediaType": "text/plain"
      },
      "DiscoveryManifest": {
        "type": "object",
        "description": "Self-describing manifest returned by GET /api/mcp with no `tool` parameter.",
        "properties": {
          "name": {
            "type": "string",
            "description": "Server name."
          },
          "description": {
            "type": "string",
            "description": "What this server serves."
          },
          "usage": {
            "type": "object",
            "description": "How to call it, per door.",
            "properties": {
              "rest_get": {
                "type": "string",
                "description": "How to use the REST GET door."
              },
              "mcp_post": {
                "type": "string",
                "description": "How to use the JSON-RPC door."
              }
            }
          },
          "tools": {
            "type": "array",
            "description": "Available tools with an example URL each.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "enum": [
                    "recommend",
                    "search_plants",
                    "get_plant_care",
                    "get_store_info"
                  ]
                },
                "best_for": {
                  "type": "string",
                  "description": "When to reach for this tool."
                },
                "get": {
                  "type": "string",
                  "format": "uri",
                  "description": "A working example URL."
                },
                "params": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Accepted parameters."
                }
              }
            }
          },
          "traits": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Valid trait filter values."
          },
          "docs": {
            "type": "string",
            "format": "uri"
          },
          "attribution": {
            "type": "string"
          }
        }
      }
    }
  }
}