ServiceLink EXOS | Analytics

Quick Start

EXOS Analytics is an operational intelligence platform built on ClickHouse Cloud, the HyperDX browser SDK, and Vercel serverless functions. It runs session replay, product analytics, process mining, and AI-powered querying over your own OpenTelemetry data — a supplement to product-analytics tools like Pendo or Amplitude, not a drop-in replacement for them.

1

Explore the live portal

Start with Process Mining to see operator patterns, then jump to Session Replay for full DOM recordings.

2

Ask a question in natural language

Open Ask AI and try: "Which operators handle the most orders?" or "Show me sessions with rage clicks"

3

Read the architecture docs

Dive into the documents below for system architecture, data model, storage tiering, and competitive analysis.

Architecture Overview

All browser telemetry flows through a single pipeline: HyperDX SDK captures DOM snapshots, traces, and console logs, sends them through an OpenTelemetry Collector, and lands them in three ClickHouse tables. The portal and API layer read from those tables to power every view.

                         EXOS Analytics -- System Architecture

  +------------------+         +-------------------+         +------------------------+
  |  Browser Client  |  OTEL   |  OTEL Collector   |  Batch  |   ClickHouse Cloud     |
  |  (HyperDX SDK)   | ------> | collector.exos-   | ------> |   GCP us-central1      |
  |                  |  HTTP   |  demo.com         |  Insert |                        |
  +------------------+         +-------------------+         |  +------------------+  |
        |                                                     |  | otel_traces      |  |
        | Captures:                                           |  | (clicks, HTTP,   |  |
        |  - DOM snapshots (rrweb)                            |  |  page loads,     |  |
        |  - User interactions                                |  |  domain events)  |  |
        |  - HTTP requests                                    |  +------------------+  |
        |  - Console output                                   |  | hyperdx_sessions |  |
        |  - Web Vitals                                       |  | (rrweb DOM       |  |
        |  - Domain events                                    |  |  recordings)     |  |
                                                              |  +------------------+  |
                                                              |  | otel_logs        |  |
                                                              |  | (console output) |  |
  +------------------+         +-------------------+          |  +------------------+  |
  |  Static Portal   |  API    |  Serverless Fns   |  SQL    |                        |
  |  (Vercel)        | <-----> | (Vercel)          | <-----> |  Join key:             |
  |  Vanilla HTML    |  REST   | 24 endpoints      |  Query  |  rum.sessionId         |
  +------------------+         +-------------------+         +------------------------+
                                      |
                                      | /api/insights calls
                                      v
                               +-------------------+
                               |  Claude (Azure)   |
                               |  (Ask AI)         |
                               +-------------------+

ClickHouse Cloud

Columnar OLAP on GCP us-central1. Sub-second analytical queries.

HyperDX SDK

OTEL-compatible browser SDK: DOM replay, traces, console, network.

OTEL Collector

Receives telemetry, batches inserts to ClickHouse.

API (Vercel Functions)

24 serverless endpoints for stats, sessions, insights, funnels, OCPM, and more.

Static Portal (Vercel)

Vanilla HTML + Tailwind CSS. No build step, no framework.

Ask AI (Claude)

Natural language to SQL. Conversation memory with follow-ups.

Concepts & Data Model

Three ideas explain everything in EXOS Analytics: the OpenTelemetry model (how events are shaped), properties (how data is segmented by product), and the core tables (where it lands in ClickHouse). The detailed column-level schema and example queries follow in Data Model below.

The OpenTelemetry model

EXOS uses the OpenTelemetry (OTel) data model end-to-end. Three nested concepts map directly onto ClickHouse columns:

Trace

A complete workflow or browsing session, identified by TraceId. All spans that share a TraceId belong to the same trace; spans also share a session via ResourceAttributes['rum.sessionId'].

Span

One event — a click, an HTTP call, a page view, or a business event like order.closed. Each span is one row in otel_traces, named by SpanName, timed by Timestamp + Duration.

Attributes

Key-value metadata stored as Map(String, String). SpanAttributes holds event-specific keys (operator.id, lob, order.id); ResourceAttributes holds session-level keys. Access with bracket syntax: SpanAttributes['operator.id'] — never as a bare column.

Properties — segmenting by ServiceName

Every span carries a ServiceName. EXOS groups services into properties — the distinct products and sites that report telemetry to the same store. Scope any view to a single property by passing ?workspace=<ServiceName>; /api/workspaces lists the properties currently reporting data.

PropertyServiceNameWhat it is
EXOS Operationsexos-orders, exos-delivery, exos-inspection, exos-scheduling, exos-assignments, exos-internal-tools, exos-portal-uiThe EXOS order-operations platform (the primary product under analysis)
Analytics Portalclickstack-portalThis analytics portal itself — it is instrumented with its own SDK (dogfooding)
RadLoradlo-webThe RadLo web application
Docsbeppe-dotfiles-docsA documentation site reporting RUM telemetry

The default product-analytics views scope to EXOS + PortalServiceName LIKE 'exos-%' OR ServiceName = 'clickstack-portal' (defined once in functions/api/_shared.js as SERVICE_FILTER). Other properties are queryable explicitly via the workspace parameter.

Core tables

Telemetry lands in the default database (raw OTel, collector-owned); EXOS process-mining derivations live in the exos database. Row counts are from a live read of the backing ClickHouse instance on 2026-06-23.

default — OpenTelemetry + session replay

TableRowsRole
otel_traces~792,000Spans: browser RUM + business events. The primary analytics table.
hyperdx_sessions~414,800rrweb DOM recordings — the session-replay payload (ScopeName = 'rum.rr-web').
otel_logs20,648Captured browser console output and structured app logs.
otel_metrics_gauge / _histogram / _sum~622KWeb Vitals and application metrics.
logs, errorsviewsConvenience views over the tables above.

exos — process mining + platform reference

TableRowsRole
ocpm_order_events29,485Object-centric event log driving the OCPM dashboard (see OCPM).
mortgage_telemetry2,079Domain telemetry for the mortgage workflow.
session_enrichment, operator_paths, object_interactions_aggstub / emptyDerived-aggregate tables defined for process-mining views but currently unpopulated (operator_paths: 1 row, object_interactions_agg and mv_object_interactions: 0 rows; session_enrichment: 42 rows). Process-mining surfaces read live from otel_traces instead.
api_endpoints, service_catalog, work_order_fields, bundle_models, platform_metrics, wiki_pageslookupSmall platform-reference / lookup tables.

Only default and exos are reachable from any portal surface. See Access & Auth for how isolation is enforced.

Documentation

The live portal surfaces are the canonical reference. Detailed design documents (system architecture, data patterns, OCPM, storage tiering, competitive analysis) are maintained in the project repository and are intentionally not served on the public portal.

Data Architecture

All browser telemetry is captured by the HyperDX Browser SDK and sent to the OTEL Collector at collector.exos-demo.com, which writes to three ClickHouse tables. Every table shares a common join key: ResourceAttributes['rum.sessionId'].

Data Flow

graph TD
    A[Browser SDK HyperDX] -->|DOM snapshots
rrweb format, video replay| B[hyperdx_sessions
~415K events] A -->|User events
clicks, HTTP, page loads, domain events| C[otel_traces
~792K spans] A -->|Console output
log, warn, error, debug| D[otel_logs
~21K logs] classDef default fill:#1e1e2e,stroke:#333,color:#d4d4d4; classDef edgeLabel fill:#1e1e2e,color:#d4d4d4,stroke:none;

All joined by: ResourceAttributes['rum.sessionId']

otel_traces

Every user interaction, HTTP request, page load, performance event, and domain event. This is the primary analytics table.

Key columns

ColumnTypeDescription
TimestampDateTime64Event time (nanosecond precision)
SpanNameStringEvent type (see taxonomy below)
DurationUInt64Span duration in nanoseconds
SpanAttributesMap(String, String)Event-specific metadata (http.url, visitor.*, operator.*, etc.)
ResourceAttributesMap(String, String)Session-level metadata (rum.sessionId, service.name, etc.)

SpanName taxonomy

CategorySpanNameCountMeaning
User Interactions
click1,123Mouse click on any element
mousedown305Mouse button pressed
mouseup286Mouse button released
Network
HTTP POST10,869Outbound HTTP POST (API calls)
HTTP GET829Outbound HTTP GET (page/asset loads)
resourceFetch1,412Resource timing (scripts, stylesheets, images)
Performance
longtask4,668Main thread blocked >50ms
webvitals283Core Web Vitals (LCP, FID, CLS, TTFB)
documentLoad149Full page load timing
Navigation
visibility262Tab visibility change (focus/blur)
page.order_detail519Navigated to order detail view
page.scheduling373Navigated to scheduling view
page.inbox356Navigated to inbox
page.documents347Navigated to documents view
Domain Events
email.read767Operator opened an email
email.received748Inbound email arrived
email.sent711Operator sent an email
review.started187Review process initiated
report.submitted177Report submitted to client
Replay
record init149rrweb recording session started

Example query

SELECT SpanName, count() as cnt FROM otel_traces WHERE Timestamp >= now() - INTERVAL 7 DAY GROUP BY SpanName ORDER BY cnt DESC LIMIT 20

Used by: Operator Patterns, Order Lifecycle, Product Analytics, Session Replay drill-down tabs

hyperdx_sessions

Full DOM recording data captured by rrweb. Each row contains a batch of rrweb events for a session, enabling pixel-perfect video replay.

Key columns

ColumnTypeDescription
session_idStringSession identifier (matches rum.sessionId)
eventsString (JSON)Serialized array of rrweb events
timestampDateTime64Batch recording time

Example query

SELECT session_id, count() as batches, min(timestamp) as started FROM hyperdx_sessions WHERE timestamp >= now() - INTERVAL 7 DAY GROUP BY session_id ORDER BY started DESC LIMIT 10

Used by: Session Replay player, activity heatbar

otel_logs

Console output captured from the browser. Every console.log(), console.warn(), console.error(), and console.debug() call is recorded.

Key columns

ColumnTypeDescription
TimestampDateTime64When the log was emitted
SeverityTextStringinfo (13,199), debug (7,422), warn (27)
BodyStringLog message content
ResourceAttributesMap(String, String)Session-level metadata (rum.sessionId, etc.)

Example query

SELECT SeverityText, count() as cnt FROM otel_logs WHERE Timestamp >= now() - INTERVAL 7 DAY GROUP BY SeverityText ORDER BY cnt DESC

Used by: Session Replay Console tab, Site Health error groups

Visitor Enrichment

On every page load, the portal calls /api/visitor to resolve the user's IP into geo, device, and network attributes via HyperDX.setGlobalAttributes().

AttributeSourceExample
visitor.citygeo.cityChicago
visitor.regiongeo.regionIllinois
visitor.countrygeo.countryUS
visitor.orgnetwork.asOrganizationComcast Cable
visitor.asnnetwork.asn7922
visitor.browserdevice.browserChrome
visitor.browser_versiondevice.browserVersion122.0.0.0
visitor.osdevice.osmacOS
visitor.os_versiondevice.osVersion14.3.1
visitor.device_typedevice.typedesktop
visitor.ipvisit.ip73.162.x.x
visitor.languagevisit.acceptLanguageen-US
visitor.returningvisit.isReturningtrue
visitor.visit_countvisit.visitCount14
visitor.refererrequest.refererhttps://google.com/

Session Identity

When a user signs in via Firebase Auth, their identity is written to all subsequent spans using HyperDX.setGlobalAttributes():

user.emailuser.nameuser.uid

Session Replay Architecture

Session replay reconstructs a pixel-perfect video of what the user saw and did. It combines rrweb DOM snapshots from hyperdx_sessions with behavioral telemetry from otel_traces and otel_logs.

How rrweb Records

The HyperDX SDK uses rrweb (v2.0.0-alpha.4) to capture the DOM. Three event types matter:

type 4 (Meta) -- Records viewport dimensions and page URL. Emitted at session start and on each navigation.

type 2 (Full Snapshot) -- Complete DOM serialization. The "keyframe" the replayer needs to build the page.

type 3 (Incremental Snapshot) -- Diffs: DOM mutations, mouse movements, scroll positions, input changes, viewport resizes.

How the Replayer Works

The rrweb Replayer reconstructs the page inside a sandboxed <iframe>. It deserializes the full snapshot (type 2) to build the initial DOM, then applies incremental events (type 3) in timestamp order. Compiled Tailwind CSS is injected so the replay retains original styling.

Seek-to-Time Mapping

When you click a row in any drill-down tab, the player jumps to that moment:

offset_ms = otel_traces.Timestamp - rrweb_first_event.timestamp

The absolute timestamp from otel_traces is converted to a millisecond offset from the first rrweb event. Implemented in seekToAbsTime().

Activity Heatbar

48px SVG waveform below the replay video showing activity density.

Bucketing: Up to 400 time buckets (min 50, 2px per bucket).

Weighted signals: click: 1.0, mousedown: 0.8, HTTP POST: 0.6, HTTP GET: 0.5, resourceFetch: 0.4, longtask: 1.5, documentLoad: 2.0. Scroll: 0.2.

Rendering: Catmull-Rom spline SVG. Errors = red circles, rage clicks = amber triangles, navigations = blue diamonds.

Interaction: Click to seek. White playhead tracks current position.

Drill-Down Tabs

Five tabs, each querying a different slice. Every row has a clickable timestamp that seeks the replay.

TabData SourceSpanName Filter
Eventsotel_tracesclick, mousedown, mouseup, visibility, page.*
Networkotel_tracesHTTP POST, HTTP GET, resourceFetch
Consoleotel_logsAll severity levels
Errorsotel_logs + otel_tracesSeverityText = 'error' + error-status traces
Perfotel_traceswebvitals, longtask, documentLoad

Frustration Signal Detection

dead-click-tracker.js detects four frustration signals via HyperDX.addAction():

SignalActionRule
Rage Clickrage_click>4 clicks on same element within 2s
Dead Clickdead_clickNo DOM mutation within 500ms of click
Excessive Scrollexcessive_scroll>3 direction changes AND >130% doc height
Quick Backquick_backA->B->A within 15 seconds

Query Cookbook

Use Ask AI for natural language queries or write SQL directly.

Natural Language Examples

Ask

"Which operators handle the most orders?"

Follow up

"Now filter to closing LOB"

Ask

"Show me sessions with rage clicks from returning visitors"

Ask

"What are the slowest API calls this week?"

SQL Examples

Sessions with rage clicks

SELECT ResourceAttributes['rum.sessionId'] as session_id, count() as rage_count
FROM otel_traces WHERE SpanName = 'rage_click'
GROUP BY session_id ORDER BY rage_count DESC

Slowest HTTP requests

SELECT SpanAttributes['http.url'] as url, avg(Duration) / 1e6 as avg_ms, count() as calls
FROM otel_traces WHERE SpanName IN ('HTTP POST', 'HTTP GET')
GROUP BY url ORDER BY avg_ms DESC LIMIT 10

Sessions from returning visitors in Chicago

SELECT DISTINCT ResourceAttributes['rum.sessionId'] as session_id
FROM otel_traces
WHERE SpanAttributes['visitor.city'] = 'Chicago'
  AND SpanAttributes['visitor.returning'] = 'true'

Web Vitals by page

SELECT SpanAttributes['location.href'] as page,
       avg(toFloat64(SpanAttributes['lcp'])) as avg_lcp_ms,
       avg(toFloat64(SpanAttributes['fid'])) as avg_fid_ms
FROM otel_traces WHERE SpanName = 'webvitals'
GROUP BY page ORDER BY avg_lcp_ms DESC

Console errors by session

SELECT ResourceAttributes['rum.sessionId'] as session_id, count() as error_count,
       groupArray(10)(Body) as sample_messages
FROM otel_logs WHERE SeverityText = 'error'
GROUP BY session_id ORDER BY error_count DESC LIMIT 10
Open Ask AI

Process Mining

EXOS performs legitimate process mining using ClickHouse's native sequence and window functions. No interviews, no manual process mapping, no proprietary query language.

Operator Patterns

The Operator Patterns page ranks operators by activity volume, task distribution, and automation opportunity.

Order Lifecycle

Three analysis modes:

Sequences -- Top 10 operator paths via groupArray() with AI-generated narrative insights.

Bottlenecks -- Transition timing via lagInFrame() and P95 via quantile(0.95).

Funnels -- Conversion analysis via windowFunnel().

ClickHouse Primitives

Seven functions power the analysis. The /api/insights endpoint self-documents every primitive.

FunctionPurposeCelonis Equivalent
sequenceMatch()Happy path conformanceProcess Conformance
sequenceCount()Rework loop detectionRework Analysis
lagInFrame()Transition timingBottleneck Analysis
windowFunnel()Funnel conversionConversion Funnel
groupArray()Variant pathsProcess Variants
quantile()P95 bottlenecksKPI Thresholds
rolling z-scoreAnomaly detectionAnomaly Alerts

Process Mining SQL Examples

Conformance check

SELECT countIf(matched) as conforming, count() as total,
       round(conforming / total * 100, 1) as conformance_pct
FROM (
  SELECT SpanAttributes['order.id'] as order_id,
    sequenceMatch('(?1)(?2)(?3)')(toDateTime(Timestamp),
      SpanName = 'email.received', SpanName = 'review.started',
      SpanName = 'report.submitted') as matched
  FROM otel_traces WHERE SpanAttributes['order.id'] != '' GROUP BY order_id
)

Rework detection

SELECT SpanAttributes['order.id'] as order_id,
  sequenceCount('(?1)(?2)')(toDateTime(Timestamp),
    SpanName = 'review.started', SpanName = 'revision.requested') as rework_loops
FROM otel_traces WHERE SpanAttributes['order.id'] != ''
GROUP BY order_id HAVING rework_loops > 0 ORDER BY rework_loops DESC

Transition bottlenecks

SELECT step_from, step_to, round(avg(gap_hours), 1) as avg_hours,
       round(quantile(0.95)(gap_hours), 1) as p95_hours
FROM (
  SELECT SpanName as step_to,
    lagInFrame(SpanName) OVER (PARTITION BY SpanAttributes['order.id'] ORDER BY Timestamp) as step_from,
    dateDiff('hour', lagInFrame(Timestamp) OVER (PARTITION BY SpanAttributes['order.id'] ORDER BY Timestamp), Timestamp) as gap_hours
  FROM otel_traces WHERE SpanAttributes['order.id'] != ''
) WHERE step_from != '' GROUP BY step_from, step_to ORDER BY p95_hours DESC LIMIT 10

SDK & Instrumentation

HyperDX Browser SDK

Every page includes the HyperDX SDK for DOM replay, console, network, and interaction traces.

<script src="https://www.unpkg.com/@hyperdx/browser@0.22.0/build/index.js"></script>
<script>
  window.HyperDX && window.HyperDX.init({
    apiKey: 'H06oX4xamF3QHsFN0hJn', service: 'clickstack-portal',
    url: 'https://collector.exos-demo.com',
    consoleCapture: true, advancedNetworkCapture: true,
    disableReplay: false, maskAllText: false, maskAllInputs: true,
  });
</script>

consoleCapture: true -- all console output to otel_logs

advancedNetworkCapture: true -- records headers/bodies for HTTP spans

disableReplay: false -- enables rrweb DOM snapshots

maskAllInputs: true -- redacts form field values for privacy

Visitor Enrichment

On page load, fetches visitor metadata and attaches to all spans:

fetch('/api/visitor').then(r => r.json()).then(v => {
  window.HyperDX && window.HyperDX.setGlobalAttributes({
    'visitor.country': v.geo?.country || '',
    'visitor.city':    v.geo?.city || '',
    'visitor.org':     v.network?.asOrganization || '',
    'visitor.browser': v.device?.browser || '',
    // ... (15 attributes total, see Data Architecture)
  });
});

Firebase Auth Identity Flow

Auth module attaches identity to all subsequent telemetry:

// auth.js onAuthStateChanged:
if (window.HyperDX) {
  window.HyperDX.setGlobalAttributes({
    'user.email': userData.email,
    'user.name':  userData.displayName,
    'user.uid':   userData.uid,
  });
}

Custom Domain Events

Logged via HyperDX.addAction(), appearing as spans in otel_traces:

HyperDX.addAction('rage_click', { element: 'button#submit', count: 6 });
HyperDX.addAction('email.sent', { order_id: '12345', template: 'scheduling_confirmation' });

Auth Bypass for Testing

For Playwright, Cypress, or manual testing:

https://exos-demo.com?skipAuth=true

Sets localStorage.exos_skip_auth = 'true', persists across loads. Clear to re-enable auth.

Object-Centric Process Mining

OCPM extends traditional case-centric analysis by recognizing that events can belong to multiple object types simultaneously. EXOS implements the first ClickHouse-native OCPM using standard SQL -- no proprietary query language required.

Why OCPM?

Case-centric mining sees one order at a time. OCPM reveals cross-object patterns:

Vendor contention -- when multiple orders compete for the same vendor, creating invisible bottlenecks

Document dependencies -- when a shared report blocks multiple downstream orders

Cross-category handoff delays -- the actual time lost when work transitions between Communication, Portal, and System Actions

Object Categories

Eight object categories derived from SpanName prefixes -- no schema changes needed:

CategorySpanName PrefixExamples
Communicationemail.*email.read, email.sent, email.received
Portalpage.*page.order_detail, page.scheduling, page.inbox
System Actionaction.*action.update_status, action.assign_vendor
Order Lifecycleorder.*order.created, order.closed
Quality Controlreview.*review.started
Documentreport.*report.submitted
Field Workinspection.*inspection.scheduled
Valuationappraisal.*appraisal.assigned

ClickHouse Primitives for OCPM

Five SQL queries power the OCPM dashboard, using eight ClickHouse primitives:

FunctionOCPM PurposeCelonis Equivalent
arrayJoin()Explode category arrays into co-occurrence pairsObject Interaction Graph
arrayDistinct()Deduplicate categories per orderN/A (PQL engine)
groupArray()Reconstruct event sequences per categoryProcess Variant Analysis
cityHash64()Fast variant hashing for countingProprietary indexing
topK()Approximate top-K path patternsTop Variants view
lagInFrame()Cross-category transition timingBottleneck Analysis (OCPM)
quantile()P50/P95 lifecycle durationsKPI Thresholds
dateDiff()Object lifecycle span computationThroughput Time

Example: Object Interaction Query

SELECT c1 AS category_a, c2 AS category_b,
  count() AS co_occurrence_count
FROM (
  SELECT oid,
    arrayJoin(categories) AS c1,
    arrayJoin(categories) AS c2
  FROM (
    SELECT SpanAttributes['order.id'] AS oid,
      arrayDistinct(groupArray(
        CASE WHEN SpanName LIKE 'email.%' THEN 'Communication'
             WHEN SpanName LIKE 'page.%' THEN 'Portal'
             WHEN SpanName LIKE 'action.%' THEN 'System Action'
        END
      )) AS categories
    FROM otel_traces
    WHERE SpanAttributes['order.id'] != ''
    GROUP BY oid
    HAVING length(categories) >= 2
  )
) WHERE c1 < c2
GROUP BY c1, c2
ORDER BY co_occurrence_count DESC

Uses arrayJoin() to create a cross-product of categories within each order, then counts co-occurrences.

Cost Comparison

CapabilityCelonis OCPMEXOS OCPM
Cost modelCommercial licenseRuns on existing ClickHouse
Query languagePQL (proprietary)Standard SQL
Setup timeWeeksZero
Schema changesRequiredNone

Comparison covers EXOS' SQL-native OCPM approach vs. a proprietary process-mining suite; it is not a like-for-like product replacement claim.

API & Query Reference

Every portal view is backed by a JSON endpoint under /api/*. All endpoints query ClickHouse in real time (no caching layer) and run as the least-privilege exos_readonly user. Responses are JSON with permissive CORS.

Conventions

Method: GET unless noted; /api/query and /api/sql are POST (JSON body).

Time window: hours — lookback in hours, clamped per-endpoint (commonly 1–720, default 168). Process-mining endpoints default to 720 (30 days).

Dimension filters (most analytics endpoints): workspace (a ServiceName / property), lob (line of business), operator (operator.id), task (core.task).

Errors: 400 for a missing/invalid required parameter, 500 with { "error": "…" } on a query failure.

Product analytics & metrics

EndpointPurposeKey parameters
GET /api/statsKPI snapshot: sessions, events, error rate.workspace, lob, operator, task, hours
GET /api/eventsRaw trace/log feed with search + pagination.table (otel_traces|otel_logs), hours, workspace, search, status, limit, offset, cursor, lob, operator, task
GET /api/funnelsStep conversion via windowFunnel().steps (CSV), hours, workspace, lob, operator, task, list
GET /api/retentionCohort retention matrix.first_action, return_action, granularity (daily|weekly), hours, workspace, lob, operator, task, list
GET /api/pathsUser-journey / path discovery.hours, depth, workspace, lob, operator, task
GET /api/errorsGrouped error analysis + timeline.hours, workspace, group, lob, operator, task
GET /api/workspacesLists properties (services) with event + session counts.
GET /api/lobsLists lines of business with volumes.
GET /api/filtersCascading filter options (LOBs, operators, tasks, mailboxes).lob, operator

Sessions & replay

EndpointPurposeKey parameters
GET /api/sessionsSession list; pass id for one enriched session's spans.id (TraceId), hours, limit (1–200), lob, operator, task, span
GET /api/sessions-by-contextSessions filtered by task / operator / object category.task, operator, category, hours, limit
GET /api/visitorResolves the caller's IP into geo/device/network attributes (called on page load).

Process mining

EndpointPurposeKey parameters
GET /api/operatorsOperator leaderboard; pass id for a single-operator profile.id, hours, lob, role
GET /api/insightsNarrative process-mining insights; self-documents the ClickHouse primitives used.hours
GET /api/ocpmObject-centric process mining (interactions, variants, lifecycle, actor-affinity).hours (default 720, max 2160)
GET /api/patternsAnomaly detection over span volume (rolling z-score).hours, min_hours, threshold
GET /api/task-detailDrill-down for one task / SpanName: sequences, entities, exit distribution.task (required), hours

AI & SQL

EndpointPurposeBody / parameters
POST /api/queryNatural-language → SQL (Claude on Azure), validated and executed read-only. Returns rows + the generated SQL.{ question } or { messages[] }
POST /api/sqlExecute a raw read-only SELECT/WITH. DDL/DML and cross-database refs are rejected.{ sql, database? (default|exos) }
GET /api/schemaColumn catalog — covers only 4 named tables (default.otel_traces, default.sessions, exos.mv_object_interactions, exos.ocpm_order_events), not the full set of queryable tables.
POST /api/explainAI plain-language summary of a result set.{ …, summarize? }

Both AI/SQL paths enforce an allowlist of { default, exos } and reject any reference to system or personal databases — see Access & Auth.

Example: request & response

KPI snapshot, scoped to one property

GET /api/stats?workspace=exos-orders&hours=168
{
  "sessions": <count>,
  "events": <count>,
  "error_rate": <0..1>,
  "window_hours": 168,
  "workspace": "exos-orders"
}

Illustrative shape only — field names and the exact response vary by endpoint, and the live values depend on the selected window and property. Use Ask AI to explore without writing SQL.

Run a read-only query directly

POST /api/sql
Content-Type: application/json

{ "sql": "SELECT ServiceName, count() AS spans FROM otel_traces WHERE Timestamp >= now() - INTERVAL 7 DAY GROUP BY ServiceName ORDER BY spans DESC" }

Access & Auth

The portal has two ways in — an authenticated sign-in for normal use, and a read-only demo mode for evaluation. Both reach exactly the same isolated demo dataset; neither can reach anything else on the cluster.

Sign-in (Firebase Auth)

Sign-in is handled by Firebase Authentication with two OAuth providers:

Microsoft (Entra / microsoft.com)Google

On a successful sign-in the user's identity (user.email, user.name, user.uid) is attached to subsequent telemetry via HyperDX.setGlobalAttributes(), and cached in localStorage (exos_auth_user) to avoid a login flash on navigation. Signing out clears the cache and restores the gate.

Demo access mode (?skipauth=true)

Appending ?skipauth=true to any URL (also skipAuth / skip_auth) bypasses the front-end sign-in overlay so reviewers and test automation can browse without a credential:

https://exos-demo.com/sessions?skipauth=true

Sets localStorage.exos_skip_auth = 'true' and persists across navigation. Clear it (localStorage.removeItem('exos_skip_auth')) to re-enable the sign-in gate.

Creates a labelled placeholder user for display only — it is not a real credential and grants no additional privilege.

What demo mode can — and can't — reach

Skipping the front-end gate does not widen data access. Data isolation is enforced server-side, in three independent layers, and applies identically to signed-in and skip-auth requests:

1

Least-privilege database user. Every query runs as exos_readonly, a ClickHouse user GRANTed SELECT on the default and exos databases only.

2

Engine-level read-only. That user runs with readonly=1, so table-function escapes (remote(), url(), s3(), merge()) are rejected by the engine, not just by application code.

3

App-layer allowlist. NL-to-SQL (/api/query) and raw SQL (/api/sql) independently allow only { default, exos } and reject cross-database references, DDL, and DML.

The result: the demo surfaces exactly the OpenTelemetry traces, session replay, logs, and process-mining tables described in Concepts & Data Model — and nothing else. Any other data co-resident on the instance is unreachable through every portal surface. Session replay additionally records with maskAllInputs: true, redacting form-field values before they leave the browser.

Keyboard Shortcuts

Press ? on any page. Press Esc to close.

Show keyboard shortcuts?
Close overlayEsc

Session Replay: Space (play/pause), Left/Right (seek), F (fullscreen).