SudoQuery is a self-hostable product analytics platform. Your apps send events through a lightweight SDK. SudoQuery streams them through Kafka into ClickHouse (for analytics), OpenSearch (for search) and S3 (for archiving). A web dashboard then lets your team explore the data:
- Chat with your data: ask questions in plain English. An LLM assistant writes and runs ClickHouse queries for you and charts the answers.
- Discover: a Kibana-style event explorer with KQL search, filters, field stats and a histogram.
- Sessions: follow what a user did, event by event, on a timeline.
- SQL console: run ClickHouse SQL directly, with tabs and saved consoles.
- Live dashboards and saved metrics: bar, line, pie, funnel and Sankey charts.
- Event schema: browse and describe the events and properties you collect.
- Multi-tenant: organizations, workspaces, members, roles and invitations, with SSO through Keycloak.
flowchart LR
SDK["SDK / any HTTP client"] -->|POST /v1/events| C[events-collector]
C -->|"events.{org}.{workspace}"| K[(Kafka / Redpanda)]
K -->|Kafka engine| CH[(ClickHouse)]
K --> SO[sink-opensearch] --> OS[(OpenSearch)]
K --> KS[kafka-to-s3] --> S3[(S3 / MinIO)]
UI[web dashboard] --> DS[dashboard-server]
DS --> PG[(Postgres)]
DS --> CH
DS --> OS
DS --> KC[Keycloak]
Every workspace is fully isolated, with its own:
- Kafka topic,
events.{org_id}.{workspace_id} - ClickHouse database and user, created from
ckh/workspace.sql - OpenSearch data stream,
events-{org_id}-{workspace_id}
dashboard-server provisions these resources when a workspace is created and
removes them when it is deleted. See workspace provisioning
for how the lifecycle works.
Every event, from the collector onwards, uses one wire format: CanonicalEvent
in crates/canonical-event.
| Path | What it is |
|---|---|
crates/events-collector |
HTTP ingestion API (Rust/axum). It validates events, enriches them (arrival time, client IP, country) and publishes them to the workspace's Kafka topic. Settings come from a CAC/Superposition config and can be overridden per org or workspace |
crates/dashboard-server |
Backend API for the dashboard (Rust/axum, Postgres via SeaORM/sqlx). It handles orgs, workspaces, members, invitations, ClickHouse queries, OpenSearch search, sessions, LLM chat and workspace provisioning |
crates/sink-opensearch |
Kafka → OpenSearch indexer with at-least-once delivery, no duplicates and a DLQ. See its README and SPEC |
crates/kafka-to-s3 |
Kafka → S3 archiver. It writes zstd-compressed JSONL partitioned by org, workspace, date and hour. See its README |
crates/canonical-event |
Shared event type and org/workspace slug validation |
clients/typescript |
sudo-query SDK for browser and Node.js (npm). It batches events, tracks anonymous IDs and supports super properties. Includes a Shopify integration and an HTML demo. See its README |
web/dashboard |
Dashboard UI (React 19, Vite, MUI) |
xtask |
cargo xtask dev task runner for formatting, linting, tests, Docker Compose, DB migrations and e2e suites. See its README |
migrations |
Postgres migrations for dashboard-server, in sqlx format |
ckh |
ClickHouse per-workspace schema and chproxy config |
keycloak/import |
Keycloak realms for local dev, including a mock "Google" IdP |
docker-compose.yml |
The local infrastructure stack |
- Rust (stable, edition 2024) and Cargo
- Docker with Docker Compose
- Node.js 20+ and npm (for the dashboard and SDK)
cargo xtask setup # interactive; press Enter to start everything
cargo xtask ps # check that containers are up and healthy| Service | Local address |
|---|---|
| Postgres | localhost:5433 |
| Redpanda (Kafka) | localhost:19092, Console at http://localhost:8081 |
| ClickHouse | http://localhost:8123 (native TCP on 19000), chproxy on 9090 |
| OpenSearch | http://localhost:9200, Dashboards at http://localhost:5601 |
| Keycloak | http://localhost:8080 (admin / admin) |
| MinIO | http://localhost:9000, console at http://localhost:9001 (minioadmin / minioadmin) |
| LocalStack (KMS) | http://localhost:4566 |
cargo xtask db migration
cargo xtask db status # must report 0 pendingThe collector and dashboard-server both default to port 3000, so give one
of them a different port:
# Dashboard API on :3000
ADMIN_API_TOKEN=dev-admin-token cargo run -p hyper-analytics-dashboard-server
# Event collector on :3001
SERVER_ADDR=0.0.0.0:3001 SERVER_ACCEPT_CORS=true \
COLLECTOR_CONFIG=crates/events-collector/cac.toml \
cargo run -p event-collector
# Optional sinks
SINK_CONFIG=crates/sink-opensearch/cac.toml cargo run -p sink-opensearch
cargo run -p kafka-to-s3 # see its README for the required env varsdashboard-server reads a .env file in its working directory. Main settings:
| Variable | Purpose / default |
|---|---|
DATABASE_URL |
Postgres connection string |
DASHBOARD_BIND_ADDR |
0.0.0.0:3000 |
KEYCLOAK_URL, KEYCLOAK_REALM, KEYCLOAK_ADMIN_USER, KEYCLOAK_ADMIN_PASS |
Keycloak access |
CLICKHOUSE_URL, CLICKHOUSE_ADMIN_URL, CLICKHOUSE_ADMIN_USER, CLICKHOUSE_ADMIN_PASSWORD |
http://localhost:8123, default |
CLICKHOUSE_KAFKA_BROKERS |
Brokers as ClickHouse sees them |
KAFKA_BROKER_URL |
localhost:19092 |
WORKSPACE_TOPIC_TEMPLATE |
events.{org_id}.{workspace_id} |
OPENSEARCH_URL, OPENSEARCH_INDEX |
http://localhost:9200, events-{org_id}-{workspace_id} |
ADMIN_API_TOKEN |
Enables the admin (provisioning) API |
LLM_API_KEY, LLM_API_ENDPOINT |
OpenAI-compatible chat completions endpoint for the chat assistant |
cd web/dashboard
cp .env.example .env.local # points at localhost:3000 and the local Keycloak
npm install
npm run dev # http://localhost:5173Sign in through Keycloak, then create an organization and a workspace. The
workspace stays PENDING until it is provisioned:
curl -X POST -H 'X-Admin-Token: dev-admin-token' \
http://localhost:3000/api/admin/workspaces/<workspace_id>/provisionYou can send events with the SDK:
import { SudoQuery } from 'sudo-query';
SudoQuery.init({
endpoint: 'http://localhost:3001/v1/events/batch',
orgId: '<org_id>',
workspaceId: '<workspace_id>',
});
SudoQuery.track('checkout_started', { cart_value: 499, currency: 'INR' });Or call the collector over plain HTTP:
curl -X POST http://localhost:3001/v1/events/batch \
-H 'Content-Type: application/json' \
-H 'x-org-id: <org_id>' \
-H 'x-workspace-id: <workspace_id>' \
-d '{
"events": [{
"envelop_version": "1.0",
"id": "0b9c6a6e-6f0e-4d3e-9a43-3c1f4e2b7a10",
"name": "page_view",
"org_id": "<org_id>",
"workspace_id": "<workspace_id>",
"anon_id": "anon-123",
"occured_at": "2026-10-27T10:00:00Z",
"properties": { "path": "/checkout" }
}]
}'Collector endpoints (all under /v1):
| Endpoint | Body |
|---|---|
POST /events/batch |
{ "events": [...], "system_properties": {...} } |
POST /events |
One or more concatenated event objects |
POST /events/authenticated |
Same as /events, but needs a KMS-encrypted bearer token |
GET /health |
Kafka and auth health |
The x-org-id and x-workspace-id headers must match the ids in the events.
Org and workspace ids are server-generated slugs, 6–30 characters long, made
of lowercase letters, digits and -.
The events then appear in the dashboard's Chat, Discover, Sessions and SQL console.
Everyday tasks go through cargo xtask. See the full reference.
cargo xtask ci # fmt --check + clippy + test (the CI gate)
cargo xtask db migration add name # new Postgres migration
cargo xtask e2e dashboard # end-to-end suites: dashboard | collector | kafka-to-s3To work on the dashboard, run these in web/dashboard: npm run lint,
npm test (vitest) and npm run build. To work on the SDK, run these in
clients/typescript: npm test and npm run build.
GitHub Actions builds multi-arch Docker images for dashboard-server,
event-collector, sink-opensearch, kafka-to-s3 and web-dashboard, and
publishes them to ghcr.io/<owner>/<repo>/<image>. Pushing a v* tag
publishes the TypeScript SDK to npm.