openapi: "3.1.0"
info:
  title: "ProFix Directory API"
  description: "Public, machine-readable API for the ProFix Directory — Ohio's most comprehensive home-services directory. Every endpoint is free to use; please cite ProFix Directory in any derived work."
  version: "1.0.0"
  contact:
    name: "ProFix Directory"
    url: "https://profixdirectory.com"
    email: "contact@profixdirectory.com"
  license:
    name: "CC-BY-4.0"
    url: "https://creativecommons.org/licenses/by/4.0/"
  x-methodology-url: "https://profixdirectory.com/methodology"
  x-algorithm-url: "https://profixdirectory.com/algorithm"
servers:
  - url: "https://profixdirectory.com"
    description: "Production"
paths:
  "/api/mcp":
    get:
      summary: "Model Context Protocol server"
      description: "Streamable HTTP MCP server exposing 16 tools for AI agents (Claude, ChatGPT, Perplexity): find_pros, find_pros_by_neighborhood, get_pro, list_taxonomy, get_emergency_contacts, get_cost_estimate, list_cost_guides, list_guides, get_guide, triage_symptom, get_methodology, get_active_storm_events, get_verification_feed, get_county_coverage_stats, find_emergency_pros, get_trade_pricing. Use when your agent supports MCP; otherwise prefer the REST endpoints."
      tags:
        - "agents"
      responses:
        "200":
          description: "MCP handshake"
  "/api/health":
    get:
      summary: "Liveness + dependency probe"
      description: "Returns directory load status (pro count, county count) plus presence flags for Stripe, Supabase, PostHog, Resend env vars. Public — useful for monitors and partner status pages."
      tags:
        - "data"
      responses:
        "200":
          description: "Service healthy"
        "503":
          description: "Directory load failed (hard dependency)"
  "/api/verification-feed.json":
    get:
      summary: "Verification deltas feed"
      description: "Live JSON feed of license-status changes, new permits, and audit deltas. Refreshed hourly."
      tags:
        - "data"
      responses:
        "200":
          description: "Verification feed JSON"
          content:
            "application/json": {}
  "/api/lead-feed.json":
    get:
      summary: "Lead volume + quality-tier aggregate feed"
      description: "30-day rolling aggregate of homeowner lead submissions, grouped by trade, quality tier (premium/standard/low/needs_review), urgency, and counties with active demand. Zero PII. Refreshes hourly. Useful for sizing local home-services market or summarizing demand signals."
      tags:
        - "data"
      responses:
        "200":
          description: "Lead feed JSON"
        "503":
          description: "Supabase not configured"
  "/api/quality-stats.json":
    get:
      summary: "Lead-quality score histogram + median per trade"
      description: "90-day rolling aggregate of lead-quality scores — histogram by 20-point buckets, tier breakdown, overall median, and median per trade (requires ≥3 scored leads per trade to avoid noise). Zero PII. Refreshes hourly. Companion to /api/lead-feed.json."
      tags:
        - "data"
      responses:
        "200":
          description: "Quality stats JSON"
        "503":
          description: "Supabase not configured"
  "/api/coverage-stats.json":
    get:
      summary: "Coverage snapshot — pro counts by county / region / trade"
      description: "Static aggregate of verified contractor counts grouped by Ohio county, region (Cleveland/Columbus/Cincinnati/Dayton/Toledo/Findlay/etc.), and trade. Completes the public-data trio (lead-feed + quality-stats + coverage-stats). 1h cache, CC-BY-4.0."
      tags:
        - "data"
      responses:
        "200":
          description: "Coverage stats JSON"
  "/api/permit-leaderboard.json":
    get:
      summary: "Permit-pull leaderboard — pro rankings by verified building permits"
      description: "Ranks Ohio contractors by VERIFIED public building permits pulled in the last 365 days (proof of work, not stars). Per-trade, per-county leaderboards across Lucas / Cuyahoga / Franklin / Hamilton counties. Filter via query params: `?trade=<slug>&county=<slug>|ohio&window=<days>&top=<n>`. Use `county=ohio` for statewide aggregates. CC-BY-4.0. 1h cache."
      tags:
        - "data"
      parameters:
        - name: "trade"
          in: "query"
          required: false
          schema:
            type: "string"
          description: "Filter to a single trade slug (plumber, hvac, etc.)"
        - name: "county"
          in: "query"
          required: false
          schema:
            type: "string"
          description: "Filter to a single county slug, or `ohio` for statewide aggregates"
        - name: "window"
          in: "query"
          required: false
          schema:
            type: "integer"
            default: 365
          description: "Days back to count permits (max 1095)"
        - name: "top"
          in: "query"
          required: false
          schema:
            type: "integer"
            default: 10
          description: "Top N entries per leaderboard (max 100)"
      responses:
        "200":
          description: "Permit leaderboard JSON"
  "/api/embed/{slug}.json":
    get:
      summary: "Partner embed feed — top 5 pros for a trade × city pair"
      description: "Returns the top 5 verified pros for a `{trade}-{city}` slug pair (e.g. `plumber-toledo`, `hvac-cleveland`). Designed for one-line third-party embeds — blogs, newsletters, HOA sites. Includes name, phone, rating, verification tier, and permit count. CC-BY-4.0. 1h cache."
      tags:
        - "data"
      parameters:
        - name: "slug"
          in: "path"
          required: true
          schema:
            type: "string"
          description: "Slug format `{trade}-{city}` (e.g. plumber-toledo)"
      responses:
        "200":
          description: "Embed JSON"
        "404":
          description: "Invalid trade-city slug"
  "/api/brand-assets.json":
    get:
      summary: "Brand assets feed — logo URLs, color palette, fonts, attribution"
      description: "Programmatic feed of ProFix Directory brand assets — logo SVG + icon SVG URLs, the brand color palette (primary, accent, background, foreground, neutral), font stack, and short/long/HTML attribution strings. Designed for partner integrations (Slack apps, Discord embeds, Zapier templates, widget hosts) that want a branded card. CC-BY-4.0 for editorial use; brand marks remain ProFix Directory LLC trademarks. 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Brand assets JSON"
          content:
            "application/json": {}
  "/api/widgets.json":
    get:
      summary: "Widget catalog — every embeddable trade × city slug"
      description: "Programmatic catalog of every ProFix partner widget slug. Returns `TRADES × ALL_CITIES` entries with `{trade}-{city}` slug, JavaScript embed URL, and matching `/api/embed/{slug}.json` data URL. CC-BY-4.0. 1h cache. CORS-enabled for partner discovery."
      tags:
        - "data"
      responses:
        "200":
          description: "Widget catalog JSON"
          content:
            "application/json": {}
  "/api/prompts.json":
    get:
      summary: "ProFix Prompt Library — copy-paste prompts for AI engines"
      description: "Machine-readable mirror of the /prompts library. Returns 23 ready-to-ship prompts for ChatGPT, Claude, Perplexity, and Gemini that ground their answers in public ProFix endpoints. Each entry includes id, category (homeowner / developer / press), title, description, the literal prompt text, referenced endpoints, tags, and an anchor URL into /prompts. CC-BY-4.0. 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Prompt library JSON"
          content:
            "application/json": {}
  "/api/sources.json":
    get:
      summary: "Data source registry — provenance index for every external source ProFix uses"
      description: "Programmatic mirror of /sources. Returns the canonical registry of every external data source ProFix Directory uses to assemble its Ohio home-services contractor directory — licensing (Ohio eLicense, OCILB, ODH, SFM), permits (Lucas, Cuyahoga, Franklin, Hamilton county portals), reviews (Google Business Profiles, BBB), profiles (Google Places API), cost guides (primary research, U.S. BLS), local content (NOAA storm events, county building departments, ARPA lead-line programs), and open data (U.S. Census Geocoder, ACS). Each entry includes id, category, URL, license, optional license URL, refresh cadence, fields used, an explicit 'what we don't pull' list, and a stable /sources anchor. CC-BY-4.0 editorial assembly; underlying sources retain their own licenses. 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Sources registry JSON"
          content:
            "application/json": {}
  "/api/glossary.json":
    get:
      summary: "Bilingual glossary — EN + ES home-services terminology"
      description: "Programmatic feed of every glossary term in the ProFix bilingual reference: trade jargon, licensing (OCILB, EPA 608), permits, manufacturer certifications, legal terms, and pricing concepts. Each entry has term, slug, English + Spanish definitions, category, and optional related-term cross-refs. CC-BY-4.0. 1h cache. CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Glossary JSON"
          content:
            "application/json": {}
  "/api/permit-leaderboard.csv":
    get:
      summary: "Permit-pull leaderboard — CSV companion to /api/permit-leaderboard.json"
      description: "RFC 4180 CSV mirror of the permit leaderboard JSON feed. Same query-param filtering (`?trade=&county=&window=&top=`). One row per leaderboard entry. CC-BY-4.0."
      tags:
        - "data"
      parameters:
        - name: "trade"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "county"
          in: "query"
          required: false
          schema:
            type: "string"
        - name: "window"
          in: "query"
          required: false
          schema:
            type: "integer"
            default: 365
        - name: "top"
          in: "query"
          required: false
          schema:
            type: "integer"
            default: 10
      responses:
        "200":
          description: "Permit leaderboard CSV"
  "/api/coverage-stats.csv":
    get:
      summary: "Coverage snapshot — CSV companion to /api/coverage-stats.json"
      description: "RFC 4180 CSV with one row per Ohio county (slug, name, region, pro_count). CC-BY-4.0."
      tags:
        - "data"
      responses:
        "200":
          description: "Coverage stats CSV"
  "/api/lead-feed.csv":
    get:
      summary: "Lead feed — CSV companion to /api/lead-feed.json"
      description: "Long-format CSV (window_days, bucket, key, value) for lead aggregate metrics — pivot-friendly. Zero PII. CC-BY-4.0."
      tags:
        - "data"
      responses:
        "200":
          description: "Lead feed CSV"
        "503":
          description: "Supabase not configured"
  "/api/changelog.json":
    get:
      summary: "Newsroom changelog — machine-readable"
      description: "JSON companion to /newsroom. Every entry has date, type (research/feature/data/api/design), headline, body, optional url. Sorted newest-first. CC-BY-4.0."
      tags:
        - "agents"
      responses:
        "200":
          description: "Changelog JSON"
  "/api/newsroom.rss":
    get:
      summary: "Newsroom RSS 2.0 feed"
      description: "RSS feed combining changelog entries + published research articles. Subscribe in any RSS reader to track ProFix's product + editorial milestones. CC-BY-4.0."
      tags:
        - "agents"
      responses:
        "200":
          description: "RSS XML"
  "/api/changelog.ics":
    get:
      summary: "Newsroom changelog — iCal (RFC 5545) calendar feed"
      description: "iCal subscription of every ProFix Directory changelog milestone as an all-day VEVENT. Subscribe in Apple Calendar, Google Calendar, or Outlook via webcal://profixdirectory.com/api/changelog.ics. Each event carries SUMMARY, DESCRIPTION, URL, CATEGORIES, and a stable UID. CC-BY-4.0, 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "iCal calendar"
          content:
            "text/calendar": {}
  "/api/research-publications.ics":
    get:
      summary: "Research publications — iCal (RFC 5545) calendar feed"
      description: "iCal subscription of every ProFix Directory research-article publication date as an all-day VEVENT. Subscribe in Apple Calendar, Google Calendar, or Outlook via webcal://profixdirectory.com/api/research-publications.ics. Each event links to the canonical /research/<slug> URL. CC-BY-4.0, 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "iCal calendar"
          content:
            "text/calendar": {}
  "/api/recently-verified.json":
    get:
      summary: "Recently-verified pros — rolling 30-day feed"
      description: "Feed of pros whose verifiedAt timestamp falls within the last 30 days. Includes verification tier, permit-verified flag, and trust score. Useful for 'what's new' surfaces + partner freshness checks. CC-BY-4.0. 1h cache."
      tags:
        - "data"
      responses:
        "200":
          description: "Recently-verified pros JSON"
  "/api/reports/this-week.json":
    get:
      summary: "Weekly recap — last 7 days of newsroom changelog + research publications"
      description: "Machine-readable mirror of /reports/this-week. Rolling 7-day window over the public CHANGELOG and RESEARCH_ARTICLES datasets. Returns generated_at, license, window (start/end/days), changelog_entries (date/type/headline/url), research_published (slug/title/url/publishedAt), and counts. Honest about empty windows. CC-BY-4.0. Daily refresh."
      tags:
        - "data"
      responses:
        "200":
          description: "This-week recap JSON"
  "/api/reports/permits-this-month.json":
    get:
      summary: "Monthly recap — last 30 days of Ohio contractor permit activity"
      description: "Machine-readable mirror of /reports/permits-this-month. Rolling 30-day window over the live + synthetic permit-by-pro feed. Returns generated_at, license, window (start/end/days), top_pros (top 25 by permit count), by_trade (top 3 per trade), by_county (top 3 per county across Lucas/Cuyahoga/Franklin/Hamilton). CC-BY-4.0. Daily refresh."
      tags:
        - "data"
      responses:
        "200":
          description: "Permits-this-month recap JSON"
  "/api/trust-scores.json":
    get:
      summary: "ProFix Trust Score aggregate — every pro's composite 0-100 score + tier"
      description: "Bulk feed of every pro's ProFix Trust Score (0-100) and tier (elite/solid/starter/minimal). Composite of verification tier, license status, rating × review count, photos/hours/specialties/tenure, permit-verified flag. Methodology at /methodology. Zero PII. CC-BY-4.0. 1h cache."
      tags:
        - "data"
      responses:
        "200":
          description: "Trust scores JSON"
  "/api/pro/{slug}.json":
    get:
      summary: "Per-pro JSON dossier"
      description: "Canonical per-contractor dossier as JSON. Returns name, location, license + verification details, source URLs, permit count + verified status, ratings. Zero PII beyond what /pro/{slug} already shows publicly. CC-BY-4.0. 1h cache."
      tags:
        - "data"
      parameters:
        - name: "slug"
          in: "path"
          required: true
          schema:
            type: "string"
          description: "Pro slug (see /api/jsonld/local-business-index for the canonical list)"
      responses:
        "200":
          description: "Pro JSON dossier"
        "404":
          description: "Pro not found"
  "/api/jsonld/dataset":
    get:
      summary: "Dataset JSON-LD — Google Dataset Search registration document"
      description: "Top-level Schema.org Dataset document describing the 21,898-record ProFix Ohio Home-Services Pros dataset. Names creator + publisher (ProFix Directory / ProFix Directory LLC), declares CC-BY-4.0 license, lists three DataDownload distributions (JSON, CSV, Hugging Face), Ohio spatialCoverage as a GeoShape, 2026/.. temporalCoverage, and five variableMeasured PropertyValue entries (license status, permit count, verification tier, trust score, review aggregate). Designed for Google Dataset Search registration + academic-search tooling that crawl JSON-LD Dataset shapes. Daily revalidate. CORS open."
      tags:
        - "data"
      responses:
        "200":
          description: "Dataset JSON-LD"
          content:
            "application/ld+json": {}
  "/api/jsonld/{type}":
    get:
      summary: "Open JSON-LD feeds — full Schema.org graphs by type"
      description: "First-class structured-data feeds for AI engines, partners, and structured-data tooling. Supported types: `pros` (LocalBusiness graph for every pro), `cost-guides` (Article graph), `faq` (combined FAQPage), `organization` (ProFix Directory entity), `local-business-index` (top-100 ItemList), `faq-trade-{trade}` (per-trade FAQPage), `breadcrumb-coverage` (BreadcrumbList for /coverage). No auth, 1h CDN cache + 1d stale-while-revalidate."
      tags:
        - "agents"
      parameters:
        - name: "type"
          in: "path"
          required: true
          schema:
            type: "string"
          description: "JSON-LD feed identifier (see description for valid values)"
      responses:
        "200":
          description: "JSON-LD graph"
        "404":
          description: "Unknown feed type"
  "/api/cities.json":
    get:
      summary: "All covered Ohio cities"
      description: "Every Ohio city ProFix covers, with ZIP codes, county, metro, and population."
      tags:
        - "geo"
      responses:
        "200":
          description: "Cities JSON"
  "/api/city-taxonomy.json":
    get:
      summary: "City taxonomy"
      tags:
        - "geo"
      responses:
        "200":
          description: "City taxonomy JSON"
  "/api/permits.json":
    get:
      summary: "Permit office directory (Toledo + Findlay metros)"
      description: "Permit-issuing offices with phone, hours, and per-trade guidance. Currently scoped to Toledo + Findlay metros (other Ohio metros forthcoming)."
      tags:
        - "data"
      responses:
        "200":
          description: "Permits JSON"
  "/api/cost-report.json":
    get:
      summary: "Cost guide aggregate (Toledo + Findlay 2026 research)"
      description: "Aggregate Toledo + Findlay home-services cost benchmarks from the 2026 NW Ohio cost research — 60 cost benchmarks across 8 trades with median + range + Findlay-vs-Toledo variance. CC-BY-4.0. CSV mirror at /api/cost-report.csv."
      tags:
        - "data"
      responses:
        "200":
          description: "Cost report JSON"
  "/api/license-evidence.json":
    get:
      summary: "License evidence index"
      description: "Per-pro license evidence: license number, source URL, last verified."
      tags:
        - "verification"
      responses:
        "200":
          description: "License evidence JSON"
  "/api/outage-status":
    get:
      summary: "Active utility outage status"
      description: "Real-time-ish outage status for major Ohio utilities serving NW Ohio."
      tags:
        - "data"
      responses:
        "200":
          description: "Outage status JSON"
  "/api/pro-card/{slug}":
    get:
      summary: "Generated pro card SVG"
      tags:
        - "assets"
      parameters:
        - name: "slug"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "200":
          description: "SVG image"
  "/api/badge/{slug}.svg":
    get:
      summary: "Embeddable 'ProFix Verified' trust badge SVG"
      description: "Per-contractor 240×80 SVG badge displaying current ProFix trust tier (Elite/Solid/Starter/Minimal), 0-100 composite Trust Score, and recent permit count when > 0. Pure inline SVG using system fonts — no external assets. Designed to be embedded with a single `<img>` tag on a contractor's own website, Slack channel, or email signature. CORS-enabled. CC-BY-4.0; keep the wordmark visible. Returns a 404 SVG fallback for unknown slugs. 1h CDN cache."
      tags:
        - "data"
      parameters:
        - name: "slug"
          in: "path"
          required: true
          schema:
            type: "string"
          description: "Pro slug (see /api/jsonld/local-business-index for the canonical list)"
      responses:
        "200":
          description: "SVG badge"
          content:
            "image/svg+xml": {}
        "404":
          description: "Pro not found — SVG fallback returned"
          content:
            "image/svg+xml": {}
  "/api/site-graph.json":
    get:
      summary: "Site graph — major sections with their pages + data endpoints"
      description: "High-level link graph of ProFix Directory grouped into 10 sections (trust, permits, research, buyer's guides, trades, metros, agent-native, transparency, Spanish, tools). Each section bundles canonical HTML hub URLs with their machine-readable data endpoints, plus a top-level `agent_discovery` block pointing at llms.txt, llms-full.txt, OpenAPI, MCP, and the sitemap index. Designed for AI engines and partners that want to understand the site structure without crawling 20K+ pages. CC-BY-4.0, 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Site graph JSON"
          content:
            "application/json": {}
  "/api/feeds-index.json":
    get:
      summary: "Feeds index — catalog of every JSON/CSV/RSS/iCal feed with metadata"
      description: "Catalog of every public ProFix Directory feed (JSON, CSV, RSS, iCal). Each entry carries `path`, absolute `url`, `format`, `category`, `description`, `license`, `refresh_cadence` (live / hourly / daily / weekly / monthly / static), `cors`, and an optional `openapi_id` cross-reference. Enumerated from the same `PUBLIC_API_FEED_PATHS` list that powers /sitemap.xml — a single fetch tells an agent every machine-readable surface ProFix exposes. CC-BY-4.0, 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Feeds index JSON"
          content:
            "application/json": {}
  "/api/openapi.yaml":
    get:
      summary: "OpenAPI 3.1 spec — YAML mirror of /api/openapi.json"
      description: "YAML serialization of the canonical OpenAPI 3.1 document at /api/openapi.json. Same spec object, byte-different envelope — for partners and AI tooling that prefer YAML over JSON. CC-BY-4.0, 24h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "OpenAPI 3.1 YAML"
          content:
            "application/yaml": {}
  "/api/changelog.atom":
    get:
      summary: "Newsroom changelog — Atom 1.0 mirror of /api/newsroom.rss"
      description: "Atom 1.0 feed of every public ProFix Directory changelog entry. Same data the RSS feed exposes, served in the Atom envelope for readers and AI engines that prefer Atom over RSS. CC-BY-4.0, 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Changelog Atom 1.0"
          content:
            "application/atom+xml": {}
  "/api/changelog.csv":
    get:
      summary: "Newsroom changelog — CSV mirror of /api/changelog.json"
      description: "RFC 4180 CSV mirror of /api/changelog.json. One row per CHANGELOG entry. Columns: `date,type,headline,slug,url`. Use when you want to drop the changelog into Excel/Sheets/pandas. CC-BY-4.0, 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Changelog CSV"
          content:
            "text/csv": {}
  "/api/research.csv":
    get:
      summary: "Research articles — CSV mirror of /api/research.json"
      description: "RFC 4180 CSV mirror of /api/research.json. One row per published research article. Columns: `slug,publishedAt,wordCount,title,summary,keywords,url` where `keywords` is `;`-joined since CSV cannot nest arrays. CC-BY-4.0, 1h cache, CORS-enabled."
      tags:
        - "data"
      responses:
        "200":
          description: "Research CSV"
          content:
            "text/csv": {}
  "/llms.txt":
    get:
      summary: "LLM content map (llmstxt.org spec)"
      tags:
        - "agents"
      responses:
        "200":
          description: "Plain-text content map"
  "/llms-full.txt":
    get:
      summary: "Full LLM content dump"
      tags:
        - "agents"
      responses:
        "200":
          description: "Plain-text full content map"
tags:
  - name: "agents"
    description: "Endpoints for AI agents (MCP, llms.txt, OpenAPI itself)"
  - name: "data"
    description: "Live data feeds: permits, costs, outages"
  - name: "geo"
    description: "Geographic + city taxonomy data"
  - name: "verification"
    description: "Authoritative source cross-checks"
  - name: "assets"
    description: "Generated media (SVG cards, OG images)"
x-mcp-server-url: "https://profixdirectory.com/api/mcp"
x-data-sources-url: "https://profixdirectory.com/data-sources"
