---
title: Anonymous telemetry
description: What Patter's opt-out usage telemetry collects, and how to disable it.
---

Patter sends a small amount of **anonymous usage telemetry** so the maintainers can
see how the SDK is used — which engines, providers, models, carriers, platforms,
and versions are popular, plus coarse per-call latency, duration, and cost — and
prioritise accordingly. It is **on by default** and designed to be invisible to
your application and impossible to tie back to a person or a call.

<Note>
Telemetry **never** collects PII or call content. No phone numbers, transcripts,
audio, prompts, tool arguments, API keys, customer identifiers, file paths,
hostnames, or IP addresses are ever sent.
</Note>

## What is collected

Each event is a small, flat JSON object with only coarse, low-cardinality fields:

Common fields on every event:

| Field | Example | Notes |
| --- | --- | --- |
| `event` | `sdk_initialized` | One of a fixed, small set of event names |
| `sdk` / `sdk_version` | `python` / `0.6.3` | Language and SDK version |
| `os` / `arch` | `linux` / `arm64` | OS family and CPU arch only |
| `runtime` / `runtime_version` | `node` / `20.11` | Bucketed to `major.minor` |
| `run_id` | random | A per-process random id, regenerated every run |
| `install_id` | random | A random, anonymous install id persisted locally, to count active installs (no PII, no hardware) |
| `ci` | `false` | Whether a CI/test environment was detected |

Per-event dimensions (all are a closed enum or a coarse bucket — an off-list
value is replaced with `other` before sending):

| Event | Dimensions |
| --- | --- |
| `sdk_initialized` | `carrier` (twilio/telnyx/plivo/none), `tunnel` (static/configured/none), and anonymous deploy-shape (presence-only env/file probes — **never the value of an env var**): `invoked_by_agent` (claude/cursor/copilot/…/none), `container` (bool), `serverless` (lambda/cloud_run/vercel/azure_functions/none), `cloud` (aws/gcp/azure/fly/none), `package_manager`, `days_since_install_bucket`, and `previous_sdk_version` (for the upgrade funnel) |
| `first_run` | Sent **once per install** (the run that creates the local state), to mark activation. Carries the same anonymous deploy-shape dimensions as `sdk_initialized`. Never sent if you have opted out (opting out never touches the filesystem) |
| `cli_command` | `cli_command` — which `getpatter` CLI command was invoked (`dashboard` / `eval` / `hermes` / `openclaw` / `telemetry` / `other`). **Only the command name** — never arguments or flag values. The `getpatter telemetry` control command never emits telemetry itself |
| `feature_used` | `engine` (realtime/convai/pipeline), `provider` (vendor family), and the models in use: for **pipeline** the composed stack — `stt_provider` / `tts_provider` / `llm_provider` (vendor) plus `stt_model` / `tts_model` / `llm_model` (e.g. `deepgram-nova-3`, `anthropic-claude-opus-4-8`) — and for **realtime** the Realtime model as `llm_model` (e.g. `openai-gpt-realtime-2`). A fine-tuned, self-hosted, or custom model collapses to `{vendor}-other` — a custom model name is **never** sent |
| `agent_configured` | `builtin_tool_count` (0–3), `custom_tool_count_bucket` (a range like `2_3` — **never the tool names**), `integration` (openclaw/mcp/hermes/other/none), `integration_kind` (consult/mcp/none), `mcp_server_count_bucket`, and feature-adoption flags: `noise_reduction` (near_field/far_field/none), `turn_detection` (default/custom/none), `preambles_used` (bool), `per_tool_timeouts_set` (bool), `llm_fallback_configured` (bool) |
| `call_started` | Sent when a call connects (media stream begins), to pair with `call_completed` for a connect→complete funnel: `engine`, `provider`, `carrier`, `direction` (inbound/outbound), and `call_uid` — a **random correlation id generated by the SDK** so the two lifecycle events of the same call can be paired. It is never the carrier call SID and cannot be linked to phone numbers or call content. No metrics exist yet at connect, so no latency/cost/duration |
| `call_completed` | `outcome` (completed / error / no_answer / busy / failed), `direction` (inbound/outbound), `error_code` (a closed code like `rate_limit` / `timeout` / `connection` — **never the error message**), `engine`, `provider`, `carrier`, `latency_ms` (whole milliseconds), `duration_seconds` (whole seconds), `cost_usd` (total call cost in USD), `turn_count_bucket`, `call_uid` (same random correlation id as the call's `call_started`) — sent once per call. **No call content.** The only per-call identifier is the SDK-generated random `call_uid` — never the carrier call SID |

The `run_id` is a fresh random value on every process start (it groups one run's
events). The `install_id` is a random UUID generated **once** and stored in a small
local file (`~/.getpatter/install-id`, or `$XDG_STATE_HOME/getpatter/install-id`
when `XDG_STATE_HOME` is set) so the maintainers can count unique active
installs over time — it is a random number, **not** tied to you or any PII, and is
never derived from hardware (MAC address, hostname, serial numbers). Opting out
never creates or reads this file. The collector drops the source IP on receipt.
Custom tool names, custom integration endpoints, custom/fine-tuned model names, and
any value not on a closed enum are **never** sent — only counts and coarse
categories.

## How to disable it

Telemetry is opt-out. Any one of the following turns it off:

```python Python
# In code:
phone = Patter(carrier=Twilio(...), phone_number="+15551234567", telemetry=False)
```

```ts TypeScript
// In code:
const phone = new Patter({ carrier: new Twilio(), phoneNumber: "+15551234567", telemetry: false });
```

Or from the command line — a persisted, machine-level opt-out (parity with
`next telemetry disable`):

```bash
getpatter telemetry disable   # persisted opt-out — no usage data is ever sent
getpatter telemetry enable    # re-enable (opt-out model, on by default)
getpatter telemetry status    # show the current state, endpoint, and how to audit
```

Or via environment variables (these also work without touching code):

| Variable | Effect |
| --- | --- |
| `PATTER_TELEMETRY_DISABLED=1` | Disable Patter telemetry |
| `DO_NOT_TRACK=1` | Disable telemetry in Patter and every tool that honors the [DO_NOT_TRACK](https://donottrack.sh/) convention |

Telemetry is also **automatically disabled** in CI and test runners (for example
when `CI`, `GITHUB_ACTIONS`, `PYTEST_CURRENT_TEST`, or `VITEST` is set).

## Inspect exactly what would be sent

Set `PATTER_TELEMETRY_DEBUG=1` to print every event to stderr **without sending
it**. This lets you audit precisely what would leave your machine:

```bash
PATTER_TELEMETRY_DEBUG=1 python app.py
# [patter telemetry] {"event":"sdk_initialized","sdk":"python","sdk_version":"0.6.3",...}
```

## Send telemetry to your own collector

Point Patter at any HTTP endpoint that accepts a JSON array of events:

```bash
export PATTER_TELEMETRY_ENDPOINT="https://telemetry.example.com/v1/ingest"
```

## Precedence

When more than one signal is present, the first match wins:

1. `DO_NOT_TRACK` set → **off**
2. `PATTER_TELEMETRY_DISABLED` set → **off**
3. persisted opt-out marker present (`getpatter telemetry disable`) → **off**
4. `telemetry=False` in code → **off**
5. CI / test environment detected → **off**
6. otherwise → **on**
