No IPs. No ad IDs. No idea who your users are.
SwiftStats measures how your app is used without collecting anything that identifies the person using it. Privacy isn't a setting you turn on — the SDK has no code path that gathers it.
- Retained
- Event names, counts, day, app version, OS version, device model, locale.
- Never retained
- IP addresses, IDFA, email, name, location, free text, anything linking one app to another.
- Install id
- A random UUID salted and hashed on device, scoped to one app. It counts installs; it does not describe a person.
- No ATT prompt
NSPrivacyTrackingis false and there are no tracking domains. A test in the repo asserts it.
Opens rose 8.2% while active installs rose 3.1%. Existing installs are opening the app more often rather than new ones arriving.
It tells you what changed
Every read-out is a deterministic rule over your own numbers. No model, no guessing — the rule that produced each sentence is shown next to it.
It admits what it doesn't know
When a figure is an upper bound, when consent limited it, when a batch never arrived — the dashboard says so on the tile, not in a support article.
Yours if you want it
The SDK and the Cloudflare backend are MIT. Run the whole pipeline in your own account, or let us host it and skip the ops.
One package, one key, first event in minutes
Add the Swift package, paste the write key the setup wizard hands you, and watch the page flip to Connected when your first batch lands. Zero dependencies, Swift 6 language mode, actor-based.
- iOS 18+, iPadOS 18+, macOS 15+
- Foundation and
osonly — nothing else to audit track()returns once the event is on disk, so a kill doesn't lose it- Write keys are append-only and scoped to one project
import Stats
import StatsCloudflare
let stats = StatsClient(configuration: StatsConfiguration(
appId: "com.example.MyApp",
projectId: "myapp",
installIdSalt: "a-constant-per-app-string",
sink: CloudflareSink(
endpoint: try CloudflareEndpoint(string: "https://api.swiftstats.co"),
writeKey: writeKey
),
autoEvents: [.appOpen, .appBackground, .sessions]
))
await stats.track("paywall_shown", props: ["variant": "b"])Start free, or run it yourself
Unlimited events on every tier. Self-hosting always free. One app on one platform, free forever. No card required.