# Self-Service Portal (SSP) — Refined Spec **Status:** Draft (ssp-refinement branch) **Author:** Bumble (research), Buzz SouthPaw (engineering review) **Date:** 2026-07-29 **Supersedes:** `docs/product/ssp.md` (thin intent doc) **Source documents:** - [Canonical Strategy](../strategy/CANONICAL_STRATEGY.md) — product definition, priorities, scope - [UC-01: Endpoint Activation](./use-cases/uc-01-endpoint-auto-repair.md) — operating use case - [UC-04: Conversational SSP — First Hour](./use-cases/uc-04-conversational-ssp-first-hour.md) — conversational onboarding - [UC-05: Conversational SSP — Ongoing](./use-cases/uc-05-conversational-ssp-ongoing.md) — ongoing relationship - [Owner MCP](../owner-mcp.md) — MCP tool definitions - MVP scope (Idea research channel, 2026-07-29) — in/out decisions - Product decisions (SSP Refinement channel, 2026-07-29) — five decision record (below) --- ## 1. Intent The SSP is the activation experience for Model 1 (individual local businesses). It turns a business owner's raw info into a live, AI-discoverable MCP endpoint through a **conversational interface** — no forms, no wizards. The owner describes their business in natural language, verifies ownership, receives a presence audit, and gets actionable recommendations — all through a ChatGPT-like experience at GeoLocal.io. **Two phases, one relationship:** - **The First Hour** (UC-04): Verification, presence audit, initial recommendations, guided setup - **The First Month+** (UC-05): Ongoing monitoring, data updates, periodic reports, seasonal guidance **One line we will not blur:** the conversational SSP is the **only activation path** — no form-based fallback. Chat replaces forms entirely. --- ## 2. Scope (MVP) One conversational flow, zero forms. The goal is the smallest shippable thing that lets one real business owner go from "tell me about my business" to "discoverable by AI" — all through chat. ### In (MVP) - Conversational welcome and discovery (natural language input) - Ownership verification (OTP via email) - Presence audit (automated, multi-platform) - Actionable recommendations (ranked, platform-specific) - Guided setup (conversational data capture, pointer install, preflight) - Session persistence (resume later) - Landing page with animated demo + "Get Started" CTA ### Out (deferred) - OAuth / SSO - Ingestion pipeline (continuous sync) - Genre packs beyond basic category - Telemetry dashboard - Partner handoff flow - Voice input - Multi-language support - Multi-business accounts - Form-based fallback (chat is the only path — see decision record) --- ## 2.5 Product Decision Record **Date:** 2026-07-29 **Source:** SSP Refinement channel — five decisions from Ty | # | Decision | Rationale | |---|----------|-----------| | 1 | **Landing page with entry point** — GeoLocal.io showcases the experience (animated demo of what the chat feels like) with a clear "Get Started" CTA that launches the conversational flow. Not buried behind a separate URL. | Lower friction; demonstrate value before asking for commitment. | | 2 | **LLM: TBD, hosted 8–12B model** — Workflow scripts drive the conversation structure; model selection defers to implementation. The model handles natural language within the workflow-driven flow. | Requirements phase, not design phase. Defer provider choice; lock the structure now. | | 3 | **Requirements phase for OTP/email** — OTP-by-email is a verification requirement without implementation detail. No email provider selected yet. | Focus on use cases first; implementation details come later. | | 4 | **Chat replaces forms entirely** — No form-based fallback. The conversational SSP is the only activation path. | Clean break; forms are the problem we're solving away. | | 5 | **Onboarding becomes ongoing** — Two phases: "The First Hour" (verification + audit + first recommendations) and "The First Month+" (presence updates, new audits, monitoring, ongoing guidance). | One-time activation degrades over time. Ongoing relationship maintains data quality and platform value. | --- ## 3. Screen-by-Screen Spec (Superseded) > ⚠️ **This section is superseded by UC-04 and UC-05.** The screen-by-screen form wizard is replaced by the conversational flow. Retained for reference during transition; remove when conversational implementation is complete. ### Screen 1: Signup **Purpose:** Create an account tied to one business. No OAuth — email + password only for MVP. **Fields:** - Email (required, validated format) - Password (required, min 8 chars) - Password confirm (required, must match) **Behavior:** - Creates a `tenants` row (email, password hash, status = 'active') - Creates a `businesses` row with status = 'draft' - Associates tenant with business via FK - Redirects to Screen 2 (Business Info) - No email verification for MVP (defer) **Errors:** - Email already registered → "Account exists. Try signing in." (add signin link) - Password mismatch → "Passwords don't match" - Empty required field → inline validation **Notes:** - One tenant = one business for MVP (multi-business deferred) - Password hashing: bcrypt or argon2 (whatever the framework provides) --- ### Screen 2: Business Info Capture **Purpose:** Collect the structured data that becomes the MCP payload. This is the core data entry screen. **Fields (organized in sections):** **Identity:** - Business name (required) - Business slug (auto-generated from name, editable, unique) - Category (required, select from predefined list) - Description / story (optional, textarea) **Location:** - Street address (optional but encouraged) - City (optional) - State (optional) - ZIP (optional) - Phone (optional) - Website URL (optional — used for scrape reflection) **Operations:** - Hours (JSON structure: Mon-Sun open/close, plus holiday override flag) - MVP: simple time picker per day + "closed" toggle - Holiday overrides deferred to post-MVP - Services (array of name/description pairs) - MVP: add/remove rows, no sub-categories - Booking link (optional, URL — Cal.com or equivalent) **Behavior:** - Slug validation: lowercase, hyphenated, unique (check against existing businesses) - Save drafts as user types (auto-save to `businesses` row, status = 'draft') - "Next: See what we found" button → Screen 3 (Scrape Reflection) - Back to signup not needed (one-way flow; edit later from dashboard — deferred) **Schema mapping:** | Field | businesses column | |-------|-------------------| | Business name | `name` | | Slug | `slug` | | Category | `category` | | Description | `story` | | Address fields | `address`, `city`, `state`, `zip` | | Phone | `phone` | | Website | `website` | | Hours | `hours` (JSONB) | | Services | `services` (JSONB) | | Booking link | `calcom_link` | --- ### Screen 3: Scrape Reflection **Purpose:** Show the owner what the platform already sees on their website — and where there are gaps. This is the "aha" moment. **Input:** Website URL from Screen 2. **Behavior:** - One-time scrape triggered when user clicks "Analyze my site" (or auto-triggers if URL provided) - Parse public HTML for: business name, hours, services, phone, address, booking links - Display side-by-side comparison: ``` +---------------------+---------------------+ | What you entered | What we found | +---------------------+---------------------+ | Name: Bob's Garage | Name: Bob's Garage | (match) | Hours: M-F 8-5 | Hours: not found | (gap) | Services: [3 items] | Services: [5 items] | (partial match) | Booking: Cal.com | Booking: not found | (gap) +---------------------+---------------------+ ``` **Output:** - Green check: field found on site and matches entry - Yellow warning: field found but differs from entry (show both values) - Red X: field not found on site at all - "Use what we found" button to auto-populate gaps from scrape - "Skip" button to proceed without scrape (site may not exist yet) **Technical notes:** - Scrape runs server-side (not client-side — CORS) - Timeout: 10 seconds per page - No JS rendering for MVP (static HTML only — defer puppeteer/playwright) - Store scrape result in a temporary field or session (not persisted to `businesses` until user confirms) - Rate limit: one scrape per signup session **Deferred:** - Continuous sync (ingestion pipeline) - JS-rendered page support - Image/logo extraction --- ### Screen 4: Pointer Install **Purpose:** Give the owner concrete instructions to attach a discovery pointer on their website so AI agents can find the hosted MCP. **Content:** **What this does (plain language):** > "This installs a tiny marker on your website that tells AI assistants where to find your business data. It takes 2 minutes." **Option A: `.well-known/mcp-server` (preferred)** Instructions: 1. Create a file: `yourdomain.com/.well-known/mcp-server` 2. Contents: `{"mcp": "https://geolocal.io/mcp/{slug}"}` 3. Upload to your website root (or ask your web host) 4. Click "Verify" below **Option B: DNS CNAME (for static sites / hosted platforms)** Instructions: 1. Add a CNAME record: `mcp.yourdomain.com` → `geolocal.io` 2. Path: `/mcp/{slug}` will be routed by hostname 3. DNS propagation: up to 24 hours 4. Click "Verify" below **Option C: Link tag (quick test)** Instructions: 1. Add to your site's ``: ```html ``` 2. Click "Verify" below **Verification:** - "Verify" button triggers a server-side fetch of the pointer location - Success: green check + "Pointer verified" message - Failure: plain-language error ("We couldn't find the file at that URL — check the path and try again") - DNS CNAME verification: check CNAME record + follow to MCP endpoint **Behavior:** - Owner can proceed to Screen 5 without verified pointer (endpoint goes live as "unverified") - Unverified endpoints show a warning badge in Screen 5 - No blocking — we want them live even if pointer install takes time **Notes:** - Multi-path discovery is the strategy (not a single fragile convention) - The pointer URL uses the HTTP transport endpoint (see decision record) - For MVP, Option A is the primary path; B and C are fallbacks --- ### Screen 5: Live Status **Purpose:** Confirm the endpoint is live and show what an AI agent sees. **Content:** **Status banner:** - Green: "You are discoverable" — MCP endpoint is live and responding - Yellow: "Almost live" — MCP is live but pointer not verified - Red: "Not ready" — preflight failed (missing required fields) **MCP Preview:** - Show the actual JSON response an agent would get for `get_business_info` with their slug - Collapsible sections for each tool: `get_hours`, `get_services`, `get_booking_link`, `get_related_businesses` **Preflight Checklist:** - [ ] Business name set - [ ] Category set - [ ] At least one service entered - [ ] Hours entered (at least one day) - [ ] Phone or booking link set - [ ] Pointer verified (optional but recommended) **Actions:** - "Edit business info" → back to Screen 2 (post-MVP: needs a dashboard) - "Re-verify pointer" → back to Screen 4 - "Share endpoint URL" → copy `https://geolocal.io/mcp/{slug}` **Done-when criteria met:** 1. ✅ One business owner completed signup through SSP 2. ✅ Their data is queryable via HTTP MCP endpoint 3. ✅ An AI agent can discover and call the MCP (pointer verified) 4. ✅ The agent returns a useful answer (preflight passes) --- ## 4. Data Model ### Businesses table (expanded) ```sql CREATE TABLE businesses ( id SERIAL PRIMARY KEY, slug VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(500) NOT NULL, category VARCHAR(100), address TEXT, city VARCHAR(200), state VARCHAR(50), zip VARCHAR(20), phone VARCHAR(50), website VARCHAR(500), hours JSONB DEFAULT '{}', services JSONB DEFAULT '[]', story TEXT, owner_bio TEXT, photos JSONB DEFAULT '[]', calcom_link VARCHAR(500), mcp_endpoint VARCHAR(500), related_business_ids INTEGER[] DEFAULT '{}', status VARCHAR(50) DEFAULT 'draft', -- NEW: draft | active | inactive | quarantine pointer_verified BOOLEAN DEFAULT FALSE, -- NEW created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); ``` **Status values:** - `draft` — owner in progress, not queryable - `active` — live, queryable by agents - `inactive` — owner requested pause or business closed - `quarantine` — quality loop flagged, manually reviewed **Decision:** Use a `status` column on `businesses` instead of a separate `quality_flags` table. Rationale: MVP has four states that are mutually exclusive per business. A separate table adds join complexity for a single-row-per-business relationship. Revisit if quality flags become multi-dimensional (e.g., separate flags for data freshness, booking health, pointer validity). ### Tenants table (new) ```sql CREATE TABLE tenants ( id SERIAL PRIMARY KEY, email VARCHAR(500) UNIQUE NOT NULL, password_hash VARCHAR(500) NOT NULL, business_id INTEGER REFERENCES businesses(id) ON DELETE CASCADE, status VARCHAR(50) DEFAULT 'active', -- active | suspended created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE INDEX idx_tenants_business ON tenants(business_id); ``` **MVP constraints:** - One tenant per business (enforced by application logic, not schema) - No role system (owner is the only role) - No multi-business support --- ## 5. HTTP Transport Decision Record **Date:** 2026-07-29 **Status:** Accepted **Decision:** Use streamable-HTTP transport for the public MCP gateway, not stdio. **Context:** - Stdio transport works for local development and single-process agents but cannot serve internet-discoverable endpoints. - The MCP spec defines streamable-HTTP as the transport for remote server access (SSE-based, with POST for tool calls). - Our gateway must be discoverable via a URL that any AI assistant can reach — stdio cannot satisfy this. **Consequences:** - Replace `StdioServerTransport` with `StreamableHTTPTransport` (or equivalent) in `mcp-server.ts`. - The Fastify/Express server will expose `/mcp/{slug}` as the streamable-HTTP endpoint. - Each slug routes to the correct business's tool set (multi-tenant via slug lookup). - No JWT auth on MVP endpoints (defer OAuth; open access is acceptable for read-only business data). - Rate limiting will be added post-MVP. **Alternatives considered:** - WebSocket: heavier client requirement, no MCP spec support yet. - gRPC: overkill for JSON tool calls, poor browser compatibility. - REST API without MCP framing: loses MCP compatibility, reinvents the protocol. --- ## 6. Flow Diagram ### Conversational Flow (Primary — UC-04) ``` Owner arrives at geolocal.io | sees animated demo + "Get Started" CTA | v [Chat Opens] "Welcome to GeoLocal! Tell me about your business." | owner describes business in natural language | AI extracts: business type, location, goal v [Verification] "Let me verify you're the owner. Emailing OTP to your website's contact." | owner enters OTP code | ownership confirmed v [Audit] "I'll review your online presence — one moment." | scans: Google, Apple, Bing, Yelp, website | gap analysis: what's missing, inconsistent, outdated v [Results] "This looks like a terrific salon! Here's what I found..." | specific findings per platform | ranked recommendations v [Guided Setup] "Want me to help you set things up?" | conversational data capture (no forms) | pointer install guidance | preflight verification v [First Session Complete] | endpoint live (or in progress) | action list for ongoing optimization | session saved for resume | v [Ongoing — UC-05] Owner returns for updates, monitoring, reports | presence monitoring | data updates (hours, services, specials) | periodic health reports | seasonal guidance ``` ### Legacy Form Flow (Superseded — retained for reference) ``` Owner arrives at geolocal.io | v [Screen 1] Signup (email + password) | creates tenant + business(draft) v [Screen 2] Business Info Capture (manual entry) | populates businesses row v [Screen 3] Scrape Reflection (one-time, optional) | compares site content vs entered data | owner confirms or edits v [Screen 4] Pointer Install (instructions + verify) | owner installs .well-known/mcp-server or CNAME | platform verifies pointer v [Screen 5] Live Status | status = 'active' | preflight passes | MCP endpoint live at /mcp/{slug} | v AI agent discovers pointer -> calls /mcp/{slug} -> gets structured data ``` --- ## 7. Acceptance Criteria ### Screen 1: Signup - [ ] User can create an account with email + password - [ ] Duplicate email is rejected - [ ] Tenant row created with correct business_id FK - [ ] Business row created with status = 'draft' ### Screen 2: Business Info - [ ] All fields save to businesses row - [ ] Slug is auto-generated, editable, unique - [ ] Category is required (from predefined list) - [ ] Services can be added/removed (min 1) - [ ] Hours captured as JSONB ### Screen 3: Scrape Reflection - [ ] Server-side scrape of provided URL (10s timeout) - [ ] Side-by-side comparison displayed - [ ] "Use what we found" populates gaps - [ ] Skippable (proceed without scrape) ### Screen 4: Pointer Install - [ ] Three pointer options presented (well-known, CNAME, link tag) - [ ] Verification fetches and validates pointer - [ ] Proceed without verification allowed (unverified badge) ### Screen 5: Live Status - [ ] Status banner reflects endpoint state - [ ] MCP preview shows actual JSON response - [ ] Preflight checklist is accurate - [ ] Setting status = 'active' makes endpoint queryable ### Cross-cutting - [ ] HTTP transport replaces stdio for `/mcp/{slug}` - [ ] Slug-based routing returns correct business data - [ ] Done-when criteria 1-4 are all satisfied --- ## 8. Open Questions 1. **Minimum content bar:** What fields are required before status = 'active'? Proposal: name, category, at least one service, at least one day of hours. 2. **Category list:** What categories ship in MVP? Proposal: auto repair, beauty/salon, home services, restaurant, retail/general store, tourism/activity. 3. **Scrape depth:** Single page only, or follow links? Proposal: single page (homepage) for MVP. 4. **Edit after live:** Do we need a dashboard screen for post-live edits? Proposal: yes, but defer to post-MVP; allow re-running the flow for now. 5. **Booking link validation:** Do we verify the Cal.com link actually works? Proposal: URL format check only for MVP. --- ## 9. What Changed from Original SSP Spec | Area | Before | After | |------|--------|-------| | Length | 14 lines | Implementation-ready | | Screens | Named but not described | 5 screens with fields, behavior, errors (superseded by conversational flow) | | Data model | Not specified | Schema with status column, tenants table | | Scrape | Mentioned | One-time on signup, side-by-side comparison | | Pointer | Mentioned | 3 options with verification flow | | Transport | Not decided | Streamable-HTTP with decision record | | Quality flags | Separate table | Status column on businesses | | Acceptance criteria | None | Per-screen + cross-cutting | ## 10. Conversational SSP Revision (2026-07-29) This revision incorporates five product decisions from the SSP Refinement channel: | Area | Before (Form SSP) | After (Conversational SSP) | |------|-------------------|----------------------------| | Activation path | 5-screen form wizard | Conversational chat (ChatGPT-like) | | Fallback | N/A | No form fallback — chat only | | Landing page | Not specified | Animated demo + "Get Started" CTA | | Verification | Email + password signup | OTP to website contact email | | Data capture | Manual form entry | Conversational (AI asks, owner answers) | | Audit timing | Not in original | After verification, before setup | | Scope | One-time activation | Ongoing relationship (First Hour + First Month+) | | LLM | Not in original | Hosted 8–12B model, workflow-driven (TBD provider) | | Use cases | UC-01, UC-02, UC-03 | UC-04 (First Hour), UC-05 (Ongoing) added | **Status of form-based screens (§3):** Superseded by UC-04 and UC-05. Retained for reference during transition. Remove when conversational implementation is complete.