{"openapi":"3.1.0","info":{"title":"Morvero Public API","version":"1.0.0","description":"The public surfaces of Morvero — portfolio-level product intelligence for enterprise software.\n\nTwo independent APIs share this document:\n\n1. **Collection API** (`/api/widget-config`, `/api/collect/*`) — what the browser widget, native mobile apps, and AI-agent telemetry emitters call to send usage and feedback data. Authenticated by a per-product key (`fbk_…`), which is public by design; abuse is controlled by origin/app-id allowlists, short-lived submission tokens, per-visitor caps, and rate limits.\n2. **Read API v1** (`/api/v1/*`) — read-only aggregates for BI tools, authenticated with an Enterprise Bearer key (`pak_…`) created in the console under Settings → Read API keys. Aggregates and comments only — never attachments.\n\nGuides: https://morvero.com/docs (Markdown: https://morvero.com/docs.md) · Machine summary: https://morvero.com/llms.txt · MCP server for coding agents: https://morvero.com/mcp · Working examples: https://github.com/Morvero/examples","contact":{"email":"hello@morvero.com","url":"https://morvero.com"}},"servers":[{"url":"https://morvero.com"}],"tags":[{"name":"Collection","description":"Send pageviews, feature-usage actions, feedback, and NPS. Used by the widget, mobile apps, and AI-agent pilots."},{"name":"Read API v1","description":"Read-only aggregates for BI tools. Enterprise plan; Bearer `pak_…` keys; 120 requests/min."}],"paths":{"/api/widget-config":{"get":{"tags":["Collection"],"operationId":"getWidgetConfig","summary":"Fetch widget config and a short-lived submission token","description":"Returns the product's widget configuration and a submission `token` (~15 min TTL) required by `/api/collect/feedback` and `/api/collect/nps`. Fetch right before submitting; on `401 token_invalid`, re-fetch and retry once. Rate limit: 60/min per IP.","parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string"},"description":"Product key (`fbk_…`)"},{"$ref":"#/components/parameters/AppIdHeader"}],"responses":{"200":{"description":"Widget configuration","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"string"},"samplingRate":{"type":"number","description":"0–1 share of visitors who get a proactive prompt"},"token":{"type":"string","description":"Short-lived submission token for feedback/NPS"},"tokenTtlMs":{"type":"integer"},"feedbackEnabled":{"type":"boolean"},"theme":{"type":"object","properties":{"color":{"type":"string"},"position":{"type":"string","enum":["br","bl"]}}},"locale":{"type":"string","description":"\"auto\" or a fixed locale (en/tr/de/fr/es)"},"strings":{"type":["object","null"],"description":"Enterprise per-string overrides"},"surveyMode":{"type":"string","enum":["rating","nps"]}}}}}},"403":{"$ref":"#/components/responses/OriginNotAllowed"},"404":{"$ref":"#/components/responses/UnknownKey"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/collect/pageview":{"post":{"tags":["Collection"],"operationId":"collectPageview","summary":"Record a pageview (or an agent conversation start)","description":"No token needed. For AI-agent pilots, one call per conversation with `path` = entry surface (`/slack`, `/web-chat`, `/vscode`). Rate limits: 240/min per IP+key, 2,400/min per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","path","visitorId"],"properties":{"key":{"type":"string","description":"Product key (`fbk_…`)"},"path":{"type":"string","maxLength":500,"description":"Route, screen name, or agent entry surface"},"title":{"type":"string","maxLength":300},"visitorId":{"$ref":"#/components/schemas/VisitorId"},"cohort":{"$ref":"#/components/schemas/Cohort"}}}}}},"responses":{"201":{"$ref":"#/components/responses/Created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/OriginNotAllowed"},"404":{"$ref":"#/components/responses/UnknownKey"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/collect/action":{"post":{"tags":["Collection"],"operationId":"collectAction","summary":"Record a feature use (or an agent capability call / task outcome)","description":"No token needed. For AI-agent pilots: `action` = capability name with optional `cost` in USD; the reserved action names `task-completed`, `task-failed`, and `escalated-to-human` drive the pilot scorecard KPIs. Rate limits: 240/min per IP+key, 2,400/min per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","action","visitorId"],"properties":{"key":{"type":"string"},"action":{"type":"string","maxLength":200,"description":"Stable kebab-case identifier, e.g. \"export-csv\". Reserved for agents: task-completed, task-failed, escalated-to-human"},"path":{"type":"string","maxLength":500,"default":"/"},"visitorId":{"$ref":"#/components/schemas/VisitorId"},"cost":{"type":"number","minimum":0,"maximum":1000,"description":"Optional cost of this call/turn in USD (agent telemetry)"},"cohort":{"$ref":"#/components/schemas/Cohort"}}}}}},"responses":{"201":{"$ref":"#/components/responses/Created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/OriginNotAllowed"},"404":{"$ref":"#/components/responses/UnknownKey"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/collect/feedback":{"post":{"tags":["Collection"],"operationId":"collectFeedback","summary":"Submit user feedback (rating or thumbs, optional comment and attachments)","description":"Requires a fresh `token` from `/api/widget-config`. Chat/agent UIs may send `thumb: \"up\"|\"down\"` instead of `rating`. Server-side caps: 5 feedback items per visitor per product per day; rate limits 10/min per IP+visitor, 60/min per IP. Form values with sensitive-looking field names (password, card, cvv, ssn, token…) are discarded server-side regardless of what the client sends.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","token"],"properties":{"key":{"type":"string"},"token":{"type":"string","description":"From /api/widget-config, ~15 min TTL"},"rating":{"type":"integer","minimum":1,"maximum":5,"description":"Required unless `thumb` is sent"},"thumb":{"type":"string","enum":["up","down"],"description":"Alternative to rating (up=5, down=1)"},"comment":{"type":"string","maxLength":4000},"path":{"type":"string","maxLength":500,"default":"/"},"title":{"type":"string","maxLength":300},"trigger":{"type":"string","enum":["manual","prompt"],"default":"manual"},"platform":{"type":"string","enum":["web","ios","android","api","agent"],"default":"web"},"visitorId":{"$ref":"#/components/schemas/VisitorId"},"cohort":{"$ref":"#/components/schemas/Cohort"},"screenshot":{"type":"string","description":"Optional `data:image/jpeg;base64,…` or PNG data URL, ≤ ~2 MB. Only with explicit user consent."},"formData":{"type":"array","maxItems":50,"items":{"type":"object","properties":{"field":{"type":"string","maxLength":100},"value":{"type":"string","maxLength":300}}},"description":"Optional form context; sensitive-looking fields are scrubbed server-side"}}}}}},"responses":{"201":{"$ref":"#/components/responses/Created"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Invalid or expired submission token (`code: token_invalid`) — re-fetch /api/widget-config and retry once","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Origin/app not allowed, or the product is usage-monitoring-only (`code: feedback_disabled`)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/UnknownKey"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/collect/nps":{"post":{"tags":["Collection"],"operationId":"collectNps","summary":"Submit an NPS answer (0–10)","description":"Requires a fresh `token` from `/api/widget-config`. Cap: 2 NPS answers per visitor per product per day. Rate limit: 30/min per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","token","score"],"properties":{"key":{"type":"string"},"token":{"type":"string"},"score":{"type":"integer","minimum":0,"maximum":10},"comment":{"type":"string","maxLength":1000},"path":{"type":"string","maxLength":500,"default":"/"},"visitorId":{"$ref":"#/components/schemas/VisitorId"}}}}}},"responses":{"201":{"$ref":"#/components/responses/Created"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"description":"Invalid or expired submission token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Origin/app not allowed, or feedback collection disabled for this product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"$ref":"#/components/responses/UnknownKey"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/overview":{"get":{"tags":["Read API v1"],"operationId":"v1Overview","summary":"Workspace-wide aggregates for the selected window","security":[{"readApiKey":[]}],"parameters":[{"$ref":"#/components/parameters/Days"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"}],"responses":{"200":{"description":"Portfolio totals, per-product stats, trends, most/least used pages and functions, and recent feedback (comments only — attachment fields are stripped).","content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer"}},"additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/UnauthorizedBearer"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/products":{"get":{"tags":["Read API v1"],"operationId":"v1Products","summary":"List products with usage stats","description":"Widget keys are never returned by the read API.","security":[{"readApiKey":[]}],"responses":{"200":{"description":"Products with pageviews, visitors, feedback counts and average rating","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string","enum":["app","agent"]}},"additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/UnauthorizedBearer"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/products/{id}/analytics":{"get":{"tags":["Read API v1"],"operationId":"v1ProductAnalytics","summary":"Per-product analytics for the selected window","description":"Includes NPS stats; products of kind `agent` additionally return `agentKpis` (task success rate, escalation rate, cost per conversation, cost per completed task).","security":[{"readApiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"$ref":"#/components/parameters/Days"},{"$ref":"#/components/parameters/From"},{"$ref":"#/components/parameters/To"}],"responses":{"200":{"description":"Usage trend, rating distribution, page/function tables, recent feedback (no attachments), `nps`, and `agentKpis` for agent products","content":{"application/json":{"schema":{"type":"object","properties":{"product":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"kind":{"type":"string"}}},"days":{"type":"integer"}},"additionalProperties":true}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/UnauthorizedBearer"},"404":{"description":"Product not found in this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/v1/feedback":{"get":{"tags":["Read API v1"],"operationId":"v1Feedback","summary":"List feedback items (comments and ratings — never attachments)","security":[{"readApiKey":[]}],"parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["new","reviewed","actioned","dismissed"]}},{"name":"product_id","in":"query","schema":{"type":"integer"}},{"name":"days","in":"query","schema":{"type":"integer","default":90}},{"name":"limit","in":"query","schema":{"type":"integer","default":100}}],"responses":{"200":{"description":"Feedback rows (attachment fields stripped)","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"401":{"$ref":"#/components/responses/UnauthorizedBearer"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"readApiKey":{"type":"http","scheme":"bearer","description":"Enterprise read key (`pak_…`) from Settings → Read API keys"}},"parameters":{"AppIdHeader":{"name":"X-Morvero-App-Id","in":"header","required":false,"schema":{"type":"string"},"description":"Native app bundle id (e.g. com.company.app); required when the product restricts allowed app IDs"},"Days":{"name":"days","in":"query","schema":{"type":"integer","minimum":1,"maximum":365,"default":30},"description":"Window size in days (ignored when from/to are set)"},"From":{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"Custom window start (YYYY-MM-DD, use with `to`, ≤366 days)"},"To":{"name":"to","in":"query","schema":{"type":"string","format":"date"},"description":"Custom window end (YYYY-MM-DD)"}},"schemas":{"VisitorId":{"type":"string","maxLength":100,"description":"Stable anonymous/pseudonymous id. Web: random localStorage id. Mobile: random UUID in Keychain/EncryptedSharedPreferences — never IDFA/AAID. Agents: salted hash of the employee id — never an email."},"Cohort":{"type":"string","maxLength":40,"description":"Optional coarse group label (department, region). Aggregate-only with a k-anonymity floor; never send usernames or employee ids."},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}}}},"responses":{"Created":{"description":"Accepted","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"}}}}}},"BadRequest":{"description":"Validation error — the `error` field says which","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnknownKey":{"description":"Unknown or inactive product key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"OriginNotAllowed":{"description":"This origin or app id is not allowed for this product key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"UnauthorizedBearer":{"description":"Invalid or revoked API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit or per-visitor cap hit — drop or queue, never crash; retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}