{
  "openapi": "3.0.3",
  "info": {
    "title": "YesTechie — UniFi Network & Security Integrator",
    "version": "1.0.0",
    "description": "## Which way should I use?\n\n| If you... | Use |\n|---|---|\n| 1. Can make HTTP POST with JSON (coding agents, custom agents) | POST /api/agent/v0/* on https://yestechie.com (index: GET https://yestechie.com/api/agent/v0/site; spec: https://yestechie.com/openapi.yaml) |\n| 2. Are connected via MCP (Claude / ChatGPT connector) | The MCP tools at https://yestechie.com/api/mcp |\n| 3. Can only open URLs / GET (ChatGPT / Claude chat apps, browsing agents) | GET /api/agent/v0/quote, then /api/agent/v0/lead. Fill in: https://yestechie.com/api/agent/v0/quote?address={address}&aps={n}&cameras={n}&doors={n} then, only if the person asks to be contacted, https://yestechie.com/api/agent/v0/lead?quoteRef={quoteRef}&name={name}&email={email} (or &phone={phone}). Example: https://yestechie.com/api/agent/v0/quote?address=350+S+Grand+Ave,+Los+Angeles,+CA+90071&aps=4&cameras=8&doors=2 and then https://yestechie.com/api/agent/v0/lead?quoteRef=<quoteRef from that answer>&name=Jane+Doe&email=jane@example.com |\n| 4. Can do none of these | Send the person to https://yestechie.com/contact or 818-275-2707 |\n\nYesTechie is a UniFi network and security integrator headquartered in California, serving businesses nationwide. It designs, installs and operates enterprise networks and physical security: UniFi switching and Wi-Fi, cameras, door access control, structured cabling and fiber, point-to-point wireless, and managed operations. UniFi is the toolset; the deliverable is a working site. This MCP server opens what we've learned on real jobs. Agents can read project case studies from completed installs and practical configuration guides for UniFi networks, Wi-Fi, cameras and access control. They can also price a project with the same logic our estimators use: send counts of access points, cameras and doors with a site address and get an itemized estimate with equipment, labor and travel lines. Coverage lookup and list prices are included. When the person asks for a walkthrough, the agent sends the quote reference with their contact details to request a site survey. Read tools need no key; contact is sent only on explicit request.\nPublic read API for YesTechie (UniFi network, camera and access-control installs across the United States),\na 2-call priced UniFi estimate, the contact and join-our-team forms, an MCP server (tools mirror these\nendpoints 1:1) and staff-only keyed endpoints.\nStart at GET /agent/v0/site: every public endpoint with its URL, methods, auth and measured size.\nConventions: token-lean lists by default (?detail=full for every field, ?fields=a,b to pick fields,\n?page=&limit= to paginate); GETs are cacheable (s-maxage=300, ETag / If-None-Match → 304); every error is\n{code, message, field, fix}; unknown paths return 404 not_found, unsupported methods 405 method_not_allowed.\n\n\nPublic spec: agent read, quote and contact endpoints only. Staff and partner endpoints are documented for authenticated callers at /api/agent/v0/openapi-internal.",
    "contact": {
      "name": "YesTechie",
      "email": "support@yestechie.com",
      "url": "https://yestechie.com"
    }
  },
  "servers": [
    {
      "url": "https://yestechie.com/api",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "llms.txt (agent guide)",
    "url": "https://yestechie.com/llms.txt"
  },
  "tags": [
    {
      "name": "Discovery"
    },
    {
      "name": "Content",
      "description": "Public, 60 requests/min per client IP, cacheable."
    },
    {
      "name": "Pricing",
      "description": "Public list prices from the BMS catalog (USD, pre-tax, before scope)."
    },
    {
      "name": "Estimate",
      "description": "Public estimates. Without a key 30 per day per client IP (America/Los_Angeles day) with bursts of 5 then 1 per 20 s, shared by GET quote / unifi_estimate / estimate_quote; per-network and global caps; X-RateLimit-* headers, 429 + Retry-After + {code, message, retryAfter}."
    },
    {
      "name": "GET-only (chat apps)",
      "description": "If you can't POST, use these GET links. Same engine, limits and email path as the POST endpoints; every response carries X-Robots-Tag noindex."
    },
    {
      "name": "Forms",
      "description": "Public form submissions (same as the website forms). 3 per client IP and 2 per email per day, 24 h dedupe, Idempotency-Key."
    },
    {
      "name": "MCP"
    }
  ],
  "paths": {
    "/agent/v0/site": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "Index of every public agent endpoint (URL, methods, auth, default response bytes)",
        "security": [],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/agent/v0/about": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "The /about page (company, founders, office, contact, credentials)",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          }
        }
      }
    },
    "/agent/v0/team": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "Published founders (lean list)",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          }
        }
      }
    },
    "/agent/v0/team/{idOrSlug}": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "One founder page in full",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/idOrSlug"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/contact-info": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "The /contact page (office address, phone, email)",
        "security": [],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          }
        }
      }
    },
    "/agent/v0/pages": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "Every published site page with its canonical URL and agent endpoint",
        "security": [],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          }
        }
      }
    },
    "/agent/v0/pages/{slug}": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "One published page as plain text",
        "security": [],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/clients": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "The /clients page (client logos, map count, deployments by state and city)",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          }
        }
      }
    },
    "/agent/v0/coverage": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "Where YesTechie works (HQ, deployments by state, city with ?detail=full, services, 100-mile travel policy)",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/detail"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          }
        }
      }
    },
    "/agent/v0/services": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "Published services (plain text; ?format=lexical returns the raw rich-text body)",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/services/{idOrSlug}": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "One published service in full",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/idOrSlug"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/projects": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "Published projects",
        "description": "Completed YesTechie projects: real case studies from installs. One item returns title, summary, tags, the full write-up (typically client background and challenge, solution and equipment, results), headings, photos and videos. Use for real deployment examples / case studies. Same as MCP list_projects.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/projects/{idOrSlug}": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "One published project in full",
        "description": "Completed YesTechie projects: real case studies from installs. One item returns title, summary, tags, the full write-up (typically client background and challenge, solution and equipment, results), headings, photos and videos. Use for real deployment examples / case studies. Same as MCP list_projects.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/idOrSlug"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/articles": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "Published articles",
        "description": "Practical configuration guides: UniFi network, Wi-Fi, cameras (Protect) and access control (Access, Identity, intercom), plus cabling and home networking. One item returns title, summary, author, tags, the step-by-step body, headings and source links. Use for how-to set up, configure or choose questions. Same as MCP list_articles.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/articles/{idOrSlug}": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "One published article in full",
        "description": "Practical configuration guides: UniFi network, Wi-Fi, cameras (Protect) and access control (Access, Identity, intercom), plus cabling and home networking. One item returns title, summary, author, tags, the step-by-step body, headings and source links. Use for how-to set up, configure or choose questions. Same as MCP list_articles.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/idOrSlug"
          },
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/pricing/equipment": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "summary": "Equipment list prices (MSRP) with name, SKU, category and image URL",
        "security": [],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/limit"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/pricing/labor": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "summary": "Labor list prices (standard rates) with name, category and unit",
        "security": [],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/limit"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/unifi_estimate/questions": {
      "get": {
        "tags": [
          "Estimate"
        ],
        "summary": "UniFi estimate question set v1 (fill-in template, required ids, rules, JSON Schema)",
        "description": "Call 1 of 2. ?fields=template,itemTemplates,required is the smallest view; ?fields=requestSchema is the JSON Schema the POST validates against; ?detail=full adds rules and sources.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/detail"
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "304": {
            "$ref": "#/components/responses/NotModified"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/agent/v0/unifi_estimate": {
      "post": {
        "tags": [
          "Estimate"
        ],
        "summary": "Priced UniFi estimate from the filled question set (call 2 of 2)",
        "description": "Body = the question set template with answers (null = not answered, \"unknown\" allowed except site.state / site.zip), max 16 KB.\nValidated against GET …/questions?fields=requestSchema unchanged; invalid bodies get 400 needs_input with needsInput[] and are not counted against the limit.\nPrices come only from the BMS catalog; an unpriced line is listed in missingSkus / missingLabor with pricingComplete false (never a silent $0).\nTravel: a site more than 100 miles straight-line from HQ (Panorama City, CA), in any state, pays the catalog Travel Expenses Fee × technicians × install days. Send site.lat / site.lng; without them a street address with a house number (site.address + city/zip) is geocoded server-side (cached; travel.coordSource geocode); if neither gives coordinates there is no travel line, travelNeedsReview is true and an open question asks for the location.\nTax (no live tax-authority call; equipment incl. sourcing & shipping is the only taxable base): out of state → 0 with taxNeedsReview; California → the Finance-reviewed CA city table (CDTFA rates, date in taxTableAsOf; an exact city hit needs no review); any other CA city → the HQ default rate with jurisdiction \"CA (unresolved, HQ default rate)\" and taxNeedsReview. Final tax is set on the Service Order.\nSourcing & shipping: one equipment line \"Equipment Sourcing / Shipping / Handling\" = 10% of pre-tax equipment, rounded to the cent, taxed at the site rate with equipment (the Service Order line, same formula; totals.shipping).\nDown payment (standard, Net 0): equipment + sourcing & shipping + tax + 10% × (labor + travel). Caller taxRate / clientInCalifornia are ignored. Nothing is saved.\n",
        "security": [
          {},
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "detail",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "full"
              ]
            },
            "description": "full adds reasoning, timelineNotes and crewNotes"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnifiEstimateRequest"
              },
              "example": {
                "site": {
                  "state": "CA",
                  "zip": "90071",
                  "city": "Los Angeles",
                  "siteType": "office",
                  "lat": 34.0527,
                  "lng": -118.2522
                },
                "infra": {
                  "installType": "new"
                },
                "wifi": {
                  "wanted": true,
                  "indoorApCount": 4
                },
                "cameras": {
                  "count": 8
                },
                "access": {
                  "doorCount": 2
                },
                "doorDefaults": {
                  "lockAlreadyElectrified": "no"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimate",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnifiEstimate"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/NeedsInput"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "405": {
            "$ref": "#/components/responses/Error"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          },
          "415": {
            "$ref": "#/components/responses/Error"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/agent/v0/estimate_quote": {
      "post": {
        "tags": [
          "Estimate"
        ],
        "summary": "DEPRECATED: legacy dry price estimate from engine requirements (compatibility alias; use unifi_estimate)",
        "deprecated": true,
        "description": "Deprecated compatibility alias. Its prices come from the same unifi_estimate pricing code (engine, catalog pricing, tax, travel, shipping), so the same job gives the same total; new clients should use POST /agent/v0/unifi_estimate (or GET /agent/v0/quote). Without a key only createEstimate false / omitted is allowed (dry, nothing saved). Same travel and tax rules as unifi_estimate (no live tax call; address-only sites are geocoded for travel); caller taxRate is ignored. createEstimate / includePdfUrl need a staff key.",
        "security": [
          {},
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "requirements"
                ],
                "properties": {
                  "siteAddress": {
                    "type": "string"
                  },
                  "site": {
                    "type": "object",
                    "properties": {
                      "street": {
                        "type": "string"
                      },
                      "city": {
                        "type": "string"
                      },
                      "state": {
                        "type": "string"
                      },
                      "zip": {
                        "type": "string"
                      },
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    }
                  },
                  "requirements": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Engine requirements, e.g. apCount, cameraCount, doorCount, doors[], doorDefaults, gates[], dataDropCount."
                  },
                  "lockAlreadyElectrified": {
                    "type": "string",
                    "enum": [
                      "yes",
                      "no",
                      "unknown"
                    ],
                    "description": "Same answer as GET /agent/v0/quote lockAlreadyElectrified and unifi_estimate doorDefaults.lockAlreadyElectrified; applied as requirements.doorDefaults.lockAlreadyElectrified. 400 if it conflicts with that nested value."
                  },
                  "createEstimate": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "400": {
            "$ref": "#/components/responses/Json"
          },
          "401": {
            "$ref": "#/components/responses/Json"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/agent/v0/quote": {
      "get": {
        "tags": [
          "GET-only (chat apps)"
        ],
        "summary": "If you can't POST: the unifi_estimate quote as a GET link",
        "description": "Engine requirements as query parameters: every accepted parameter and alias is listed below (siteAddress, apCount, cameraCount, doorCount, dataDropCount, needsRack, lockAlreadyElectrified, …). Prices with the unifi_estimate engine (the same code as POST /agent/v0/unifi_estimate and MCP unifi_estimate: the same job gives the same total), read-only: never an estimate, Service Order, PDF, QBO record or email; the only write is the shared rate-limit counter.\nReturns lines, totals, tax / travel decisions, a signed quoteRef (HMAC, valid 24 h), leadUrlTemplate and requestQuoteUrl (the public contact page).\nJSON by default; ?format=md or ?format=html, or Accept: text/html / text/markdown, for a readable view.\nLimits: the same 30 per day per client IP (bursts of 5, then 1 per 20 s) as the POST quote lanes, one shared count; the same normalized parameters again from the same client are served from cache (X-Quote-Cache: hit) and not counted. Invalid parameters are rejected before counting (400 {code, message, field, fix}).\nHEAD, prefetch requests and link previews / crawlers get a harmless page (header X-Preview-Ignored: true): the engine does not run and nothing is counted.\nExample: https://yestechie.com/api/agent/v0/quote?address=350+S+Grand+Ave,+Los+Angeles,+CA+90071&aps=4&cameras=8&doors=2\n",
        "security": [],
        "parameters": [
          {
            "name": "siteAddress",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Site address, e.g. \"350 S Grand Ave, Los Angeles, CA 90071\" (state and city are read from it for tax). Alias: address."
          },
          {
            "name": "address",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Alias of siteAddress."
          },
          {
            "name": "street",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "description": "Street line (optional)."
          },
          {
            "name": "city",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "description": "City (tax)."
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 2,
              "pattern": "^[A-Za-z]{2}$"
            },
            "description": "2-letter state, e.g. CA (tax)."
          },
          {
            "name": "zip",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 5,
              "pattern": "^\\d{5}$"
            },
            "description": "5-digit ZIP."
          },
          {
            "name": "lat",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            },
            "description": "Site latitude (travel: more than 100 miles from HQ adds the travel line)."
          },
          {
            "name": "lng",
            "in": "query",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            },
            "description": "Site longitude."
          },
          {
            "name": "apCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Indoor Wi-Fi access points. Alias: aps."
          },
          {
            "name": "aps",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of apCount."
          },
          {
            "name": "outdoorAps",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Outdoor access points. Alias: outdoor_aps."
          },
          {
            "name": "outdoor_aps",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of outdoorAps."
          },
          {
            "name": "wifiGeneration",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "wifi6",
                "wifi7"
              ]
            },
            "description": "wifi6 or wifi7. Alias: wifi."
          },
          {
            "name": "wifi",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "wifi6",
                "wifi7"
              ]
            },
            "description": "Alias of wifiGeneration."
          },
          {
            "name": "apModel",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._+-]*$"
            },
            "description": "Specific AP model (optional)."
          },
          {
            "name": "siteSqft",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5000000
            },
            "description": "Building square feet. Alias: sqft."
          },
          {
            "name": "sqft",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5000000
            },
            "description": "Alias of siteSqft."
          },
          {
            "name": "clientDensity",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "low",
                "medium",
                "high"
              ]
            },
            "description": "Wi-Fi client density. Alias: density."
          },
          {
            "name": "density",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "low",
                "medium",
                "high"
              ]
            },
            "description": "Alias of clientDensity."
          },
          {
            "name": "employeeCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000
            },
            "description": "Employees (handoff training length). Alias: employees."
          },
          {
            "name": "employees",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000
            },
            "description": "Alias of employeeCount."
          },
          {
            "name": "cameraCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Cameras. Alias: cameras."
          },
          {
            "name": "cameras",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of cameraCount."
          },
          {
            "name": "camera4kCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "4K cameras (of cameraCount). Alias: cameras_4k."
          },
          {
            "name": "cameras_4k",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of camera4kCount."
          },
          {
            "name": "cameraModel",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._+-]*$"
            },
            "description": "Specific camera model (optional)."
          },
          {
            "name": "ptzCameraCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "PTZ cameras. Alias: ptz."
          },
          {
            "name": "ptz",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of ptzCameraCount."
          },
          {
            "name": "lprCameraCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "License-plate cameras. Alias: lpr."
          },
          {
            "name": "lpr",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of lprCameraCount."
          },
          {
            "name": "storageRetentionDays",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "description": "Video retention days. Alias: retention_days."
          },
          {
            "name": "retention_days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "description": "Alias of storageRetentionDays."
          },
          {
            "name": "doorCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Access-controlled doors. Alias: doors."
          },
          {
            "name": "doors",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of doorCount."
          },
          {
            "name": "readerCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Door readers. Alias: readers."
          },
          {
            "name": "readers",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of readerCount."
          },
          {
            "name": "readerModel",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._+-]*$"
            },
            "description": "Specific reader model (optional)."
          },
          {
            "name": "readerPinRequired",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Readers need PIN entry. Alias: pin."
          },
          {
            "name": "pin",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Alias of readerPinRequired."
          },
          {
            "name": "lockCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "New electric locks. Alias: locks."
          },
          {
            "name": "locks",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of lockCount."
          },
          {
            "name": "existingLockRewireCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Existing locks to rewire."
          },
          {
            "name": "existingExitDeviceRewireCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Existing exit devices to rewire."
          },
          {
            "name": "exitDeviceCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Exit devices."
          },
          {
            "name": "exitButtonCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Exit buttons. Alias: exit_buttons."
          },
          {
            "name": "exit_buttons",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of exitButtonCount."
          },
          {
            "name": "exitMotionCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Exit motion sensors. Alias: exit_motion."
          },
          {
            "name": "exit_motion",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of exitMotionCount."
          },
          {
            "name": "lockTypeStrike",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Strike locks."
          },
          {
            "name": "lockTypeMaglock",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Maglocks."
          },
          {
            "name": "lockTypeMortise",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Mortise locks."
          },
          {
            "name": "lockTypeLever",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Lever locks."
          },
          {
            "name": "gateCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Gates. Alias: gates."
          },
          {
            "name": "gates",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of gateCount."
          },
          {
            "name": "gateHubRequested",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Dedicated gate hub requested."
          },
          {
            "name": "gateLvCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Gate low-voltage runs."
          },
          {
            "name": "intercomCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Intercoms. Alias: intercoms."
          },
          {
            "name": "intercoms",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of intercomCount."
          },
          {
            "name": "intercomCountEth",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Ethernet intercoms."
          },
          {
            "name": "intercomAsReader",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Intercoms used as readers."
          },
          {
            "name": "viewerCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Protect viewers. Alias: viewers."
          },
          {
            "name": "viewers",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of viewerCount."
          },
          {
            "name": "aiHornCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "AI horns. Alias: horns."
          },
          {
            "name": "horns",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of aiHornCount."
          },
          {
            "name": "needsRack",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "A rack is needed. Alias: rack."
          },
          {
            "name": "rack",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Alias of needsRack."
          },
          {
            "name": "ethernetRuns",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5000
            },
            "description": "Explicit ethernet run count."
          },
          {
            "name": "lowVoltageRuns",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 5000
            },
            "description": "Explicit low-voltage run count."
          },
          {
            "name": "dataDropCount",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Data drops (ethernet). Alias: drops."
          },
          {
            "name": "drops",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Alias of dataDropCount."
          },
          {
            "name": "farthestDeviceFt",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000
            },
            "description": "Farthest device run, feet."
          },
          {
            "name": "uplinkFt",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 100000
            },
            "description": "Uplink distance, feet."
          },
          {
            "name": "lockHardwareBy",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "yestechie",
                "others"
              ]
            },
            "description": "Who supplies lock hardware."
          },
          {
            "name": "lockAlreadyElectrified",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no",
                "unknown"
              ]
            },
            "description": "Applies to every door (unifi_estimate doorDefaults.lockAlreadyElectrified): no = new electric strike + lock install per door; yes = existing electrified lock; unknown = the question set rule. Alias: locks_electrified."
          },
          {
            "name": "locks_electrified",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no",
                "unknown"
              ]
            },
            "description": "Alias of lockAlreadyElectrified."
          },
          {
            "name": "preferredConsole",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._+-]*$"
            },
            "description": "Preferred console model (optional)."
          },
          {
            "name": "preferredSwitch",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40,
              "pattern": "^[A-Za-z0-9][A-Za-z0-9 ._+-]*$"
            },
            "description": "Preferred switch model (optional)."
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "md",
                "html"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Estimate (JSON, Markdown or HTML), or the harmless preview page",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/XRateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetQuote"
                }
              },
              "text/html": {
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "405": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "head": {
        "tags": [
          "GET-only (chat apps)"
        ],
        "summary": "Harmless (no engine run, not counted)",
        "security": [],
        "responses": {
          "200": {
            "description": "Headers only"
          }
        }
      }
    },
    "/agent/v0/lead": {
      "get": {
        "tags": [
          "GET-only (chat apps)"
        ],
        "summary": "If you can't POST: ask YesTechie to follow up on a GET quote",
        "description": "Open only when the person asked YesTechie to contact them: it creates one lead and sends the /contact form's two emails (a confirmation to the email given and a notice to YesTechie). Lead source ai-chat.\nRequires a valid, unexpired quoteRef from GET /agent/v0/quote (forged, altered or expired refs get 400 quote_ref_invalid / quote_ref_expired). email or phone is required (both allowed). address, if given, must match the quoted address (400 address_mismatch); note has no links.\nIdempotent: the same quoteRef + contact returns the same leadRef and sends nothing again (Idempotency-Replayed: true). A second quote for the same email / phone within 24 h is added to the open lead (no new email).\nLimits: 3 per client IP, 2 per email / phone and 2 per quoteRef per America/Los_Angeles day, global daily breaker; 429 + Retry-After.\nHEAD, prefetch requests (Purpose / Sec-Purpose: prefetch) and link unfurlers / crawlers (Slackbot, facebookexternalhit, Twitterbot, Discordbot, TelegramBot, WhatsApp, LinkedInBot, Applebot, Googlebot, bingbot, link previews, …) get a harmless page: nothing is created or sent.\nExample: https://yestechie.com/api/agent/v0/lead?quoteRef=q1.…&name=Jane+Doe&email=jane@example.com&note=Prefer+mornings\n",
        "security": [],
        "parameters": [
          {
            "name": "quoteRef",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "name",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Full name (no digits)"
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 254
            },
            "description": "email or phone is required"
          },
          {
            "name": "phone",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 40
            },
            "description": "7 to 15 digits"
          },
          {
            "name": "address",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "Must match the quoted address; omit to use it"
          },
          {
            "name": "note",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 500
            },
            "description": "No links"
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "md",
                "html"
              ],
              "default": "json"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Already received (replay) or added to an open request, or the harmless preview page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetLead"
                }
              }
            }
          },
          "201": {
            "description": "Received (lead created, emails sent)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetLead"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "405": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "head": {
        "tags": [
          "GET-only (chat apps)"
        ],
        "summary": "Harmless (nothing created or sent)",
        "security": [],
        "responses": {
          "200": {
            "description": "Headers only"
          }
        }
      }
    },
    "/agent/v0/contact": {
      "post": {
        "tags": [
          "Forms"
        ],
        "summary": "Send YesTechie a message, exactly as the /contact form does (one inquiry + a confirmation email)",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FormSubmission"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Duplicate or replay (nothing sent again)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormResult"
                }
              }
            }
          },
          "201": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "413": {
            "$ref": "#/components/responses/Error"
          },
          "415": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/agent/v0/join-our-team": {
      "get": {
        "tags": [
          "Content"
        ],
        "summary": "The /join-our-team page (hiring text, office, phone, email)",
        "security": [],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          }
        }
      },
      "post": {
        "tags": [
          "Forms"
        ],
        "summary": "Apply to join the team, exactly as the /join-our-team form does",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FormSubmission"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Duplicate or replay",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormResult"
                }
              }
            }
          },
          "201": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FormResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/mcp": {
      "post": {
        "tags": [
          "MCP"
        ],
        "summary": "MCP server (Streamable HTTP, JSON-RPC 2.0, stateless JSON responses)",
        "description": "Methods: initialize, ping, tools/list, tools/call. Tools mirror the public REST endpoints 1:1 and share their rate limits: get_site_info, get_about, get_team, get_contact_info, get_join_our_team, list_pages, get_clients, get_coverage, list_services, list_projects, list_articles, get_pricing, get_unifi_questions, unifi_estimate, submit_contact, submit_join_application.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method"
                ],
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "enum": [
                      "2.0"
                    ]
                  },
                  "id": {
                    "oneOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "integer"
                      }
                    ]
                  },
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Json"
          },
          "202": {
            "description": "Notification accepted (no body)"
          },
          "400": {
            "$ref": "#/components/responses/Json"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Staff API key, issued by YesTechie out of band. Agents do not need one for any public endpoint; do not mint or store keys."
      },
      "PayloadApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Payload REST form of the same staff key: Authorization: users API-Key <key>. Used by /invoices/{id}/paid."
      }
    },
    "parameters": {
      "page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        },
        "description": "max 25 with detail=full"
      },
      "fields": {
        "name": "fields",
        "in": "query",
        "schema": {
          "type": "string"
        },
        "description": "Comma-separated field names to return"
      },
      "detail": {
        "name": "detail",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "full"
          ]
        }
      },
      "format": {
        "name": "format",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "lexical"
          ]
        },
        "description": "Raw rich-text body (back-compat)"
      },
      "idOrSlug": {
        "name": "idOrSlug",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 255
        },
        "description": "Same key + same body within 24 h returns the original response and sends nothing again"
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "schema": {
          "type": "integer"
        },
        "description": "Per-IP daily limit"
      },
      "XRateLimitRemaining": {
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitReset": {
        "schema": {
          "type": "integer"
        },
        "description": "Unix seconds of the next 00:00 America/Los_Angeles"
      },
      "RetryAfter": {
        "schema": {
          "type": "integer"
        },
        "description": "Seconds to wait"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message",
          "field",
          "fix"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "field": {
            "type": "string",
            "nullable": true
          },
          "fix": {
            "type": "string"
          }
        }
      },
      "NeedsInput": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "needsInput": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        ]
      },
      "FormSubmission": {
        "type": "object",
        "required": [
          "fullName",
          "email",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "fullName": {
            "type": "string",
            "maxLength": 200
          },
          "organization": {
            "type": "string",
            "maxLength": 200
          },
          "email": {
            "type": "string",
            "maxLength": 254
          },
          "phone": {
            "type": "string",
            "maxLength": 40
          },
          "message": {
            "type": "string",
            "maxLength": 5000
          },
          "website": {
            "type": "string",
            "maxLength": 500,
            "description": "Spam trap; omit it"
          },
          "quoteRef": {
            "type": "string",
            "description": "Contact only (join-our-team rejects it): the quoteRef from GET /agent/v0/quote or POST /agent/v0/unifi_estimate, valid 24 h. Verified (400 quote_ref_invalid / quote_ref_expired) and the quote summary is attached to the request."
          }
        }
      },
      "FormResult": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "type": {
            "type": "string"
          },
          "ref": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "deduplicated": {
            "type": "boolean"
          },
          "nextSteps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "UnifiEstimateRequest": {
        "type": "object",
        "description": "Question set v1 answers; the authoritative JSON Schema is GET /agent/v0/unifi_estimate/questions?fields=requestSchema.",
        "required": [
          "site",
          "infra"
        ],
        "properties": {
          "site": {
            "type": "object",
            "required": [
              "state",
              "zip",
              "siteType"
            ],
            "properties": {
              "state": {
                "type": "string",
                "pattern": "^[A-Z]{2}$"
              },
              "zip": {
                "type": "string",
                "pattern": "^[0-9]{5}$"
              },
              "city": {
                "type": "string",
                "nullable": true
              },
              "address": {
                "type": "string",
                "nullable": true
              },
              "lat": {
                "type": "number",
                "nullable": true
              },
              "lng": {
                "type": "number",
                "nullable": true
              },
              "siteType": {
                "type": "string"
              },
              "buildingSqft": {
                "type": "integer",
                "nullable": true
              },
              "floors": {
                "type": "integer",
                "nullable": true
              }
            }
          },
          "infra": {
            "type": "object",
            "required": [
              "installType"
            ],
            "properties": {
              "installType": {
                "type": "string",
                "enum": [
                  "new",
                  "existing",
                  "unknown"
                ]
              }
            }
          },
          "wifi": {
            "type": "object"
          },
          "cameras": {
            "type": "object"
          },
          "access": {
            "type": "object"
          },
          "doors": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "object"
            }
          },
          "doorDefaults": {
            "type": "object",
            "nullable": true
          },
          "gates": {
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "object"
            }
          },
          "dataDropCount": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "Line": {
        "type": "object",
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "equipment",
              "labor",
              "other"
            ]
          },
          "model": {
            "type": "string"
          },
          "laborCode": {
            "type": "string"
          },
          "laborItemId": {
            "type": "integer"
          },
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "unitPrice": {
            "type": "number"
          },
          "amount": {
            "type": "number"
          },
          "taxable": {
            "type": "boolean"
          },
          "priced": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Present only on an unpriced line"
          }
        }
      },
      "UnifiEstimate": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "unifi_estimate"
            ]
          },
          "quoteVersion": {
            "type": "string"
          },
          "questionSet": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "estimate"
            ]
          },
          "disclaimer": {
            "type": "string"
          },
          "priceAsOf": {
            "type": "string",
            "format": "date-time"
          },
          "taxTableAsOf": {
            "type": "string",
            "nullable": true
          },
          "pricingComplete": {
            "type": "boolean"
          },
          "missingSkus": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "missingLabor": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "equipment": {
                "type": "number"
              },
              "labor": {
                "type": "number"
              },
              "travel": {
                "type": "number"
              },
              "shipping": {
                "type": "number"
              },
              "tax": {
                "type": "number"
              },
              "total": {
                "type": "number"
              }
            }
          },
          "downPayment": {
            "type": "object",
            "properties": {
              "mode": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "finalPayment": {
                "type": "number"
              },
              "terms": {
                "type": "string"
              },
              "formula": {
                "type": "string"
              },
              "isEstimate": {
                "type": "boolean"
              },
              "needsReview": {
                "type": "boolean"
              }
            }
          },
          "tax": {
            "type": "object",
            "properties": {
              "taxRate": {
                "type": "number"
              },
              "taxableBase": {
                "type": "number"
              },
              "taxAmount": {
                "type": "number"
              },
              "jurisdiction": {
                "type": "string"
              },
              "taxBasis": {
                "type": "string",
                "enum": [
                  "saved_site_rate",
                  "ca_city_table",
                  "hq_default",
                  "out_of_state"
                ]
              },
              "taxNeedsReview": {
                "type": "boolean"
              },
              "taxTableAsOf": {
                "type": "string",
                "nullable": true
              },
              "isEstimate": {
                "type": "boolean"
              }
            }
          },
          "travel": {
            "type": "object",
            "properties": {
              "applies": {
                "type": "boolean"
              },
              "distanceMiles": {
                "type": "number",
                "nullable": true
              },
              "hqLat": {
                "type": "number"
              },
              "hqLng": {
                "type": "number"
              },
              "siteLat": {
                "type": "number",
                "nullable": true
              },
              "siteLng": {
                "type": "number",
                "nullable": true
              },
              "coordSource": {
                "type": "string",
                "enum": [
                  "request",
                  "saved_site",
                  "geocode"
                ],
                "nullable": true
              },
              "techs": {
                "type": "integer"
              },
              "installDays": {
                "type": "integer"
              },
              "rate": {
                "type": "number",
                "nullable": true
              },
              "amount": {
                "type": "number"
              }
            }
          },
          "travelNeedsReview": {
            "type": "boolean"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Line"
            }
          },
          "timeline": {
            "type": "object"
          },
          "crew": {
            "type": "object",
            "properties": {
              "technicianCount": {
                "type": "integer"
              }
            }
          },
          "derived": {
            "type": "object"
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "openQuestions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "quoteRef": {
            "type": "string",
            "nullable": true,
            "description": "Signed quote reference (HMAC, valid 24 h; null if not configured). Pass it to POST /agent/v0/contact (or MCP submit_contact) or GET /agent/v0/lead when the person asks YesTechie to follow up."
          },
          "quoteRefExpiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "requestQuoteUrl": {
            "type": "string"
          },
          "leadHandoff": {
            "type": "object",
            "description": "Ready-to-fill POST contact body, MCP submit_contact arguments and GET lead link carrying quoteRef."
          }
        }
      },
      "GetQuote": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "quote"
            ]
          },
          "engine": {
            "type": "string",
            "enum": [
              "unifi_estimate"
            ],
            "description": "Priced by the unifi_estimate engine (same total as POST unifi_estimate for the same job)"
          },
          "status": {
            "type": "string",
            "enum": [
              "estimate"
            ]
          },
          "disclaimer": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "description": "Normalized parameters (full estimate_quote names)"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Line"
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "labor": {
                "type": "number"
              },
              "equipment": {
                "type": "number"
              },
              "tax": {
                "type": "number"
              },
              "total": {
                "type": "number"
              }
            }
          },
          "pricingComplete": {
            "type": "boolean"
          },
          "quoteRef": {
            "type": "string",
            "nullable": true,
            "description": "Signed (HMAC), valid 24 h; pass it to GET /agent/v0/lead"
          },
          "quoteRefExpiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "leadUrlTemplate": {
            "type": "string",
            "nullable": true
          },
          "requestQuoteUrl": {
            "type": "string"
          },
          "openQuestions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "nextSteps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "GetLead": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "type": {
            "type": "string",
            "enum": [
              "ai_chat_lead"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "merged",
              "already_received"
            ]
          },
          "leadRef": {
            "type": "string"
          },
          "quote": {
            "type": "string"
          },
          "emailed": {
            "type": "boolean"
          },
          "nextSteps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "contactPage": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "Json": {
        "description": "JSON",
        "content": {
          "application/json": {
            "schema": {
              "type": "object"
            }
          }
        }
      },
      "NotModified": {
        "description": "Not modified (If-None-Match matched the ETag)"
      },
      "Error": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NeedsInput": {
        "description": "needs_input / no_scope / invalid_json",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/NeedsInput"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limited",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
