Documentation

Up and running in five minutes.

Everything you need to install, configure and integrate Tacilytics. Questions? Open an issue.

Quick start

You need Node.js 18 or newer. Create a folder for your instance and start the server:

terminal
mkdir my-analytics && cd my-analytics
npx tacilytics serve      # http://127.0.0.1:3920

The database (data/analytics.db) and your settings (.env) live in that folder, so updating the package never touches your data. Prefer a global install? npm i -g tacilytics, then tacilytics serve.

From source:

terminal
git clone https://github.com/Open-Cosmic-Software/tacilytics.git
cd tacilytics && npm install
cp .env.example .env      # set your own secrets
npm start
Set a password first. Without WEB_PASSWORD the dashboard uses the default passphrase tacilytics, and the server prints a warning on start.

Add the script

Open the dashboard, add your site, then put this line in the <head> of every page:

index.html
<script defer data-domain="example.com"
        src="https://analytics.example.com/tacilytics.js"></script>

Page changes in single-page apps (pushState, popstate) are tracked automatically. Bots and crawlers are dropped on the server.

Configuration

Set these in .env next to your data, or as environment variables.

VariableDefaultPurpose
PORT3920Port, bound to 127.0.0.1
WEB_PASSWORDtacilyticsDashboard passphrase. Always set your own.
SESSION_SECRETrandomSecret for login sessions. Set it so logins survive restarts.
INGEST_SALTfixedExtra server secret mixed into the daily visitor hash.
API_KEYnoneBearer key for the REST API and the CLI.
DB_PATH./data/analytics.dbLocation of the SQLite database.
DATA_RETENTION_DAYS00 keeps raw events forever; any other number deletes raw events older than that, daily.

TLS & reverse proxy

The server only listens on 127.0.0.1. Put a TLS proxy in front, for example Caddy, which fetches certificates automatically:

Caddyfile
analytics.example.com {
    encode gzip
    reverse_proxy 127.0.0.1:3920
}

Events & goals

Record any action as a named event, optionally with properties:

app.js
tacilytics('Signup');
tacilytics('Upgrade', { plan: 'pro' });

Events appear under Goals & conversions with a conversion rate. Click one to filter the whole dashboard to the visitors who triggered it. Properties are limited to short scalar values, so they can't turn into a store of personal data.

Purchases & revenue

Send a Purchase event with a value and currency to see revenue, average order value and revenue by source and platform:

checkout.js
tacilytics('Purchase', { value: '49.90', currency: 'EUR' });

Visitors returning from payment providers (PayPal, Klarna and the like) are recognised and not counted as a new traffic source.

Script options

index.html
<script defer data-domain="example.com"
        data-honor-dnt
        data-track-outbound
        src="https://analytics.example.com/tacilytics.js"></script>
AttributeEffect
data-honor-dntSends nothing when Do Not Track or Global Privacy Control is switched on.
data-track-outboundRecords clicks on links to other domains as events.

Mobile & desktop apps

Apps report into the same site as your website. Every event carries a platform (web, android, ios, macos, windows, linux), and the dashboard gains a platform filter, app versions and revenue per platform.

Flutter

The package lives in sdk/flutter:

main.dart
Tacilytics.init(endpoint: 'https://analytics.example.com', domain: 'example.com', appVersion: '1.4.0');
Tacilytics.instance.setEnabled(userConsented);
MaterialApp(navigatorObservers: [TacilyticsRouteObserver()]);
Tacilytics.instance.search('linen shirt', 6);
Tacilytics.instance.purchase(value: 49.90, currency: 'EUR', items: 2);

Any other stack

Send one JSON POST /api/collect per screen view or event:

FieldMeaning
dSite domain, as registered
pScreen, written like a path, e.g. /product/42
pfPlatform: android, ios, macos, windows, linux
sidRandom ID drawn per app launch and kept in memory only (16 to 64 characters)
av / dvApp version / device class (mobile, tablet, desktop), optional
l / ccLanguage / two-letter region of the device locale, optional
n / prEvent name and flat properties, optional

No advertising ID, no device ID, no cookies. The launch ID is hashed with the daily salt, so a user is never followed across days.

REST API

Everything the dashboard shows is available with a Bearer key (API_KEY):

terminal
curl -H "Authorization: Bearer $TACILYTICS_API_KEY" \
  "https://analytics.example.com/api/v1/stats?site=example.com&period=30d&country=DE"
EndpointDescription
GET /api/v1/sitesList tracked sites
POST /api/v1/sitesAdd a site { "domain": "…" }
GET /api/v1/statsAggregated stats: period = today, 7d, 30d, 90d, 12mo, or from/to
GET /api/v1/realtimeVisitors online, 30-minute pulse and recent events

Add country, source, page, browser, os, device, lang, medium, campaign or goal to /stats to segment.

CLI

terminal
export TACILYTICS_URL=https://analytics.example.com
export TACILYTICS_API_KEY=your-key

tacilytics sites
tacilytics stats example.com 30d
tacilytics live example.com --watch
tacilytics add example.com

Add --json to any command for machine-readable output.

How counting works

The script sends a small beacon with the page, referrer, timezone and language. The server reads browser, operating system and device class from the user agent, drops bots, maps the timezone to a country and builds the daily visitor hash. One row is stored per pageview; the dashboard aggregates with plain SQL.

The honest trade-off: without a persistent identifier, Tacilytics counts unique visitors per day and can't follow one person across days or devices. That is the point.

Privacy & security

  • No cookies, no local storage, IP addresses are never written to disk.
  • The daily salt lives in its own file (data/.day-salt, mode 600), never in the database, and is overwritten in place when the day changes. Events that arrive late get a one-off hash that can't be linked.
  • Copy-on-write filesystems, snapshots and backups can keep an old salt physically. Treat backups of data/ like the live server, or exclude .day-salt.
  • Run a single process per data folder.
  • Login and the collect endpoint are rate-limited; everything visitors send is escaped before it reaches the page.