{"ok":true,"name":"M3 API","api":"https://api.m3sys.org","status":"operational","generated_at":"2026-10-06T23:20:43.100Z","services":[{"service":"scrape","name":"gbp-scraper","status":"ok","health":{"ok":true,"service":"gbp-scraper"},"health_latency_ms":169,"endpoints":[{"path":"/scrape/gbp","methods":["POST"],"description":"Scrape GBP data. Two modes: (1) single profile from gbp_link, or (2) keyword search that scrapes ALL results up to max and saves each as a lead in D1.","request_body":{"gbp_link":"string (optional) — Google Maps URL of the business profile (single-scrape mode)","business":"string (optional) — business name hint for single-scrape mode","keyword":"string (optional) — search term (search mode; required if no gbp_link)","city":"string (optional) — city filter for search mode","state":"string (optional) — state filter for search mode","max":"integer (optional, default 10, 1-50; -1 for all results) — max results to scrape in search mode"},"response":"{ profile: GbpProfile } OR { keyword, city, state, max, results, leads_saved, profiles: GbpProfile[] }"},{"path":"/scrape/gbp/search","methods":["POST"],"description":"Search Google Maps by business name (+ optional location).","request_body":{"business":"string (required)","location":"string (optional)"},"response":"{ profile: GbpProfile }"},{"path":"/scrape/gbp/places","methods":["POST"],"description":"Search the Google Places API by a business name.","request_body":{"business":"string (required)"},"response":"{ profile: GbpProfile }"},{"path":"/scrape/gbp/competitors","methods":["POST"],"description":"Fetch competitor data for a business category.","request_body":{"category":"string (required)"},"response":"{ competitors: Competitor[] }"},{"path":"/scrape/gbp/qualify","methods":["POST"],"description":"Score existing scraped leads against qualification rules and store qualifying leads in the qualified_leads table.","request_body":{"max_reviews":"integer (optional, default 100) — skip leads with this many or more reviews","check_website":"boolean (optional, default false) — live-check websites for breakage"},"response":"{ qualified, disqualified, total, config, disqualify_reasons }"},{"path":"/scrape/gbp/leads","methods":["GET"],"description":"List qualified leads from the qualified_leads table.","response":"{ total, limit, offset, leads: QualifiedLead[] }"},{"path":"/scrape/gbp/email","methods":["POST"],"description":"Collect email for a business: checks GBP email (if gbp_link), then website (homepage + /contact etc), then BBB search. Uses Browser Rendering.","request_body":{"business":"string (optional) — business name (required if no website/gbp_link)","website":"string (optional) — website URL to scrape","city":"string (optional) — city for BBB search","state":"string (optional) — state for BBB search","address":"string (optional) — full address for BBB search","gbp_link":"string (optional) — Google Maps URL to scrape GBP email first","try_bbb":"boolean (optional, default true) — whether to fallback to BBB"},"response":"{ email: string|null, source: string|null, emails: string[], bbb_url?: string, profile?: GbpProfile }"}]},{"service":"audit","name":"gbp-audit","status":"ok","health":{"ok":true,"service":"gbp-audit"},"health_latency_ms":38,"endpoints":[{"path":"/audit/gbp","methods":["POST"],"description":"Generate a full GBP audit report from a scraped profile.","request_body":{"profile":"GbpProfile (required) — profile returned by /scrape/gbp","request":"object (optional) — audit request data","competitors":"Competitor[] (optional)"},"response":"{ report: object, generated_at: ISO timestamp }"},{"path":"/audit/gbp/run","methods":["POST"],"description":"Full audit pipeline: gather the business profile, fetch competitors, and generate the report.","request_body":{"business":"string (required) — business name to audit","city":"string (optional)","gbp_link":"string (optional) — Google Maps URL hint","request":"object (optional) — audit request data","allow_mock":"boolean (optional) — fall back to a placeholder profile if scraping fails"},"response":"{ report, profile: GbpProfile, source: string }"},{"path":"/audit/gbp/profile","methods":["POST"],"description":"Gather a business profile via the scraper (no report).","request_body":{"business":"string (required)"},"response":"{ profile: GbpProfile, source: string }"}]},{"service":"leads","name":"surface-lander","status":"ok","health":{"ok":true,"service":"surface-lander"},"health_latency_ms":70,"endpoints":[{"path":"/api/leads","methods":["GET"],"description":"List qualified leads (sort/filter/search/paginate)."},{"path":"/api/leads/:id","methods":["GET"],"description":"Single qualified lead with communication history."},{"path":"/api/leads/:id/notes","methods":["POST"],"description":"Add a communication entry (note/call/email/followup)."},{"path":"/api/leads/:id","methods":["POST"],"description":"Re-scrape (refresh) a lead's current data."},{"path":"/api/leads/scrape","methods":["POST"],"description":"Launch a new keyword scrape (leads auto-qualify on insert)."},{"path":"/api/leads/:id/audit","methods":["GET","POST"],"description":"Fetch or generate an on-demand audit report for a lead."},{"path":"/api/leads/:id/pitch","methods":["GET"],"description":"Generate a sales pitch email body from the latest audit (demo URL front and center)."},{"path":"/api/leads/:id/email","methods":["POST"],"description":"Send an email about a lead (optionally with the latest audit or pitch loaded)."},{"path":"/api/leads/:id/collect-email","methods":["POST"],"description":"Collect email for a lead: GBP → website (homepage + /contact etc) → BBB search via Playwright. Also ensures demo_url = https://surface.m3sys.org/demo?id=<id>."},{"path":"/demo?id=:qualifiedleadid","methods":["GET"],"description":"Render the single-page demo site for a qualified lead."},{"path":"/api/admin/clients","methods":["GET"],"description":"List onboarding clients (clients table as-is)."},{"path":"/api/admin/config","methods":["GET","PUT"],"description":"Read or replace the admin config text file (qualification + audit rules)."}]},{"service":"leads","name":"surface-lander","status":"ok","health":{"ok":true,"service":"surface-lander"},"health_latency_ms":69,"endpoints":[{"path":"/api/leads","methods":["GET"],"description":"List qualified leads (sort/filter/search/paginate)."},{"path":"/api/leads/:id","methods":["GET"],"description":"Single qualified lead with communication history."},{"path":"/api/leads/:id/notes","methods":["POST"],"description":"Add a communication entry (note/call/email/followup)."},{"path":"/api/leads/:id","methods":["POST"],"description":"Re-scrape (refresh) a lead's current data."},{"path":"/api/leads/scrape","methods":["POST"],"description":"Launch a new keyword scrape (leads auto-qualify on insert)."},{"path":"/api/leads/:id/audit","methods":["GET","POST"],"description":"Fetch or generate an on-demand audit report for a lead."},{"path":"/api/leads/:id/pitch","methods":["GET"],"description":"Generate a sales pitch email body from the latest audit (demo URL front and center)."},{"path":"/api/leads/:id/email","methods":["POST"],"description":"Send an email about a lead (optionally with the latest audit or pitch loaded)."},{"path":"/api/leads/:id/collect-email","methods":["POST"],"description":"Collect email for a lead: GBP → website (homepage + /contact etc) → BBB search via Playwright. Also ensures demo_url = https://surface.m3sys.org/demo?id=<id>."},{"path":"/demo?id=:qualifiedleadid","methods":["GET"],"description":"Render the single-page demo site for a qualified lead."},{"path":"/api/admin/clients","methods":["GET"],"description":"List onboarding clients (clients table as-is)."},{"path":"/api/admin/config","methods":["GET","PUT"],"description":"Read or replace the admin config text file (qualification + audit rules)."}]}],"endpoints_count":34}