---
name: publik-telemetry
description: Usage telemetry for an app on publik. The publik module (skills/setup.md) sends it for you, with Sign in with publik. This file is for an app that cannot ship the module, and documents the request. Two events (app opened, still running), no content, and a user can turn it off. publik counts an app's weekly users from it.
---

# Add usage telemetry to your app

**The publik module does this for you.** Every app on publik opens with Sign in with publik. The module adds it, and counts signed-in users: after the sign-in, it sends the two events below with the install key. Add the module with https://publikhq.com/skills/setup.md. Use the files in section 2 only for an app that cannot ship the module.

Usage telemetry tells publik that your app ran on a computer. publik counts your **weekly users** from it. It sends two small events and nothing else: no file names, no prompts, no content, no user name, no hardware ID. publik does not store the IP address with a count. Its rate limiter keeps a hashed form of the address for up to one day. Each row also keeps a hash of the network, kept like the rest.

Every app on publik that ships a download sends it. The module sends it; an app without the module adds it with one file. publik holds a fellow's listing without the module, and counts no users for an app that sends nothing. This guide is public at https://publikhq.com/skills/telemetry.md.

## 1. The two events

| Event | When the app sends it |
|---|---|
| `app_opened` | Once per launch, right after start-up. |
| `active_session` | Every 30 minutes while the app runs. The first one is 30 minutes after `app_opened`. |

## 2. Copy one file (only without the module)

Copy the file for your language into your app. Do not add a package. Then add the lines below. Use your publik listing slug (the last part of `publikhq.com/<slug>`) and the version your build was made from, with no leading `v`.

| Language | Copy this file |
|---|---|
| Node, Electron main process | https://publikhq.com/sdk/telemetry.cjs.md |
| Rust (Tauri, egui, command-line tool) | https://publikhq.com/sdk/telemetry.rs.md |
| Swift (macOS) | https://publikhq.com/sdk/telemetry.swift.md |
| Python | https://publikhq.com/sdk/telemetry.py.md |

publik counts macOS, Windows and Linux. iOS is not counted in v1: the Swift file sends nothing on iOS.

Node or Electron (`publik-telemetry.cjs`, no dependencies):

```js
const { app } = require("electron");
const telemetry = require("./publik-telemetry.cjs");
telemetry.start("your-slug", app.getVersion());
```

Rust (`src/publik_telemetry.rs`; add `ureq = "2"`, or use the reqwest block in the file):

```rust
mod publik_telemetry;
// in main(), at start-up:
publik_telemetry::start("your-slug", env!("CARGO_PKG_VERSION"), None);
```

A Rust command-line tool calls `publik_telemetry::start_cli("your-slug", env!("CARGO_PKG_VERSION"));` as the first line of `main()`, in place of `start`. A detached copy of the tool does the send, so the tool does not wait. `--version` and `--help` send nothing. A tool sends at most one `app_opened` in 10 minutes. These four rules are in this standalone file only. The publik module (https://publikhq.com/sdk/publik.rs.md) has a terminal UI instead; its page says how a command-line tool uses it.

Swift (`PublikTelemetry.swift`, Foundation only):

```swift
// once, at start-up:
PublikTelemetry.start(appSlug: "your-slug", appVersion: "1.2.3")
```

Python (`publik_telemetry.py`, standard library only):

```python
import publik_telemetry
publik_telemetry.start("your-slug", "1.2.3")
```

`start` sends `app_opened` in the background and starts the 30-minute timer. It does not block start-up. It does not throw. It shows no error to the user. `stop()` stops the timer; you do not have to call it.

## 3. The exact request

The file above sends this. Use it only if you write your own client. Send it from the app itself: publik drops an event that a page on another web site sends.

```sh
curl -i -X POST https://publikhq.com/api/v1/telemetry \
  -H 'Content-Type: application/json' \
  -d '{"v":1,"app":"your-slug","install_id":"3f0c2a52-7a1e-4c1b-9a53-0b6c0e1d2f44","event":"app_opened","app_version":"1.2.3","os":"macos","arch":"arm64"}'
```

| Field | Rule |
|---|---|
| `v` | The number `1`. |
| `app` | Your publik listing slug, in lower case. |
| `install_id` | A random UUID (version 4) in lower case, from the file below. |
| `event` | `"app_opened"` or `"active_session"`. |
| `app_version` | The release version, at most 64 characters, no leading `v`. |
| `os` | `"macos"`, `"windows"` or `"linux"`. |
| `arch` | Optional: `"arm64"` or `"x64"`. |

Send no other field. publik refuses a body that has one. The body is at most 2 KB.

| Answer | Meaning |
|---|---|
| `204` | Accepted. Also the answer when a limit drops the event. |
| `400` | The body is wrong. `error.message` names the field. |
| `404` | `app` is not the slug of a public publik listing. |
| `413` | The body is too large. |

Your app ignores every answer. Send in the background, wait at most 5 seconds, and do not retry. publik stores at most 6 events per computer and app each hour. It also limits how many new computers one network can add each hour. When that limit cannot be checked, publik drops the events of a new computer.

The install ID is one random UUID per computer account. All publik apps share it. Another app can change the file, so publik does not trust the ID alone. It is in `telemetry.json` in the publik folder:

- macOS: `~/Library/Application Support/publik/telemetry.json`
- Windows: `%LOCALAPPDATA%\publik\telemetry.json`
- Linux: `${XDG_CONFIG_HOME:-~/.config}/publik/telemetry.json`

```json
{"install_id": "3f0c2a52-7a1e-4c1b-9a53-0b6c0e1d2f44", "created_at": "2026-11-06T12:00:00Z"}
```

Create the file if it is missing (mode 0600 on macOS and Linux). If it holds a valid UUID, use that one, in lower case. If you cannot read or write it, use a random UUID for this run only.

## 4. Signed-in users

The publik module sends every event with the install key:

```
Authorization: Bearer <publik key>
```

publik then links the count to that publik install, and to the publik account that signed in on it. A signed-in person counts once, by their account, across every computer and every app. publik counts an event with no key by its install ID only. A standalone client sends the key when it has one. To send it, give `start` a third argument: a function that returns the current publik key, or nothing when the user is signed out. The client calls it before each send, so it sees a sign-in or a sign-out during a run.

```js
telemetry.start("your-slug", app.getVersion(), () => currentPublikKey());
```

With no third argument, the client looks in `PUBLIK_API_KEY`, then in the `key` field of `apps/<slug>.json` in the publik folder. Send only a publik key (`pk_...`). Never send a user's own provider key. A wrong or revoked key causes no error: publik counts the event as signed out. Send the key when you have one, so publik counts a computer that also calls publik API once.

## 5. How a user turns it off

Telemetry is on by default. It is off when one of these is true:

- The environment variable `DO_NOT_TRACK` is set to anything except empty, `0` or `false`.
- The environment variable `PUBLIK_TELEMETRY` is `0`, `false` or `off`.
- `consent.json` in the publik folder has `"usage": false`.

When it is off, the client sends nothing and does not create `telemetry.json`. Tell your users what your app sends. The words publik uses are at https://publikhq.com/privacy#app-usage.

## 6. Check that it works

Run your app one time with `PUBLIK_TELEMETRY_DEBUG=1`. The client prints one line to stderr for each attempt:

```
publik-telemetry: app_opened -> 204
```

`204` means publik has the event. `404` means the slug is wrong, or your listing is not public yet. `400` means a field is wrong. In tests, set `PUBLIK_TELEMETRY_URL` to send the events to your own server. On a build machine (CI), set `DO_NOT_TRACK=1`, so a test run does not count as a user.

## 7. Where the number shows

The count is **weekly users**: the computers that ran your app in the last 7 days, with each signed-in person counted once. It shows on **Your apps** (https://publikhq.com/dashboard), on the Numbers tab of the app, on your public profile and on its badge. An old build with no telemetry still counts while it calls publik API.

A person or a computer qualifies for the fellowship board with 25 minutes of use in one UTC day, or with use on two UTC days, by the times publik stored. People signed in with publik count once. At most 10 qualified people and computers from one network count for one app, and at most 20 for one fellow in a week. iOS, Android and web apps are not counted in v1.
