Audience: operators of self-hosted Ever Gauzy, Ever Teams, Ever Works, Ever Rec and Ever Traduora installations; privacy and security reviewers.
Prerequisites: access to the product's settings as an operator (Gauzy: super admin; Works: platform admin; Rec: an admin listed in the admin module; Traduora: an address in TR_EVER_ADMIN_EMAILS); shell access if you want to change environment variables.
The statistics module (ever-stats) sends one small, signed, anonymous report per day so the maintainers can learn which versions and features are in use. It is on by default, it can be turned off in two ways, you can see every byte it sent, and it can never carry your business data: the report format is a closed schema and every string in it must match an allow-list.
One JSON document per day, schema ever.stats.v1, at most 16 KiB, integers only for numbers. Example (Ever Gauzy), generated from the SDK's golden fixture contracts/fixtures/stats/valid/gauzy.json:
{
"schema": "ever.stats.v1",
"report_id": "9f1c1d4a-7b2e-4f0a-9d3c-2a6b1e5f8c01",
"instance_id": "3d2b1a0c-5e4f-4a6b-8c7d-9e0f1a2b3c4d",
"sent_at": "2026-10-03",
"module_version": "1.0.0",
"product": "gauzy",
"instance_kind": "backend",
"serves": [
"gauzy",
"teams"
],
"version": "0.750.1",
"channel": "stable",
"install_source": "self-hosted",
"country": "ZZ",
"period": "2026-09",
"final": true,
"counts": {
"tenants": 3,
"organizations": 4,
"users": 41,
"users_active_30d": 30,
"employees": 27,
"employees_active": 25,
"teams": 6,
"projects": 18,
"tasks": 912,
"contacts": 57,
"integrations_in_use": {
"github": 1,
"ever_connect": 0
}
},
"features": {
"time_tracking": true,
"invoice": true,
"payment": true,
"open_stats": false
},
"aggregates": {
"invoiced_minor": {
"EUR": 18230055,
"USD": 950000
},
"invoices": 214,
"payments_minor": {
"EUR": 17500000
},
"payments": 190,
"hours_tracked_min": 259140
}
}| Field | Meaning |
|---|---|
instance_id |
a random UUID generated by your installation on first boot; not derived from a URL, hostname or licence; you can reset it (see How to see what was sent) |
product, instance_kind, serves |
which product; backend or frontend; which products one API serves (Gauzy + Teams share one backend) |
sent_at |
the UTC date the report was built; no time of day, so a report cannot be matched to a request log line |
version, module_version |
product version; statistics module version; both major.minor.patch only, because a build suffix of a fork (for example 1.2.3-acme-corp) could name a company |
channel |
stable, rc, beta, dev or custom: the builder turns any pre-release or build suffix of the version into one of these |
install_source |
self-hosted unless you set EVER_INSTALL_SOURCE (e.g. partner:<slug>, ever.sh, desktop) |
country |
ZZ (undeclared) unless you set EVER_STATS_COUNTRY; never derived from your tenants' data |
period, final |
the UTC month the counts cover; final: true on the re-send for the previous month (days 1–3) |
counts |
instance-wide totals from the allow-list below (never per tenant, never per record) |
features |
which product feature switches are on (booleans only) |
aggregates |
instance-wide sums per currency in minor units (cents), or plain integers |
Every installation sends the same fields whatever its size, a one-person installation and the desktop app's embedded server included; counts and aggregates are empty only for a frontend-only installation (Ever Teams web), which holds no data of its own. Reports are only ever looked at in aggregate: Ever Platform never publishes a figure that covers fewer than 10 installations.
| Product | counts |
features |
aggregates |
|---|---|---|---|
| Gauzy | tenants, organizations, users, users_active_30d, employees, employees_active, teams, projects, tasks, contacts, integrations_in_use.<integration> (number of tenants using each integration) |
every FEATURE_* switch of the product, lower-cased without the FEATURE_ prefix (time_tracking, invoice, payment, …) |
invoiced_minor.<CCY>, invoices, payments_minor.<CCY>, payments, hours_tracked_min |
| Teams (web) | — | — | — (version and install source only) |
| Works | users, tenants, organizations, works, works_by_kind.<kind>, agents, missions, teams, fleet_nodes, plugins_enabled |
app_works_enabled, app_launcher_enabled, dynamic_plugins, deploy_ever_works_enabled, subscriptions_enabled, mcp_enabled |
deployments, deployments_by_provider.<provider>, runs, credits_consumed |
| Rec | users, workspaces, teams |
gauzy_mirror, google_login |
screenshots, videos, storage_mb |
| Traduora | users, projects, terms, translations, locales, project_clients |
google_login, signups_enabled |
translations_changed |
The schema is published in the SDK (contracts/schemas/ever.stats.v1.json), vendored into every product with a test that fails if the copies differ, and served for reading at GET https://api.ever.co/v1/stats/schema (your installation never fetches it; it uses its bundled copy). It sets additionalProperties: false at every level and a per-product oneOf, so a Gauzy report cannot carry a Works key and no unknown field can be added by accident. The keys of the nested maps (integrations_in_use, works_by_kind, deployments_by_provider) come from closed lists too: a custom integration, kind or provider plugin is counted under other, never under its own name. The platform rejects anything off-schema (422 schema_violation) and drops it.
Names, e-mail addresses, phone numbers, postal addresses, tax or registration numbers, invoice or document contents, per-record amounts, task or project titles, free text of any kind, URLs, hostnames, IP addresses, precise location, per-tenant breakdowns. The only string fields are schema, report_id, instance_id, sent_at, module_version, product, instance_kind, serves[], version, channel, install_source, country, period, each constrained by a pattern or an enumeration. Continuous-integration tests in every product build a report from a database seeded with realistic names and assert that none of them appears in the serialized payload.
- Once per day at a fixed time derived from
instance_id(± 10 min), starting 24 h after first boot (10 min after boot if a send is overdue). - On days 1–3 of a month, one
final: truere-send for the previous month. - If the platform is unreachable, answers 5xx or
429, the module retries after 1 h, 4 h, 12 h, then the next day (or later, whenRetry-Aftersays so). A report the platform refuses (400,413,415,422) is not sent again until the module is upgraded;409 key_mismatchmeans another key holds theinstance_id, and only Reset instance identity (a new id and key) resumes reporting. Nothing is queued beyond the last 12 reports kept locally for the Last payload view. - Multi-replica deployments elect one sender through a database lease; there is never more than one report per day per installation.
The request: POST <EVER_STATS_API_URL>/v1/stats/reports, no credentials, headers Ever-Stats-Key: <base64url public key> and Ever-Stats-Signature: ed25519=<base64url signature over the exact request body>, both required; Ever-Stats-Key-Id is optional. The signature uses the statistics key, an Ed25519 key pair your installation generates on first boot for statistics only; the platform remembers the public key on first sight so that nobody else can submit reports under your instance_id. It is separate from the key of an Ever Platform connection: the connection key only authenticates the connection's calls and rotates on its own, and rotating it changes nothing here. The statistics key proves continuity, not identity: the platform learns nothing about who you are from it.
| Where | Default | Who can change it |
|---|---|---|
| Self-hosted installation | on | the operator: environment variable or settings toggle |
| Ever-operated cloud | on, managed by Ever | shown read-only ("Managed by Ever Cloud") with the last payload visible |
| Desktop / Electron builds with an embedded server | on (install_source: desktop) |
the toggle in the desktop settings form |
Tenants and organization admins inside a multi-tenant installation cannot change it; it is an instance-level setting.
Set EVER_STATS_ENABLED=false (Traduora: TR_EVER_STATS_ENABLED=false; Teams web: the server-side EVER_STATS_ENABLED=false) and restart. The module is not loaded: no route, no timer, no request. GET /api/ever-stats/status answers 404.
| Product | Where |
|---|---|
| Ever Gauzy | Settings → Anonymous usage statistics (/pages/settings/usage-statistics) → toggle |
| Ever Teams | Settings → Team → Ever Platform → statistics (reflects the Gauzy API's setting) |
| Ever Works | Settings → Ever Platform (/settings/ever-platform) → Usage statistics |
| Ever Rec | Portal → Settings → Ever Platform → Usage statistics |
| Ever Traduora | Ever Platform (admin menu, /ever-platform) → Usage statistics |
With the toggle off the scheduler never runs and Send now is disabled; the module makes no request. The state and its reason (env, ui, or cloud-managed) are shown on the page and by GET /api/ever-stats/status. Environment beats the toggle.
The same settings page shows:
| Element | What it shows |
|---|---|
| What is sent | a live preview built right now from your database (POST /api/ever-stats/preview); nothing leaves the server |
| Last payload | the exact bytes of the last report as transmitted, with sent_at and the HTTP status (202 accepted, 422 rejected by schema, 409 key mismatch, failed for transport errors); GET /api/ever-stats/last |
| Send now | sends one report immediately (at most once per 10 min); disabled while the toggle is off |
| Reset instance identity | generates a new statistics instance_id and statistics key; the old series can no longer be continued; an Ever Platform connection is not affected (its key is separate) |
If the organization that owns the installation's connection to Ever Platform enabled the stats_link integration and you accepted it on the installation, app.ever.co → Instances shows the same last payload for that instance to that organization only. stats_link is never offered on Ever-operated cloud.
| Item | Value |
|---|---|
| Endpoint | POST {EVER_STATS_API_URL}/v1/stats/reports (default base https://api.ever.co) |
| Schema | ever.stats.v1 — JSON Schema 2020-12, closed, ≤ 16 KiB, integers only |
| Signature | Ed25519 over the exact request body bytes; headers Ever-Stats-Key: <base64url public key> and Ever-Stats-Signature: ed25519=<base64url signature over the exact request body>, both required, including for Ever Teams web; Ever-Stats-Key-Id optional |
| Platform answers | 202 {accepted, superseded?} (the same report, by report_id, sent again the same UTC day: 202 {accepted} and nothing changes); 400 validation_failed (the key header) or 400 signature_invalid (the signature, or a key id that names another key); 409 key_mismatch; 413 validation_failed (too_large); 415 unsupported_media_type; 422 schema_violation with errors[] (path, and code one of unknown_field, type, pattern, range, required, duplicate_key, schema_unknown); 429 rate_limited with Retry-After (more than 24 reports a day, reports for more than two months a day, too many requests or new statistics ids from one source address, or too many new ids that day) |
| Env | EVER_STATS_ENABLED, EVER_STATS_API_URL, EVER_STATS_COUNTRY, EVER_STATS_SERVES (Gauzy), EVER_STATS_SEND_INTERVAL_S (tests), EVER_INSTALL_SOURCE, EVER_INSTANCE_ID and EVER_STATS_PRIVATE_KEY (stateless frontends: set both to keep one series across restarts, or neither for a new random identity per process) |
Routes (Gauzy; Works, Rec and Traduora expose the same functions under their own API; Teams web has only status and last) |
GET /api/ever-stats/status, GET /api/ever-stats/last, POST /api/ever-stats/preview, PUT /api/ever-stats/enabled, POST /api/ever-stats/send-now (at most once per 10 min), POST /api/ever-stats/reset-identity |
| Retention on the platform | reports are stored per instance and month; the source IP is never stored and never used to derive a country; the rate limit keeps a keyed hash of the address in memory only, under a key that rotates hourly |
Both SDKs run the platform's checks before anything is sent, so a report the SDK lets through is one the platform accepts, and a refusal names the same field with the same code: the 16 384-byte limit, a strict JSON reader (integers only, no key twice), the published schema (one closed key set per product) and the calendar date of sent_at. Every statistics fixture under contracts/fixtures/stats/ (vendored byte for byte from Ever Platform, with the expected answer of each in expected.json) gets the same answer from the platform, the mock, the TypeScript SDK and the Rust SDK.
TypeScript (@ever-co/connect-sdk):
import { generateStatsKey, sendStatsReport, signStatsReport, statsSignerFromSeed } from '@ever-co/connect-sdk';
const { seed } = generateStatsKey(); // once, on first boot; store the seed as a secret
const signer = statsSignerFromSeed(seed);
const signed = signStatsReport(report, signer); // throws StatsValidationError: no bytes leave
const outcome = await sendStatsReport(signed, { baseUrl: process.env.EVER_STATS_API_URL ?? 'https://api.ever.co' });
// outcome.kind: 'accepted' | 'retry' (retryAfterS) | 'reset_identity' | 'dropped'Rust (ever-connect-sdk, feature stats, no HTTP client of its own):
use ever_connect_sdk::stats::{classify_answer, reports_url, sign_report, StatsKey};
let key = StatsKey::from_seed(&stored_seed);
let signed = sign_report(&report, &key, false)?; // StatsRefusal: no bytes leave
// POST signed.body with signed.headers to reports_url(base_url)? with your HTTP client, then:
let outcome = classify_answer(status, &answer_body, retry_after.as_deref(), attempt);A refusal's message names fields and codes only; a key the schema does not know is shown as * (the exact path, which is the key itself, stays in its errors). walkStrings / walk_strings list every string of a report, keys included, for the test that no seeded name or e-mail address reaches the payload.