The SDK's module is called AppCoreKit — FolioKit's original
working name, kept so shipped apps never need a rename. It's distributed via
Swift Package Manager and pinned to version tags, never a moving branch.
You don't integrate it — your agent does
Adding an app in the dashboard produces one self-contained prompt. Paste it into Claude Code, Cursor, or whatever agent you build with: it adds the package, starts the SDK, wires the RevenueCat handshake, instruments your screens and key events, then calls a live verification endpoint with your app's own credentials and reports "you're live — events are flowing". The integration rules the agent follows are strict: events fire once per user action, paired events must have similar counts, and no PII goes into properties.
Automatic events
Tracked without any code:
| Event | When |
|---|---|
app_opened | launch and foreground (a new session starts after a background gap) |
session_end | app backgrounded; session length recorded |
screen_view | each screen marked with .appCoreScreen("Name") |
ad_attribution | Apple Search Ads attribution via AdServices, when present |
Standard events
Instrumented by the agent at the moments that matter — the dashboard's funnel, the SKAdNetwork mapping, and the weekly brief all expect these exact names:
| Event | When |
|---|---|
onboarding_step | each onboarding step, with the step number |
onboarding_completed | user finishes onboarding |
paywall_shown | the paywall becomes visible, any trigger |
trial_started | a trial begins |
purchase_completed | a purchase goes through |
Plus a handful of app-specific events for your core value loop
(scan_completed, book_finished, …) — moments a user
gets value, not UI minutiae. There is deliberately no autocapture.
Delivery: offline queue, batched uploads
Events are queued on the device and uploaded in batches — roughly every 15 seconds and immediately when the app goes to the background. No network, no problem: the queue persists and retries on the next flush. Server errors are retried; permanently rejected batches are dropped rather than wedging the queue. All calls are fire-and-forget and never block the main thread.
Remote config — change behavior without an app update
- Feature flags and config values, fetched and cached by the SDK.
- Server-gated rating prompt:
requestReviewIfAppropriate()is a no-op until you enable it from the dashboard; it self-limits by session count and cooldown and never interrupts a first-time user. - Minimum version signal: set a minimum version
server-side and the SDK exposes
updateRequiredfor your own "please update" UI. - SKAdNetwork conversion values are mapped from the standard events by server config — retunable without shipping a build.
"No ATT prompt" — what that means, exactly
This claim gets abused in analytics marketing, so here is the precise version:
- Apple Search Ads attribution is deterministic, through Apple's own AdServices framework. The install's campaign comes from Apple directly. No ATT consent is involved because no cross-app tracking occurs.
- Paid social (Meta, TikTok, …) is measured in aggregate, through SKAdNetwork conversion values: Apple reports installs and conversion quality per campaign — anonymised, delayed by design, and never joinable to an individual user. You get campaign-level truth, not user-level surveillance.
- What FolioKit will not do: per-user link → install → purchase attribution. That requires fingerprinting or deferred deep-link matching — the practices that need an ATT prompt, violate App Review rules without one, and are precisely the trap that makes MMPs look necessary.
Nothing the SDK does is "tracking" in Apple's definition — no IDFA, no fingerprinting, no cross-app device graphs — which is why no ATT prompt is required. The full list of what's collected is on the data & privacy page.
Honest limits
- iOS only. The web snippet covers landing pages; Android when demand pulls. No Flutter or React Native wrappers today.
- Requires RevenueCat for revenue data — FolioKit completes it, it doesn't replace it.
- Instrumented events only: if you don't track it, it isn't there. We consider that a feature.