{"openapi":"3.1.0","info":{"title":"Quotrr Public API","version":"1.0.0","description":"Real verified line-item home-services data for agents: public contractor profiles with a legible Quotrr Score, self-quote catalogs, and shared proposal portals. Transactional and price-range routes that need human consent or a verified-agent identity are tagged v2-preview.","contact":{"name":"Quotrr agent operators","email":"agents@quotrr.com"}},"servers":[{"url":"https://api.quotrr.com","description":"Production"}],"tags":[{"name":"public","description":"No auth. Anonymized or contractor-published data only."},{"name":"v2-preview","description":"Planned. Needs a verified-agent identity or a human-consent token (OAuth 2.0 with PKCE); not anonymously available yet."}],"paths":{"/v1/health":{"get":{"tags":["public"],"operationId":"health","summary":"Liveness check","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}},"required":["ok"]}}}}}}},"/v1/biz/{slug}":{"get":{"tags":["public"],"operationId":"getContractorProfile","summary":"Public contractor profile, Quotrr Score, and verified Props","description":"Proof-first public profile by slug: verified outcomes, the legible Quotrr Score, and non-deletable verified Props. This is the contractor-reputation surface an agent reads (the strategy's check_contractor_reputation maps here).","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Profile","content":{"application/json":{"schema":{"type":"object","properties":{"profile":{"type":"object"}}}}}},"404":{"description":"Not found"}}}},"/v1/quote/{slug}":{"get":{"tags":["public"],"operationId":"getQuoteCatalog","summary":"Self-quote catalog for a contractor","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Catalog","content":{"application/json":{"schema":{"type":"object","properties":{"catalog":{"type":"object"}}}}}},"404":{"description":"Not found"}}},"post":{"tags":["public"],"operationId":"submitQuoteRequest","summary":"Submit a homeowner self-quote request (lands as a lead)","description":"Open today for homeowner self-serve. Agent submission on a homeowner's behalf moves behind a human-consent token in v2 (see the MCP request_quote tool).","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"},"note":{"type":"string"},"itemNames":{"type":"array","items":{"type":"string"}}},"required":["name"]}}}},"responses":{"201":{"description":"Created lead","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"jobId":{"type":"string"}}}}}},"400":{"description":"Bad request"},"404":{"description":"Not found"}}}},"/v1/quote/{slug}/configure":{"post":{"tags":["public"],"operationId":"configureQuote","summary":"Get a real configured price for a project (the agent moat)","description":"The agent-callable configurator. POST a project configuration; receive a real configured total + labeled line items + a direct book-consult URL the agent hands to the user. No lead created. No auth. Designed so an AI agent on behalf of a homeowner can answer \"pool + spa + BBQ = $X\" in one call with a real number, not a \"contact us\" form. Resolves the slug as either the full D1 tenant slug OR the short siteId from the design-studio URL (e.g. \"phenomenal\").","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["project"],"properties":{"project":{"type":"string","enum":["new-pool","remodel","full-backyard","outdoor-living"]},"pool":{"type":"object","properties":{"perimeter_ft":{"type":"number","minimum":1,"maximum":400},"water_sqft":{"type":"number","minimum":1},"finish":{"type":"string","enum":["standard-plaster","quartz","mini-pebble","micro-pebble","polished-marble"]}}},"features":{"type":"array","items":{"type":"string","enum":["spa","ledge","waterfall","bubblers","leds","heater","cover","saltwater","firepit"]}},"decking":{"type":"object","properties":{"sqft":{"type":"number","minimum":0},"finish":{"type":"string","enum":["broom","salt","stamped","exposed-aggregate","acid-stain"]}}},"turf_sqft":{"type":"number","minimum":0},"landscape_sqft":{"type":"number","minimum":0},"structures":{"type":"array","items":{"type":"object","required":["id"],"properties":{"id":{"type":"string","enum":["kitchen","bbq","firepit","pergola","retwall","veneer","solar","lights","speakers","pool_heater"]},"tier":{"type":"string"},"count":{"type":"number"},"width_ft":{"type":"number"},"depth_ft":{"type":"number"}}}},"remodelOptions":{"type":"array","items":{"type":"string","enum":["newtile","newcoping","newequip"]}}}},"examples":{"pool-spa-bbq":{"summary":"Pool + spa + BBQ 8ft (Nestor's canonical example)","value":{"project":"new-pool","pool":{"perimeter_ft":80,"finish":"mini-pebble"},"features":["spa","saltwater"],"structures":[{"id":"bbq","tier":"8"}]}},"resurface-with-pebble":{"summary":"Resurface with Signature Mini Pebble","value":{"project":"remodel","pool":{"perimeter_ft":80,"finish":"mini-pebble"},"remodelOptions":["newtile","newcoping"]}},"outdoor-living-only":{"summary":"Outdoor kitchen + BBQ + firepit, no pool","value":{"project":"outdoor-living","structures":[{"id":"kitchen","tier":"mid"},{"id":"bbq","tier":"8"},{"id":"firepit","tier":"gas"}]}},"full-backyard":{"summary":"Full backyard: pool + spa + outdoor kitchen + pergola + firepit","value":{"project":"full-backyard","pool":{"perimeter_ft":92,"finish":"quartz"},"features":["spa","leds","saltwater","firepit"],"decking":{"sqft":800,"finish":"stamped"},"structures":[{"id":"kitchen","tier":"premium"},{"id":"firepit","tier":"gas"},{"id":"pergola","width_ft":14,"depth_ft":12}]}}}}}},"responses":{"200":{"description":"Real configured total + line items + book_consult_url","content":{"application/json":{"schema":{"type":"object","required":["total","currency","line_items","config","book_consult_url","disclaimer","source","brand","request_hash"],"properties":{"total":{"type":"number","description":"Configured total in USD"},"currency":{"type":"string","enum":["USD"]},"line_items":{"type":"array","items":{"type":"object","properties":{"label":{"type":"string"},"amount":{"type":"number"},"category":{"type":"string","enum":["pool","feature","finish","decking","turf","landscape","structure","remodel","resurface","prepwork","tile"]}}}},"config":{"type":"object"},"book_consult_url":{"type":"string","format":"uri"},"disclaimer":{"type":"string"},"source":{"type":"string"},"brand":{"type":"object","description":"Contractor brand block so an agent can quote the business by name in its answer to the user.","properties":{"name":{"type":"string"},"tagline":{"type":"string"},"trade":{"type":"string"},"service_area":{"type":"array","items":{"type":"string"}},"license":{"type":"string"},"phone":{"type":"string"},"website":{"type":"string","format":"uri"}}},"request_hash":{"type":"string"}}}}}},"400":{"description":"Validation error (invalid JSON, missing project, etc.)"},"404":{"description":"Unknown contractor slug"},"429":{"description":"Rate limit (anonymous 30 burst, refills slowly)"}}}},"/v1/p/{token}":{"get":{"tags":["public"],"operationId":"getSharedProposal","summary":"Shared proposal as JSON, by token","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Proposal"},"404":{"description":"Not found"}}}},"/c/{slug}/badges":{"get":{"tags":["public"],"operationId":"getContractorBadgePage","summary":"Public earned-badge page for a contractor (server-rendered HTML)","description":"Crawlable HTML page of a contractor's earned milestone badges, by slug. Server-rendered with no client JavaScript for the content, and carries schema.org JSON-LD: a LocalBusiness with a hasCredential array of EducationalOccupationalCredential entries an agent can cite. Renders an honest empty state when no badges are earned yet.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Badge page","content":{"text/html":{}}},"404":{"description":"Contractor not found"}}}},"/v1/badges":{"get":{"tags":["v2-preview"],"operationId":"getMyBadges","summary":"The signed-in contractor's twelve badges with progress","description":"Authed and tenant-scoped. Returns all twelve milestone badges in a fixed order, each with earned, earned_at, progress, current, and target. Not anonymously available; the public badge page at /c/{slug}/badges is the open surface.","responses":{"200":{"description":"Badges"},"401":{"description":"Missing or invalid token"}}}},"/v1/year-in-review":{"get":{"tags":["v2-preview"],"operationId":"getYearInReview","summary":"The signed-in contractor's yearly recap","description":"Authed and tenant-scoped. Totals, biggest ticket, busiest month, best customer, longest working streak, Props, badges earned, and a few plain observations for one calendar year. Default year is the most recent year with completed jobs, derived from the data.","parameters":[{"name":"year","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Year in Review"},"400":{"description":"Bad year"},"401":{"description":"Missing or invalid token"}}}},"/v1/price-range":{"get":{"tags":["public"],"operationId":"getPublicPriceRange","summary":"Anonymized local price ranges, no auth (cached, up to 24h stale)","description":"Public p25/p50/p75 ranges by locality and trade. A range is published only when at least three line items back it, so no single contractor's price is exposed. Cached; answers may be up to 24 hours stale. Anonymous-agent policy decided 2026-05-31.","parameters":[{"name":"zip","in":"query","required":true,"schema":{"type":"string"}},{"name":"trade","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ranges","content":{"application/json":{"schema":{"type":"object","properties":{"result":{"type":"object"},"staleAfterHours":{"type":"number"}}}}}},"400":{"description":"zip required"}}}},"/v1/pricing":{"get":{"tags":["v2-preview"],"operationId":"getTenantPricing","summary":"Anonymized local price ranges (p25/p50/p75)","description":"Today this requires an authenticated contractor session and is tenant-scoped. The anonymous, cached agent price-range tier in the Agentic Strategy is not public yet, so treat this as v2-preview.","parameters":[{"name":"zip","in":"query","required":true,"schema":{"type":"string"}},{"name":"trade","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Ranges"},"400":{"description":"zip required"},"401":{"description":"Auth required (today)"}}}},"/v1/contractors":{"get":{"tags":["v2-preview"],"operationId":"findContractors","summary":"Search verified contractors by trade and ZIP (planned)","description":"Not implemented yet. Use /v1/biz/{slug} for a known contractor today. A search index is planned for the verified-agent tier (strategy find_contractors).","parameters":[{"name":"trade","in":"query","required":false,"schema":{"type":"string"}},{"name":"zip","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"501":{"description":"Not implemented (v2-preview)"}}}},"/oauth/authorize":{"get":{"tags":["v2-preview"],"operationId":"oauthAuthorizeView","summary":"View an agent consent request (human session required)","description":"OAuth 2.1 authorization-code with PKCE (S256 only). A signed-in human sees what scoped, time-boxed authority an agent is asking for. POST the same request to grant. Self-gated OFF until OAUTH_ENABLED is true.","parameters":[{"name":"agent_id","in":"query","required":true,"schema":{"type":"string"}},{"name":"scope","in":"query","required":true,"schema":{"type":"string"},"description":"Space-delimited, from the published scope set."},{"name":"code_challenge","in":"query","required":true,"schema":{"type":"string"}},{"name":"code_challenge_method","in":"query","required":true,"schema":{"type":"string","enum":["S256"]}}],"responses":{"200":{"description":"Consent request to confirm"},"400":{"description":"Bad request (plain PKCE rejected, unknown scope)"},"401":{"description":"Human session required"},"404":{"description":"OAuth disabled"}}},"post":{"tags":["v2-preview"],"operationId":"oauthAuthorizeGrant","summary":"Grant consent and mint a single-use authorization code","description":"The signed-in human consents. Returns a single-use authorization code bound to the agent, the scope, and the PKCE challenge, with a short expiry.","responses":{"201":{"description":"Authorization code issued"},"400":{"description":"Bad request"},"401":{"description":"Human session required"}}}},"/oauth/token":{"post":{"tags":["v2-preview"],"operationId":"oauthToken","summary":"Exchange an authorization code plus PKCE verifier for a token","description":"Verifies the PKCE code_verifier against the stored S256 challenge, enforces single use of the code, and returns a Bearer access token bound to the agent, the scope, and an expiry.","responses":{"200":{"description":"Access token issued"},"400":{"description":"invalid_grant or unsupported_grant_type"},"404":{"description":"OAuth disabled"}}}},"/oauth/revoke":{"post":{"tags":["v2-preview"],"operationId":"oauthRevoke","summary":"Revoke an access token (idempotent)","description":"Revokes a token by value or bearer header. Idempotent and non-probing per RFC 7009: always returns revoked true, so a caller cannot enumerate which tokens exist.","responses":{"200":{"description":"Revoked (idempotent)"},"404":{"description":"OAuth disabled"}}}},"/v1/community/feed":{"get":{"tags":["public"],"operationId":"communityFeed","summary":"The public community bulletin feed","description":"Anonymous, paginated bulletin of project showcases, threads, before/after pairs, homeowner requests, and auto-promoted trending Verified Outcomes. No auth. Per-IP rate-limited (200/min) and KV-cached 60s. Contractor posts carry a verified badge.","parameters":[{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["trending","new","local","trades"]}},{"name":"trade","in":"query","required":false,"schema":{"type":"string"}},{"name":"zip","in":"query","required":false,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"A page of community posts"}}}},"/v1/community/posts/{id}":{"get":{"tags":["public"],"operationId":"communityPost","summary":"A single community post with its first comments","description":"Anonymous. Full post plus the first 20 comments. KV-cached 30s.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The post and its comments"},"404":{"description":"Not found"}}}},"/v1/community/posts/{id}/comments":{"get":{"tags":["public"],"operationId":"communityPostComments","summary":"A page of a post's comments","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"A page of comments"}}}},"/v1/community/search":{"get":{"tags":["public"],"operationId":"communitySearch","summary":"Search the community bulletin","description":"Anonymous free-text search with optional trade and zip filters. Per-IP rate-limited (60/min).","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"trade","in":"query","required":false,"schema":{"type":"string"}},{"name":"zip","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Matching posts"}}}},"/v1/community/trending-outcomes":{"get":{"tags":["public"],"operationId":"communityTrendingOutcomes","summary":"Auto-promoted trending Verified Outcomes","description":"Anonymous. Real completed projects that crossed the Props threshold (>=5 Props, >=4.5 average) in the last 30 days, surfaced in the bulletin. KV-cached 5 min.","parameters":[{"name":"trade","in":"query","required":false,"schema":{"type":"string"}},{"name":"zip","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Trending outcome posts"}}}},"/v1/community/users/{slug}/posts":{"get":{"tags":["public"],"operationId":"communityContractorPosts","summary":"A contractor's public community posts","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer"}}],"responses":{"200":{"description":"The contractor's posts"},"404":{"description":"Unknown contractor"}}}},"/v1/community/posts":{"post":{"tags":["v2-preview"],"operationId":"communityCreatePost","summary":"Create a community post (signup required)","description":"Requires a Clerk session (soft signup tier). Both contractors and homeowners may post; a contractor post carries a verified badge. Per-account limit of 10 posts per day.","responses":{"201":{"description":"Post created"},"400":{"description":"Invalid post"},"401":{"description":"Sign in required"},"429":{"description":"Daily post limit reached"}}}}}}