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.