← Listen

What the telemetry sends

Every event and every property, in full. Nothing here is a summary: this is the list the code is written against, so "see exactly what is shared" can mean exactly.

Listen sends anonymous usage statistics and crash reports by default, and Settings, Privacy is where you turn it off. This file is the complete dictionary: every event, every property, every bucket boundary. An event or property that is not written down here is not merely undocumented, it is dropped before it leaves your device, by a filter compiled from the same file both apps share: Sources/ListenKit/TelemetrySchema.swift.

The policy in one sentence, borrowed from the app's own activity log: events and ids, never names, questions or transcript text.

What is never sent

Audio, transcripts, notes, titles, speaker or contact names, tags, dictionary terms, search text, Ask questions or answers, calendar contents, recording or CloudKit identifiers, file paths, URLs, API keys, raw error messages, and the device's name. Not as a promise of restraint: the send filter strips everything outside the tables below, and the filter's source is public.

Where it goes, and as whom

Properties on every event

PropertyValues
platformmac or iphone
app_buildthe app version, e.g. 0.16.0
os_majorthe OS major version, e.g. 26
install_age_bucketday_0, week_1, month_1, month_2_3, over_3_months, measured from the day you opted in
acquisition_channelonly if you answered "How did you hear about Listen?": github, homebrew, app_store, search, reddit, hacker_news, youtube_podcast, friend, other
schema_versionthis dictionary's version, currently 3

The SDK also stamps its own context: OS name and version, app version and build, and SDK name and version. Nothing else of its automatic context survives the filter, and $device_name is stripped explicitly.

internal is present, and true, only on installs belonging to whoever builds Listen, set by a preference that is deliberately not in Settings. It exists so those rows can be excluded from every chart without naming an install ID that goes stale the moment telemetry is switched off and on again.

Events

installation_activated

Once, at the moment consent first becomes yes. activation is new_install if this copy had never finished setup when telemetry came on, and existing otherwise. It exists because the one-time default-on migration fires this event for everybody who upgrades, so without it the count is a floor on the installed base rather than a count of new users, and install_age_bucket cannot tell them apart either, being measured from the opt-in day.

setup_completed

Once at the end of setup, and once if setup is closed before the end. outcome is finished or dismissed; the other properties describe whatever had been chosen by that point, so an abandoned run says how far it got. mic_granted (bool), model (model id or none), dictation_on (bool), sync_on (bool), calendar_on (bool), memory_on (bool, whether people are remembered automatically). Choices, never contents. Re-running setup from Settings sends it again, so this is not once per install.

recording_completed

When a capture made on that device lands in its library. Only the device that made a recording counts it; one arriving over sync is never counted again.

PropertyValues
kindmeeting, memo, phone_memo, import, cli
source_appzoom, teams, slack, facetime, discord, whatsapp, telegram, signal, webex, browser, other, none. Mapped from the call's app bundle id through a fixed table; anything unrecognised is other, never the raw id
duration_bucketunder_1_min, 1_5_min, 5_15_min, 15_30_min, 30_60_min, 1_2_h, over_2_h

recording_transcribed

When a transcription run ends, on the Mac that ran it, whether it worked. Fires for phone memos too, on the transcribing Mac. Transcription failures ride this event's outcome and are deliberately not doubled into operation_failed.

PropertyValues
outcomeok, or a stable code: transcription.asr_failed, transcription.pipeline_failed, transcription.diarization_failed, network.failed, unknown
asr_modelthe id of the model the run actually used. It is what ran, not what the recording or the app would choose now
duration_bucketas above
processing_buckettranscription time as a fraction of the audio's length: under_0_1x, 0_1_to_0_25x, 0_25_to_0_5x, 0_5_to_1x, over_1x
speaker_countdistinct diarized voices, capped at 12
track_layoutmic_only or mic_and_system
kindas above

dictation_completed

When a dictation lands. duration_bucket (under_5_s, 5_15_s, 15_30_s, 30_60_s, over_1_min), word_count_bucket (1_5, 6_20, 21_50, 51_100, over_100), engine (parakeet or apple). Never the words.

ask_completed

One event after one Ask question succeeds or fails. It never contains the question, answer, transcript, title, source ids, quotes, or a conversation/run identifier. The event is deliberately a performance summary rather than a trace: exact timings and fixed tool names stay in the device's unified log.

PropertyValues
outcomeok, timeout, offline, provider_error, ungrounded, invalid_evidence, too_many_rounds
backendopenrouter, claude_code, codex, local_endpoint, remote_endpoint
modela model id the selected backend advertised, default, or custom. User-authored custom model text is never sent
scopelibrary, recording, person, note
latency_bucketunder_2_s, 2_5_s, 5_15_s, 15_30_s, 30_60_s, 60_90_s, over_90_s, or unknown
round_countprovider rounds, capped at 24; absent when a CLI harness does not expose it
retry_countprovider retries, capped at 24; absent when the harness does not expose it
tool_call_counttool calls, capped at 24
local_read_countlocal library reads, capped at 24; currently available on iPhone
request_size_bucketunder_16_kb, 16_32_kb, 32_64_kb, 64_128_kb, over_128_kb, or unknown
prompt_tokens_bucket, completion_tokens_bucketunder_1k, 1_4k, 4_16k, 16_64k, over_64k, or unknown
cost_bucketunder_0_001_usd, 0_001_0_005_usd, 0_005_0_02_usd, 0_02_0_10_usd, over_0_10_usd, or unknown
reference_count_bucket0, 1, 2_4, 5_plus, or unknown
zdrbool; present for OpenRouter, whose requests require Zero Data Retention

feature_used

A closed list, and the fact only: note_saved (never the note), sync_enabled, dictation_enabled, calendar_connected, share_export, import, iphone_capture, keep_audio_toggle, recognise_voices_toggle, speaker_named_on_phone, galaxy_opened.

recognise_voices_toggle is the iPhone's voice bank being switched on or off. speaker_named_on_phone is somebody saying who a voice is. Both are the fact and nothing else: no name, no voiceprint, no recording id, and no count of who the bank holds. The second is the event in this list that would most obviously carry a person's name, so it is worth saying twice that it does not.

galaxy_opened is the picture of the library being opened, and it is the fact alone: not how many stars were drawn, which would be a library size, and not what any of them are. It is here because the galaxy is one row in one menu, and a feature nobody finds looks exactly like a feature nobody wants.

operation_failed

subsystem (capture, model_download, sync, dictation, library, ui), code (a fixed identifier such as model_download.failed, sync.pass_failed, sync.pass_timeout, or ui.main_thread_stall.<bucket> .<phase> when the window stopped responding for more than two seconds), retryable (bool). Error text never travels: a raw error message routinely contains file names, and file names are content. Sync failures are edge-triggered, one event when a failure starts rather than one per retry.

audio_transfer

Sent by the iPhone, once per attempt to send a recording's audio that actually began sending bytes. outcome (completed, interrupted, failed), bytes_bucket, duration_bucket, progress_bucket (how far an interrupted one got: under_10, 10_50, 50_90, 90_100, complete), retry (bool, whether this recording had been offered before), foreground (bool, whether the app was on screen).

This event exists because a 45-minute memo once sat at 9% for a day and nothing anywhere recorded that a transfer had begun, how big it was, or where it stopped. No id, no title, no duration of the recording itself: how many bytes in what shape of bucket, and where it got to.

audio_received

Sent by the Mac that takes a phone recording's audio out of iCloud. bytes_bucket, duration_bucket, wait_bucket (how long between the recording being made and a Mac having it). The far end of audio_transfer, and the pair is the only way to see whether recordings cross at all: the two devices are two installs and nothing joins them, so the question is answered by comparing counts rather than by following one recording.

recording_stalled

One event the first time a recording has been waiting more than six hours with nothing happening to it. stage (awaiting_upload, awaiting_transcript, awaiting_audio), source (iphone, mac, ...), age_bucket, holder (the kind of device that has the audio: iphone, mac, or nobody; never which one). Edge-triggered per recording, so a library having a bad day says so once rather than every two minutes.

sync_pass

outcome (ok, failed, timeout, throttled), duration_bucket, moved (bool, whether anything actually changed), and reason on the outcomes that are not ok: a closed word such as quota, signed_out, offline, not_permitted, busy, too_old, conflict, missing, store or other. The reason is mapped from the error code and never from the error's text, because CloudKit's own wording varies by language and can contain identifiers.

Sent on a change of outcome and otherwise at most twice an hour. The poll runs every two minutes, so an event per pass would be 720 a day per install.

app_launched

launch_bucket (how long until there was a window), library_bucket (how many recordings, in a bucket), pending (how many are waiting to be transcribed, capped at 24). Sent immediately rather than batched, which also makes it the signal that an install is alive at all.

$exception

A crash report: stack trace, app build, OS version. Captured by the PostHog SDK, only while turned on, so a crash before that is never reported. The $device_name the SDK would attach is stripped.

Bucket midpoints

Dashboards that sum hours use the midpoint of each duration bucket: 0.5, 3, 10, 22.5, 45, 90 and 150 minutes respectively. Buckets are never re-cut silently; a change bumps schema_version and is noted here.

Retention and publication

Event data is kept indefinitely. This file used to say twelve months, and that was never true: PostHog has no time-based event deletion to switch on, on any plan or through any API, and the feature request for it has been open since 2023. A promise nothing enforces is worth less than an accurate sentence, so this is the accurate one. If PostHog ships retention, twelve months is the setting to choose and this paragraph goes back to promising it.

The identity is the part that does get deleted, on your instruction and at once: turning telemetry off deletes the install ID, so the events that remain are no longer connected to each other or to a later you.

Anything published from this data (a "users transcribed X hours" style statistic) is aggregate only, says it comes from installs with telemetry on, and uses no cohort smaller than 50 installs.

Verifying all of this