Concepts

Six primitives. The rest of Earshot is built on top.

Team

The billing + access boundary. A team has many members (admins or members) and many projects. Created during signup; usually one team per company.

Project

One widget install. Usually one production app. A project has:

  • An id like prj_q24wfw27mlh6 — shows up everywhere.
  • A public API key like pk_xxxxxxxxxxxx — embedded in the widget snippet. Rotate any time from the project settings page.
  • Optional integrations: at most one tracker (Linear or Jira) and any number of federation providers (PostHog, Sentry, LogRocket, Datadog).
  • A project brief the enrichment LLM loads on every submission.

Feedback

The unit of work. One feedback row carries:

  • A voice transcript (transcribed via Whisper) and the audio file.
  • An annotated screenshot of the host page at capture time.
  • A captured DOM snapshot (rrweb full-snapshot), recent console entries, recent network requests, browser/device/viewport/URL.
  • An LLM-derived structured record: title, verbose summary, priority, labels — grounded in the project brief + captured browser context.
  • Federation context per provider (replay URL, recent events, feature flags).
  • Identity (if identify() was called) — or null for anonymous submissions, with deferred-identity backfill.
  • Status (triage / backlog / in_progress / done / canceled), priority (0 = highest, 4 = lowest), labels.
  • An external_ref pointing to the linked tracker issue (Linear url, Jira key).
  • Comments (with author type human / agent / submitter).

Session

A short-lived row keyed by (project_id, session_id) used as the async progress tracker between the widget POST and the Inngest worker. Lifecycle:

  • processing — POST has uploaded the media + queued the worker.
  • done — worker has transcribed + enriched + ingested. Carries the resulting feedback_id.
  • error — anything failed; carries a human-readable message.

The row TTLs out after one hour. The widget treats the POST 202 as "filed" and doesn't normally wait, but a client that wants progress can poll GET /v1/feedback/session/<id>/state.

Federation provider

A read-side integration (PostHog / Sentry / LogRocket / Datadog). On every feedback submission Earshot calls each connected federation provider to pull session context for the affected URL — replay URL, recent events, feature flag values. The enrichment LLM sees this; the eventual ticket carries it.

You bring your own keys. Nothing about your existing observability changes.

Tracker

A write-side integration (Linear or Jira). On ingest, Earshot files the structured report as an issue in your tracker with full context attached. Two-way sync mirrors status changes and comments back to the issue.

How they fit

End user clicks launcher
  ↓
Widget captures: voice → audio + transcript, screenshot, DOM, console, network, identity, federation
  ↓
POST /v1/feedback → 202 "processing"
  ↓
Inngest worker: transcribe (Groq) → enrichment LLM grounded in project brief + context → ingest
  ↓
Feedback row persisted, federation pulled, tracker issue filed
  ↓
Inbox shows the row; Linear shows the ticket; submitter gets an email on resolve

The MCP exposes every step as a tool. An agent doing triage reads the same data from get_feedback that the dashboard renders.