Fruitalytics

First-party product analytics guide for humans and agents.

Fruitalytics

Fruitalytics is first-party product analytics for BYOB-generated apps and standalone websites. It gives each site a write key for event collection and a read token for dashboards and API access.

Register a website

Agents and humans can register a website directly by calling:

POST /v1/register
Content-Type: application/json

{
  "name": "Example Product",
  "origins": ["https://example.com"]
}

The response includes one-time install credentials:

site_id=site_...
write_key=pk_...
read_token=rt_...
site_admin_token=sa_...
auto_pageviews=true
auto_routes=true
autocapture=true
capture_errors=true
capture_network_errors=true
capture_performance=true
capture_scroll_depth=true
capture_outbound_links=true
capture_downloads=true
capture_rage_clicks=true
capture_impressions=true
script_src=https://.../analytics.js
hashed_script_src=https://.../analytics/analytics.<hash>.min.js
tracking_code=<script ...></script>
dashboard_url=https://.../dashboard?site_id=site_...&token=rt_...

Use tracking_code directly when installing Fruitalytics on the registered website.

Allowed websites

Each website can register multiple origins, and the allowlist can be changed later with the site's site_admin_token. By default there is no domain restriction: any http/https origin may self-register and send events, subject to the per-site origin allowlist and self-registration rate limits.

Operators can re-restrict the service-wide host policy with ANALYTICS_ALLOWED_ORIGIN_PATTERNS (comma-separated patterns, e.g. *.byob.studio,*.byob.page,localhost,127.0.0.1,::1). An empty value or a * pattern allows all hosts.

Manage origins

Site origin allowlists can be updated with the site's site_admin_token (returned at registration). The list below replaces the allowlist, adds origins, or removes origins:

PUT /v1/sites/<site_id>/origins
Content-Type: application/json
Authorization: Bearer <site_admin_token>

{ "origins": ["https://example.com", "http://localhost:5173"] }
POST /v1/sites/<site_id>/origins
Content-Type: application/json
Authorization: Bearer <site_admin_token>

{ "origins": ["https://another.example.com"] }
DELETE /v1/sites/<site_id>/origins
Content-Type: application/json
Authorization: Bearer <site_admin_token>

{ "origins": ["http://localhost:5173"] }

A site_admin_token only manages its own site. The read_token is read-only and cannot change origins. PUT with { "origins": [] } clears the allowlist. Origins must be http/https origins without credentials, paths, queries, or fragments. The admin UI at /admin also supports adding and removing origins per site.

Install tracking

Add the returned script tag before </body>:

<script
  async
  src="https://fruitalytics.example/analytics.js"
  data-site-id="site_..."
  data-write-key="pk_..."
  data-auto-pageviews="true"
  data-auto-routes="true"
  data-autocapture="true"
  data-capture-errors="true"
  data-capture-network-errors="true"
  data-capture-performance="true"
  data-capture-scroll-depth="true"
  data-capture-outbound-links="true"
  data-capture-downloads="true"
  data-capture-rage-clicks="true"
  data-capture-impressions="true"></script>

Generated tracking code records page_view on initial load, client-side route changes, clicks, form submissions, input/select/textarea changes, JavaScript errors, unhandled rejections, failed API calls, performance metrics, scroll depth, outbound/download clicks, rage clicks, and annotated impressions by default. Use data-auto-pageviews="false", data-auto-routes="false", data-autocapture="false", or the relevant data-capture-*="false" only when a site explicitly wants to opt out.

The write key can send events. It cannot read dashboards or create sites.

Track events

Custom JavaScript events:

window.fruitloop.track("cta_clicked", { location: "hero" });
window.fruitloop.page({ source: "manual" });
window.fruitloop.identify("user_123", { plan: "pro" });
window.fruitloop.flagEvaluated("new_checkout", "variant_a");
window.fruitloop.variantSeen("pricing_test", "b");
window.fruitloop.surveyResponse("nps_q1", { rating: 9 });
window.fruitloop.reset(); // clear signed-in user on sign-out
window.fruitloop.getUserId(); // current signed-in user id, or ''
window.fruitloop.flush();

data-fl-* attribute values are coerced to booleans and numbers before sending. scroll_depth thresholds and data-fl-impression elements re-fire per SPA route change. flush() requeues failed batches on 429/5xx up to 2 times.

Annotated HTML events:

<button data-fl-event="cta_clicked" data-fl-location="hero">
  Start
</button>

Broad autocapture is enabled by default:

<script
  async
  src="https://fruitalytics.example/analytics.js"
  data-site-id="site_..."
  data-write-key="pk_..."
  data-autocapture="true"
  data-auto-pageviews="true"></script>

Autocapture records clicks, form submissions, and input/select/textarea changes as $autocapture events. It does not collect input values.

Diagnostics and engagement events avoid request bodies and form values. URLs are stripped of query strings and hashes at ingest; referrer is kept. data-fl-* attribute values become booleans and numbers automatically.

Annotated impressions:

<section data-fl-impression="pricing" data-fl-plan="pro">
  ...
</section>

Read analytics

Open the returned dashboard_url, or call:

GET /v1/dashboard?site_id=<site_id>&limit=50
Authorization: Bearer <read_token>

The read token can read analytics for its site. It cannot send events or create sites.

Query funnels

Define a funnel from your event stream with funnel.step keys (read-token protected):

GET /v1/funnels?site_id=<site_id>&funnel_steps=activation.signup_started,activation.signup_completed
Authorization: Bearer <read_token>

Each step counts distinct resolved users (the non-empty user_id associated with each anonymous browser, else anonymous_id), so conversion stays consistent before and after login. Returns { funnel: { startUsers, steps: [{ step, users, conversion }] } }. The dashboard has a funnel step editor for this.

List identified users

GET /v1/users?site_id=<site_id>&limit=50&include_anonymous=true&funnel_steps=activation.signup_started
Authorization: Bearer <read_token>

Returns per user { id, traits (last identify), firstSeen, lastSeen, sessionCount, eventCount, furthestStep }. Traits can contain emails or names; keep them behind read-token auth.

Protection

Fruitalytics applies origin checks, self-registration rate limits, and failed-token throttling. Browser events must come from an origin that is both registered to the site and allowed by the service-wide host policy.