# Morvero — How-to Guide (Markdown edition)
> Morvero is portfolio-level product intelligence for enterprise software: usage analytics, feature-level adoption, in-product feedback, NPS, and AI-drafted backlogs across every internal app and AI-agent pilot — flat-priced per portfolio (never per-user or per-event), anonymous by design, installed with one script tag per product. Feedback flows to Jira or Azure DevOps as tickets with acceptance criteria and cited user evidence.
This is the machine-readable edition of https://morvero.com/docs, served at `/docs.md` and `/llms-full.txt`. URLs below use the Morvero cloud host (`https://morvero.com`); self-hosted installs substitute their own origin. The human version with styling and anchors: https://morvero.com/docs
## 1. Getting started
1. **Create a workspace** — https://morvero.com/app/?signup=1 with your company name and work email. A single-use sign-in link verifies the address and opens the workspace. Passwordless: add a passkey (Face ID / Touch ID / security key) after first sign-in; email links remain the fallback.
2. **Create a product** — console → Products → + New product. Name it, choose a random-sampling rate (share of visitors who get a proactive feedback prompt), optionally restrict allowed domains.
3. **Copy the snippet** — one line of HTML; that's the whole integration.
4. **Follow the setup checklist** — new workspaces show a "Get Morvero live" checklist on the Overview (create product → install snippet → first feedback → invite a teammate). Progress is read from real data and the card retires itself once you're live.
## 2. Install the widget
Paste the snippet into the product's footer or layout template so it renders on every page:
```html
```
- Works on multi-page apps and SPAs (route changes tracked automatically).
- The widget lives in a closed Shadow DOM — your CSS can't break it, it can't break your page, and `async` means it never blocks rendering.
- Deactivating the product in the console is an instant kill switch.
- Appearance & language (product page → Widget appearance): accent color, corner, language auto-detect or fixed (EN/TR/DE/FR/ES); Enterprise can override individual strings.
- The product card shows "receiving data ✓" once the first pageview arrives.
- Set **allowed domains** for production keys — requests from other origins are rejected.
- **Usage monitoring only**: untick "Collect feedback" and the widget turns invisible — no feedback button, no prompts, feedback API refuses submissions for that key; pageview and function tracking continue.
## 3. What your users see
- A small floating 💬 button (bottom-right). The panel: 1–5 star rating for the current page, optional comment, and two opt-in attachments:
- **Screenshot** — via the browser's native share prompt (explicit consent; the widget hides itself from the shot).
- **Form entries** — values typed on the page, for "the form rejected my input" context. Passwords, hidden fields, and sensitive-looking names (card, cvv, ssn, token…) are excluded client-side AND re-scrubbed server-side; exclude anything else with a `data-feedback-ignore` attribute.
- **Random sampling** — the configured share of visitors gets the panel proactively after ~8 seconds, at most once per 7 days per visitor, never again for a week after any feedback.
- **Privacy** — visitors are anonymous (random ID in the browser, no cookies, no fingerprinting). Do Not Track / Global Privacy Control disable tracking and sampling entirely; deliberate manual feedback still works.
## 4. Track pages & functions
**Pages — automatic.** Every page load and SPA route change is an anonymous pageview.
Hash-routed SPA (`#/invoices`)? Add `data-feedback-hash-routing` to the script tag so the hash counts as part of the route. Opt-in, because on ordinary sites hashes are anchors.
**Functions — tag elements (no JS):**
```html
```
Clicks appear under "Most / least used functions".
**Functions — from code:** `MorveroFeedback.track('name')` records a use; `MorveroFeedback.open()` opens the panel.
Two rules: API calls must live in their **own** `
```
**Cohorts — optional, privacy-floored.** Pass a coarse group label (department, region, role family) via `data-feedback-cohort="finance"` and the product page gains a usage-and-sentiment breakdown by cohort. Aggregate-only with a k-anonymity floor: cohorts under 5 visitors in range are withheld entirely. Never pass usernames or employee ids.
**Countries — automatic.** Every event is stamped with a two-letter country code resolved server-side from the request IP at collection time (in-process GeoLite2 lookup; the IP is discarded immediately, only the code is stored). Overview shows a world heat map; product pages get a Geography card. Behind Cloudflare or similar, the CDN's country header is used instead.
## 5. Mobile apps & REST API
The web widget is a client of a small public REST API — native apps and server-side integrations call the same endpoints. No SDK to bundle. The full surface (collection API + Enterprise read API) is specified at https://morvero.com/openapi.json.
Flow: **token, then submit.**
1. `GET https://morvero.com/api/widget-config?key=fbk_...` → short-lived `token` (~15 min). On `401 token_invalid`, re-fetch and retry.
2. `POST https://morvero.com/api/collect/feedback` with JSON:
```json
{
"key": "fbk_...",
"token": "TOKEN_FROM_STEP_1",
"rating": 4,
"comment": "Love the new checkout, but Apple Pay failed once.",
"path": "/checkout",
"title": "Checkout",
"platform": "ios",
"visitorId": "STABLE_ANONYMOUS_UUID"
}
```
- `rating` — required integer 1–5; everything else optional.
- `path`/`title` — your screen name, so mobile screens join the same page analytics as web routes.
- `platform` — `"ios"`, `"android"`, or `"api"` (default `"web"`); filterable in triage and CSV export.
- `visitorId` — random UUID generated once, stored in Keychain / EncryptedSharedPreferences. Never a device identifier (IDFA/AAID) — anonymous and resettable keeps you clean under GDPR/KVKK.
- `screenshot` — optional data-URL JPEG/PNG ≤ ~2 MB, only with explicit user consent.
- **NPS** — `POST /api/collect/nps` with `{key, token, score: 0–10, comment?, visitorId, path?}`.
- **Pageviews & actions** (no token needed) — `POST /api/collect/pageview` `{key, path, title?, visitorId}` and `POST /api/collect/action` `{key, action, path?, visitorId}`.
**Drop-in native SDKs (optional):** two single-file, dependency-free wrappers implement the whole flow (anonymous id persistence, `X-Morvero-App-Id` header, token fetch + retry):
- https://morvero.com/sdk/MorveroFeedback.swift — iOS 13+ / macOS 10.15+, URLSession only.
- https://morvero.com/sdk/MorveroFeedback.kt — Android minSdk 21, HttpURLConnection + org.json only.
```swift
MorveroFeedback.configure(origin: "https://morvero.com", key: "fbk_...", appId: "com.company.app")
MorveroFeedback.trackScreen("/invoices")
MorveroFeedback.trackAction("export-csv")
MorveroFeedback.submitFeedback(rating: 4, comment: "Works well")
```
Every call is fire-and-forget on a background thread and never crashes the host app. Both files support the optional cohort label.
**Restrict the key to your apps.** Keys are public by design. Web keys pin with allowed domains; native apps set allowed app IDs (product page → Mobile apps & REST API) and send `X-Morvero-App-Id` on every call. Both lists empty → any client. Only domains → browsers only. Only app IDs → mobile only. Both → either match passes. The header is a declaration, not proof — per-visitor caps and token expiry are the real abuse controls.
**Limits:** 5 feedback items + 2 NPS answers per visitor per day; bursts return `429`. Treat any non-201 as "drop or queue, never crash". Kill switch: deactivating the product rejects the key everywhere.
## 6. Track AI agent pilots
Enterprises pilot dozens of internal AI agents — support copilots, HR bots, SQL assistants — and nobody can say which are used, liked, or worth continuing. Morvero treats **each agent pilot as a product** with usage, sentiment, outcomes and cost on one scorecard.
Quickstart:
1. Create a product with type **"AI agent pilot"** — it gets relabeled dashboards (conversations, active users, capabilities), a pilot scorecard, and a row in the Overview's AI agent pilots table.
2. Instrument the agent's backend with three HTTPS calls (usage telemetry needs no token; feedback uses the usual short-lived token):
| Agent event | Morvero call | Notes |
|---|---|---|
| Conversation started | `POST /api/collect/pageview` | `path` = entry surface (`/slack`, `/web-chat`, `/vscode`) |
| Capability / tool call | `POST /api/collect/action` | `action` = capability name; optional `cost` in USD |
| Task outcome | `POST /api/collect/action` | reserved names: `task-completed`, `task-failed`, `escalated-to-human` — these drive the success/escalation KPIs |
| User thumbs 👍/👎 | `POST /api/collect/feedback` | `thumb: "up"\|"down"` (or `rating` 1–5), `comment`, `platform: "agent"` |
| Periodic CSAT | `POST /api/collect/nps` | 0–10, same as web NPS |
- `visitorId` — pseudonymous per-employee id (salted hash of the employee id; never emails or names).
- Rate allowance — usage telemetry is limited per source IP *per product key* (240/min each, 2,400/min per IP total), so one backend egress IP can report many pilots concurrently.
- Feedback flows into triage tagged ✨ Agent — filterable, exportable, clustered by the AI backlog copilot alongside human feedback.
**The pilot scorecard:** task success rate (completed ÷ decided), escalation rate (escalations ÷ conversations), cost per conversation, cost per successful task; the Overview ranks every pilot side by side. Fund the leaders, fix the frustrating, retire the unused — with evidence.
**Drop-in emitter** (Node 18+, zero dependencies) — framework adapters just decide when to call it:
```js
class MorveroAgent {
constructor({ host, key, userId }) {
Object.assign(this, { host, key, userId });
}
#post(path, body) { // fire-and-forget: telemetry must never break the agent
fetch(this.host + path, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: this.key, visitorId: this.userId, ...body }),
}).catch(() => {});
}
conversationStarted(surface = '/chat') { this.#post('/api/collect/pageview', { path: surface }); }
capability(name, cost = 0, surface = '/chat') { this.#post('/api/collect/action', { action: name, cost, path: surface }); }
taskCompleted() { this.capability('task-completed'); }
taskFailed() { this.capability('task-failed'); }
escalated() { this.capability('escalated-to-human'); }
async thumbs(direction, comment = '', surface = '/chat') {
const cfg = await (await fetch(`${this.host}/api/widget-config?key=${this.key}`)).json();
this.#post('/api/collect/feedback', { token: cfg.token, thumb: direction, comment, path: surface, platform: 'agent' });
}
}
const morvero = new MorveroAgent({ host: 'https://morvero.com', key: 'fbk_...', userId: 'emp-8f2a' });
```
Adapters (treat as shapes — anywhere you can hook "run started / tool called / run ended", Morvero can score the pilot):
- **Claude Agent SDK / Anthropic tool loop** — conversation per session, capability per tool call, cost from usage; `taskCompleted()` / `taskFailed()` / `escalated()` per your outcome logic.
- **LangChain (JS)** — a callback handler mapping `handleChainStart` → `conversationStarted`, `handleToolStart` → `capability`, `handleLLMEnd` → `capability('turn', cost)`.
- **OpenAI Agents (Python)** — the same three POSTs with `requests` from run hooks.
## 7. Connect AI coding agents (MCP)
Morvero ships a remote **MCP server** so coding agents can read feedback data and instrument codebases themselves:
```bash
claude mcp add --transport http morvero https://morvero.com/mcp
```
First use opens the browser: sign in (passkey or email link), review the consent screen, Allow. The connector acts with *your* role and workspace — OAuth 2.1 with PKCE, tokens stored hashed, revocable. Works with any MCP client supporting Streamable HTTP + OAuth (Claude Code, Claude.ai connectors, Cursor…).
| Tool | Purpose |
|---|---|
| `list_products` / `create_product` | Find or create the product a codebase belongs to (apps and AI-agent pilots) |
| `get_install_snippet` | Ready-to-paste widget snippet or agent-telemetry calls with the real API key, plus an AGENTS.md block to persist in the repo |
| `get_instrumentation_guide` | Platform guides: web, iOS, Android, AI-agent conventions |
| `list_feedback` | Real user comments & ratings, filterable |
| `list_backlog` / `get_backlog_item` | AI-suggested backlog with acceptance criteria and verbatim user evidence |
| `get_product_analytics` | Usage/sentiment totals + agent-pilot KPIs |
| `verify_install` | Confirms data is actually arriving post-install — working / silent / never-received, naming the likely blocker. The agent's definition of done |
Prompts that work well: "Add Morvero feedback tracking to this repo and verify it works" · "Implement the accepted backlog items for the Billing Portal" · "What are users complaining about on mobile? Fix the top issue." · "Instrument our support bot as an agent pilot".
Working examples for every integration surface (vanilla HTML, React, Next.js, Vue, Swift/Kotlin, agent telemetry) plus a portable install skill: https://github.com/Morvero/examples
The connector can read feedback and keys and create products; it can never touch billing or manage the team — it holds your role, nothing more.
## 8. AI: theme summaries & backlog suggestions
Available when the server has an AI API key (Anthropic or OpenAI) and the plan is Growth+. AI output assists reading — raw feedback stays the source of truth.
- **Theme summary** — clusters recent comments into named themes with sentiment and an example quote (regenerate at most daily).
- **Backlog suggestions** — the AI analyzes a window (7/30/90 days, ≥10 comments), classifies every comment (praise, feature request, bug report, complaint, question), and proposes up to 8 backlog items: title, user story, 2–4 testable acceptance criteria grounded in the comments, type, and evidence-derived priority. Criteria travel into Jira / Azure DevOps on export.
- **AI proposes, you dispose.** Every suggestion cites the real feedback behind it. Nothing enters the backlog or reaches Jira until a person clicks Accept; rejections teach the next run.
- Accepting marks cited feedback *actioned*; rejecting marks it *dismissed*; actioned always wins conflicts.
- Accepted items form a drag-to-reorder priority list; with Jira/Azure DevOps connected, accepted items export with evidence stats (AI-flagged bugs become Azure DevOps Bug work items). An auto-open switch can create tracker items automatically on accept. All decisions audit-logged.
- Screenshots and form captures are never sent to the AI — only ratings, page paths, and comment text.
## 9. Read the dashboards
- **Overview** — portfolio totals, usage/rating trends, products ranked by usage and rating, most/least used pages and functions, anomaly alerts. 7/30/90-day ranges.
- **Portfolio scorecard** — usage × sentiment quadrant: **Fund** (high/high), **Fix** (used but disliked), **Niche** (loved but small), **Retire?** (low/low). Median-split signals, not verdicts. Movers panel shows biggest changes vs. the prior period (Growth+).
- **Board report** (Growth+) — print-friendly executive report with a plain-language executive summary computed directly from the figures (no AI), stats with deltas, quadrant, movers, per-product scorecard, alerts, sample voices.
- **NPS trend** (Scale+), **custom date ranges & quarter presets** (Growth+), **returning-visitors share**, **triage health** (volume by status, share triaged/actioned, median time-to-triage).
- **Product detail** — per-product trend, rating distribution, page/function tables, embed snippet, recent feedback with attachments.
- Reading signals: high usage + low rating = fix; low/low = retire candidate; low usage + high rating = niche gem — check who uses it first. Least-used functions are your dead-feature list.
## 10. Team, sign-in & passkeys
- **Passwordless**: email → SAML SSO if your domain has it, else passkey, else single-use email link (15-min expiry).
- **SAML SSO** (Scale+): claim your email domain, paste IdP SSO URL + signing certificate, give the IdP the shown Entity ID + ACS URL. Users provision on first sign-in.
- Roles: **Owner** (governance), **Admin** (full product ops), **Member** (assigned products only), **Viewer** (read-only, no attachments, free — doesn't consume a paid seat).
- **Tribes, POs, squads** (Growth+): tribes with tribe leads (auto member-access + per-tribe rollups), per-product Product Owner, squads serving products. A member's reach = assigned ∪ squad ∪ PO'd ∪ tribe-led products.
- A **privacy-operator** flag lets a DPO manage retention/erasure without being Owner. Deactivating a teammate frees the seat and ends sessions instantly; the last active owner can never be removed.
- Platform support can never enter a workspace unless an Owner grants a time-boxed access window; all staff actions land in your own audit trail.
## 11. Plans
| Plan | Price | Products | Admins | Max sampling |
|---|---|---|---|---|
| Starter | Free | 2 | 2 | 5% |
| Growth | $99/mo flat | 15 | 10 | 25% |
| Scale | $499/mo flat | 50 | 25 | 50% |
| Enterprise | from $15,000/yr flat | 150–unlimited | Unlimited | 100% |
Always flat per portfolio — never per-user, per-MAU, or per-event. Starter→Growth→Scale are self-serve (Settings → Plan & usage); Enterprise is sales-assisted (annual invoice, SLA, DPA, security review support). Limits are API-enforced with a clear upgrade message.
## 12. Privacy & GDPR
- **Retention** — 30–730 days; older raw data purged automatically.
- **Workspace export** — everything as JSON (portability).
- **Data subject requests** — a visitor's only identifier is the `fbk_visitor` value in their browser's localStorage; export or permanently erase everything tied to it (audit-logged).
- No names, emails, IPs, or fingerprints are ever stored for visitors.
- **Country codes** — resolved in-process from the request IP at collection time; the IP is discarded immediately, only the two-letter code is stored. Includes GeoLite2 data by MaxMind.
## 13. Security & abuse protection
- Feedback requires a short-lived signed token minted per page load — replayed/scripted junk is rejected.
- Rate limits on all collection endpoints (per IP + visitor with per-IP backstop) plus a 5-feedback-per-visitor-per-product daily cap.
- Feedback-volume anomaly alerts flag possible rating poisoning on the Overview.
- Per-product domain allowlists (web) and app-ID allowlists (native), product kill switch, strict payload validation, full admin audit log.
- Disclosure policy and controls: https://morvero.com/security · https://morvero.com/.well-known/security.txt
## 14. Troubleshooting
**Feedback isn't arriving** — Is the product active? Do allowed domains include the widget's host? One visitor caps at 5 feedback/product/day and IP bursts throttle — wait a minute. If the panel said "Thank you", it reached the server.
**Function tracking isn't recording** — `MorveroFeedback.track()` inside the widget's own `