This page is generated directly from the app's data inventory, which is kept in the source repository and updated in the same commit as any change to how the app handles data. What follows is that inventory, unedited.
Status: none of your records are collected, and nothing is shared with anyone. There is no server of ours to collect anything to.
One thing is disclosed on the store forms: purchase history, because RevenueCat stores whether this install's subscription is active. It is tied to an anonymous identifier, it says nothing about the owner or the dog, and it is the only thing that leaves the device without the owner choosing to send it.
That distinction is the point of this file. "No data collected" would be the easier line, and it would be false.
None of your records. Threshold has no backend, no account system, no login, no sync, and no analytics. The app makes no network requests of its own. Every encounter, daily record, photo, note and the dog's name stays on the device.
The only outbound traffic the binary is capable of comes from the platform subscription SDK (RevenueCat), which talks to RevenueCat and the platform store to answer one question: is this install's subscription active. It is given an anonymous store-generated identifier and no owner data — no dog name, no encounters, no email, no device identifier we set.
RevenueCat keeps that subscription status on its servers, which is why the App Privacy form below discloses purchase history. It is the one thing that leaves without the owner choosing to send it, and it says nothing about the dog.
| Item | Where | Encrypted | Leaves device |
|---|---|---|---|
| Encounters (time, trigger, distance, response, recovery, optional note) | App private storage, one file | AES-256-GCM | Only if the owner exports a backup |
| Daily records — sleep and play (time, minutes), toilet (time, outcome), each with an optional note | Same file | AES-256-GCM | Same |
| Dog name and settings | Same file | AES-256-GCM | Same |
| Attached photos | App private storage | AES-256-GCM, same key | Same |
| Document encryption key | iOS Keychain / Android Keystore | Protected by the OS | Never |
| Backup passphrase | Not stored anywhere | n/a | Never |
The 256-bit document key is generated on the device on first launch, held in expo-secure-store with WHEN_UNLOCKED_THIS_DEVICE_ONLY, and never transmitted. Every write draws a fresh random 96-bit nonce.
Two things can leave the device, both only when the owner chooses:
Verified against the generated AndroidManifest.xml, not just the config. Only these two are present in a build:
| Permission | Why | User-facing |
|---|---|---|
INTERNET | The subscription SDK asking the store whether this install is subscribed. Nothing else makes a network request. | No prompt; not shown by Android |
READ_EXTERNAL_STORAGE (max SDK 32) | Reading a photo the owner chose to attach | Prompted, and declinable — the app works fully without it |
Explicitly stripped via blockedPermissions, because the libraries we depend on would otherwise add them by default:
ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, RECORD_AUDIO, SYSTEM_ALERT_WINDOW, WRITE_EXTERNAL_STORAGE, VIBRATE, CAMERA
CAMERA is contributed by expo-image-picker. The app only ever opens the photo library, never the camera, so it is removed and the iOS camera usage string is not declared either.
A few normal-level permissions arrive from transitive Android libraries rather than from our configuration - ACCESS_NETWORK_STATE, and the biometric pair USE_BIOMETRIC / USE_FINGERPRINT that comes with the OS keystore integration behind expo-secure-store. None is a runtime-prompted permission, none grants access to personal data, and none is used to collect anything.
No location, no microphone, no contacts, no camera roll scanning, no background access, no notifications, no draw-over-other-apps.
On iOS the only permission string declared is NSPhotoLibraryUsageDescription. The config plugins would otherwise add NSCameraUsageDescription, NSMicrophoneUsageDescription and NSFaceIDUsageDescription by default, so each is explicitly disabled (cameraPermission: false, microphonePermission: false, faceIDPermission: false), which deletes the key rather than leaving a prompt the app would never show. The app never opens the camera, never records audio, and never gates the keystore behind biometrics.
An app update replaces the code, never the stored data. The key alias (threshold.document.key.v1) and the filename (threshold.enc) are fixed, the envelope format is unchanged, and nothing in the startup path deletes anything - erasing is reachable only from the owner tapping "Erase all data on this device". Tests cover a document written by one version being read whole by the next, including fields a newer version added.
What DOES lose records, by design:
WHEN_UNLOCKED_THIS_DEVICE_ONLY, so it is deliberately excluded from iCloud and iTunes backups and never leaves the device. A restored copy of the encrypted file has no key to open it. On Android the same holds: the expo-secure-store backup rules keep the keystore entry out of auto-backup, so a restored file cannot be opened either.That is the cost of the privacy promise: because we hold no copy and the key never leaves the device, only the owner's own encrypted export can move records between installs. Onboarding says so on first launch, and it is the reason export exists.
Apple — App Privacy: one data type, and it is about the transaction rather than the dog.
| Question | Answer |
|---|---|
| Do you or your third-party partners collect data? | Yes |
| Data type | Purchases → Purchase History |
| Linked to the user's identity | No |
| Used for tracking | No |
| Purpose | App Functionality |
Everything else — contact info, health, location, identifiers, usage data, diagnostics, user content — is No.
This previously said "Data Not Collected", which was wrong. RevenueCat stores this install's subscription status on its own servers, persistently, and Apple's question covers "you or your third-party partners". Apple excuses what it collects for App Store transactions; it does not excuse a third party's copy, and RevenueCat is one we chose. Not linked, because we never send a name, email or identifier of ours — RevenueCat generates an anonymous one. Not tracking, because there is no ad network, attribution SDK or data broker anywhere in the app.
None of the owner's own records are collected under any heading. The encounters, the daily records, the photos, the notes and the dog's name never leave the device at all.
PrivacyInfo.xcprivacy declares NSPrivacyTracking: false, no tracking domains, and required-reason API declarations only (file timestamp C617.1, disk space E174.1, user defaults CA92.1).
Google Play — Data safety: the same single disclosure — purchase history, not linked to identity, not used for tracking, for app functionality. Data is encrypted at rest. There is no account, therefore no account deletion flow is required; Settings offers "Erase all data on this device", which deletes every record and destroys the encryption key.
There are no accounts, so there is nothing to delete server-side. In-app, Settings → Erase all data on this device removes every encounter, photo and setting and destroys the document key. It cannot be undone and we hold no copy.
4+. The app is aimed at adults managing a dog's behaviour programme, but it contains nothing that warrants an age restriction: no user-generated content sharing, no social features, no chat, no web access, and no purchasable content other than the subscription.
This document previously said "18+ / adults only", which was wrong and contradicted the store. Apple derives the rating from the content questionnaire, and every one of its 25 answers here is "None" — which yields 4+. There is no honest set of answers that produces 18+ for an app like this, and inventing content to justify a higher rating would be a false declaration. A 4+ rating does not place the app in the Kids Category, which is a separate opt-in we do not take.