# Set up Tapll in your iOS app You are integrating the Tapll iOS SDK (https://github.com/tapll/tapll-ios, v0.5.0). This is the generic version of the prompt; the one in the Tapll panel (Settings → SDK setup) is filled in with the project's write key, privacy mode and integrations. ## Goal Integrate the Tapll iOS SDK into this app. Tapll is product analytics and crash reporting for mobile apps: live sessions, screens, feature funnels, releases, retention, crashes and revenue. The SDK is a Swift Package with no third-party dependencies, no IDFA and no ATT prompt. Work in four phases and do not skip ahead: **explore → install and configure → propose a feature plan and wait for approval → instrument and verify.** The rules below are enforced by the Tapll server. Most mistakes are not errors — the event is accepted and the numbers are quietly wrong — so follow them exactly rather than approximately. ## 1. Explore before you edit Read the project and report back, before changing anything: - The app entry point (SwiftUI `App` struct, or `AppDelegate` / `SceneDelegate`) and the deployment target. The SDK needs **iOS 16+**; if the target is lower, stop and say so. - How screens are built and navigated (SwiftUI `NavigationStack`, tabs, sheets, UIKit view controllers). - Sign-in and sign-out, and what stable user id the backend uses. - Purchases: StoreKit 2 `product.purchase(…)`, StoreKit 1, RevenueCat, or none. - Deep links (`onOpenURL`, `scene(_:openURLContexts:)`, universal links). - Survicate, if present. - Any existing analytics (Firebase, Mixpanel, Amplitude, PostHog, custom). Their call sites are the best map of the moments worth measuring — but do not copy their event names blindly; they must follow the naming rules below. Do not remove the other SDK unless asked. ## 2. Install Add the Swift Package `https://github.com/tapll/tapll-ios` (from: "0.5.0") and link the `Tapll` library to the **app target only** (not extensions or test targets). With an `.xcodeproj`, add it through the project's package dependencies; with a `Package.swift`, add: ```swift .package(url: "https://github.com/tapll/tapll-ios", from: "0.5.0") ``` Do not add CocoaPods or Carthage variants — there are none. ## 3. Configure Call `configure` exactly once, as early as possible — in the SwiftUI `App` initializer or at the top of `application(_:didFinishLaunchingWithOptions:)`: ```swift import Tapll // privacyMode must match the project's setting in Tapll (Settings → Privacy): // .productAnalytics or .strictAnonymous. It cannot be changed after the project is created. Tapll.configure(writeKey: "av_…", privacyMode: .productAnalytics) ``` - Replace `av_…` with the project's client write key from Tapll → Settings → SDK write keys. It is meant to ship in the app binary. - One key serves every build. The SDK detects the environment itself (DEBUG → `development`, TestFlight → `testflight`, App Store → `production`) and sends it with every batch. Do **not** set `Options.environment` unless the user asks for it. - For local debugging, raise the log level in DEBUG builds only: ```swift var options = Options() #if DEBUG options.logLevel = .debug #endif Tapll.configure(writeKey: "av_…", privacyMode: .productAnalytics, options: options) ``` - `configure` already sends installs, updates, sessions, foreground/background and device context. Do not send your own `app_opened`, `session_started` or similar — they duplicate what the panel already counts. - Deep links: call `Tapll.handleDeepLink(url)` from every place the app receives a URL. It records scheme, host and path only, never the query or fragment. ## 4. Screens - SwiftUI: add `.trackScreen("Name")` to the root view of each screen (not to reusable subviews, rows or cells). UIKit: set `options.automaticScreenTracking = true`, or call `Tapll.screen("Name")` in `viewDidAppear`. - Names are stable and PascalCase, one per screen: `"Checkout"`, `"WorkoutDetail"`. **Never put data in a screen name** (`"Profile-\(userId)"`, a title, a date) — every distinct string becomes a separate screen. - Sheets and full-screen covers that the user perceives as a screen get their own name. Alerts, menus and toasts do not. ## 5. Identity **If the project is Product Analytics:** call `Tapll.identify(userId:)` right after sign-in (and at launch when a session is restored) with the backend's **stable, opaque user id** — never an email address, phone number or name as the id. Call `Tapll.reset()` on sign-out. - Traits (`identify(userId:traits:)`, ≤ 8 keys, values ≤ 256 characters) are optional. If you add `email` or `name`, the app is collecting that data and it must appear in the App Store privacy answers — tell the user rather than deciding for them. **If the project is Strict Anonymous:** Do **not** call `Tapll.identify` or `Tapll.setCrashKey`, and do not read `Tapll.installId` / `Tapll.installUUID` (they are `nil`). The server rejects identity in this mode with `privacy_violation`. Screens, events, features and crashes all still work. ## 6. Feature plan — propose it, then wait for approval A feature in Tapll is a funnel built from events the code **declares**. A declaration from a TestFlight or App Store build creates the feature in the panel permanently (development builds only pre-fill the editor). So before instrumenting anything, write a plan and **stop until the user approves it**. Present it as a table: `feature key | event name | role | where it fires (file / function) | properties`. Then implement it with the feature handle: ```swift let checkout = Tapll.feature("checkout") checkout.screen("Checkout") // discovery checkout.start("checkout_started") // start checkout.use("shipping_selected") // use checkout.success("order_placed", properties: ["items": 3]) // success checkout.failure("payment_failed", properties: ["reason": "card_declined"]) // failure ``` Roles — use them precisely: - `discovery` — saw that the feature exists, has not tried it yet (a screen view, opening a menu). `feature.screen(…)` sets it for you. - `start` — made an attempt whose outcome is not known yet. **This is the funnel's denominator**; a feature without `start` has no funnel, only a counter. - `use` — working inside the feature; may happen many times per attempt. It is **not** a funnel step and never the default bucket. - `success` — the feature gave the user what they came for. Once per attempt, not once per tap. - `failure` — the attempt failed **from the user's point of view**: permission denied, no results, a save that did not happen. Not every caught error — those go to `Tapll.recordError`. Rules the server enforces: - **One event name = one (feature, role) pair**, everywhere in the code. The same name as `success` in one place and `failure` in another is a conflict the server shows and never resolves. - Giving up is its **own event name** (`onboarding_completed` and `onboarding_abandoned`), not one event with a `skipped` property. - A feature is something a user **can fail to adopt**. Never name a feature after a data model or a core surface every user touches on day one (`items`, `home`, `users`) — it reports 100% adoption and says nothing. Send those events without a feature key. - Every feature needs a real path to `start` and `success`. If the attempt can visibly fail, give that path a `failure` event, or the funnel reads as if nobody ever fails. - A declared feature that nothing emits is worse than none: it reads as zero adoption. Declare only what you actually instrument. - Aim for 3–8 features for a typical app. Fewer, well-bounded features beat many thin ones. - Feature keys follow the event-name rules below. A typo creates a second feature, so define each key once (a constant or a static handle) and reuse it. - Events that belong to no feature are fine: `Tapll.track("settings_changed", properties: ["setting": "units"])`. ## 7. Event names and properties - Names: lowercase `snake_case`, matching `^[a-z][a-z0-9_]*$`, ≤ 64 characters, `object_action` in the past tense (`workout_finished`, `photo_shared`). The `$` prefix is reserved for the SDK — never use it. - Properties: ≤ 32 keys per event, keys in the same `snake_case` form; values are a string (≤ 256 characters), a finite number or a bool. No nested objects, arrays or dates (send an ISO string or a number). - Keep values **low-cardinality** (an enum-like `plan: "pro"`, a count, a duration in seconds). Ids, timestamps and free text in properties make every value unique and useless for breakdowns. - An invalid event is logged and **dropped**, never truncated or partially sent. The SDK never throws or crashes the app. - `Tapll.track(_:properties:screen:)` can say where an event happened (`screen: "Checkout"`) without counting a screen view. ## 8. What must never be sent - Anything the user typed: search queries, messages, notes, form fields, names of things they created. - Tokens, passwords, API keys, receipts, full URLs or query strings. - Precise location, contacts, photos metadata, advertising identifiers. - Survey answers, comments or scores. - Free text in `Tapll.recordError(_:reason:)` — use a short code (`"card_declined"`), not an exception message that may contain user data. If a moment is only interesting because of its content, send the fact and a category (`note_created`, `length_bucket: "long"`), never the content. ## Purchases Tapll links purchases, renewals and refunds to a person. Do this whichever purchase stack the app uses: With **StoreKit 2**, pass Tapll's install id as the `appAccountToken` on every purchase: ```swift let result = try await product.purchase( options: Tapll.installUUID.map { [.appAccountToken($0)] } ?? [] ) ``` - The token **must be a UUID**; the App Store silently accepts other values and links nothing. If the app already sets `appAccountToken` to something else, stop and ask — there can be only one. - It is not retroactive: purchases made before it ships can never be linked. It must ship before the first release that sells anything. With **RevenueCat**, set the subscriber attribute after `Purchases.configure` and whenever the user changes: ```swift if let id = Tapll.installId { Purchases.shared.attribution.setAttributes(["tapll_install_id": id]) } ``` - Call `Tapll.identify(userId:)` with the same id the app passes to RevenueCat as `app_user_id` (`Purchases.logIn`). - Revenue itself arrives through RevenueCat's webhook to Tapll — do not send purchase events from the app. If the project has App Store Server Notifications connected in Tapll, those own the revenue numbers and RevenueCat is not counted; set the token either way. ## Survicate (NPS) Only if the app uses Survicate: ```swift if let id = Tapll.installId { SurvicateSdk.shared.setUserTrait(UserTrait(withName: "tapll_install_id", value: id)) } ``` - That trait puts each answer on the right person. Set it after `Tapll.configure`. - From Survicate's survey listener, send exactly these two events — the server looks the names up literally, so a typo fails silently: ```swift Tapll.track("survey_displayed", properties: ["survey_id": surveyId]) Tapll.track("survey_closed", properties: ["survey_id": surveyId]) ``` - Never send the answer, score or comment to Tapll. Scores arrive from Survicate's webhook; answer text is never stored. ## Crashes, hangs and errors Crash and hang reporting is on by default once `configure` runs — do not add another crash reporter's handlers on top without asking (if the app already has Crashlytics or Sentry, report it and let the user decide). A crash is uploaded on the **next launch**, so it appears later, not immediately. - `Tapll.recordError("PaymentFailed", reason: "card_declined")` or `Tapll.recordError(error)` for caught errors that matter. Not for expected control flow. - `Tapll.leaveBreadcrumb("retrying_upload")` before risky operations — a name only, never a value. - `Tapll.setCrashKey("plan", "pro")` for up to 8 labels on every later report. Same rules as properties: no ids, no personal data. Upload dSYMs so crash frames get function names. Use whichever CI the project has; the key is a **server key** stored as the secret `TAPLL_SERVER_KEY` (created in Tapll → Settings → Server keys) — never paste it into the project or the prompt. The script is pinned by SHA-256 45588f5ee591…, and must be downloaded and verified exactly as shown: - **Xcode Cloud** (`ci_scripts/ci_post_xcodebuild.sh`): Add TAPLL_SERVER_KEY to the workflow as a secret environment variable. Runs after every archive action. ```sh #!/bin/sh set -e if [ "$CI_XCODEBUILD_ACTION" = "archive" ]; then curl -fsSL https://tapll.com/dsyms/v3.sh -o "/tmp/tapll-dsyms.sh" \ && echo "45588f5ee591290d86e5e82a02f56e47c7256a66f303580e13e2f4caa137af8d /tmp/tapll-dsyms.sh" | shasum -a 256 -c - \ && bash "/tmp/tapll-dsyms.sh" "$CI_ARCHIVE_PATH" fi ``` - **fastlane** (`fastlane/Fastfile`): After build_app (gym), which leaves the zipped dSYMs at DSYM_OUTPUT_PATH. Set TAPLL_SERVER_KEY in your CI secrets. ```ruby lane :release do build_app(scheme: "MyApp") sh(<<~SH) curl -fsSL https://tapll.com/dsyms/v3.sh -o "/tmp/tapll-dsyms.sh" \ && echo "45588f5ee591290d86e5e82a02f56e47c7256a66f303580e13e2f4caa137af8d /tmp/tapll-dsyms.sh" | shasum -a 256 -c - \ && bash "/tmp/tapll-dsyms.sh" "#{lane_context[SharedValues::DSYM_OUTPUT_PATH]}" SH upload_to_testflight end ``` - **Build phase** (Run Script phase, last in the target): Release builds only. Turn off User Script Sandboxing for the target (the script reads the dSYM and uses the network), and provide TAPLL_SERVER_KEY from the environment — never paste it into the project. ```sh [ "$CONFIGURATION" = "Release" ] || exit 0 curl -fsSL https://tapll.com/dsyms/v3.sh -o "$DERIVED_FILE_DIR/tapll-dsyms.sh" \ && echo "45588f5ee591290d86e5e82a02f56e47c7256a66f303580e13e2f4caa137af8d $DERIVED_FILE_DIR/tapll-dsyms.sh" | shasum -a 256 -c - \ && bash "$DERIVED_FILE_DIR/tapll-dsyms.sh" "$DWARF_DSYM_FOLDER_PATH" ``` Set up only the one that matches the project, and tell the user to add the secret. ## Server-side events Events that happen on the app's backend (a subscription renewed server-side, an invoice paid) go to `POST https://in.tapll.com/v1/server` with a **server key** (`sk_…`) — from the backend only, never from the app and never committed. The SDK never calls that endpoint. If the backend needs to attribute an event to the device, the app sends `Tapll.installId` to its own backend. Only mention this to the user; do not implement backend code unless asked. ## App privacy The SDK ships a `PrivacyInfo.xcprivacy`. Tell the user which App Store privacy answers apply — you cannot change them in code: - Product Analytics: Product Interaction, Device ID and User ID — linked to the user, used for analytics, **not** tracking. Plus anything added as traits (email, name). - Strict Anonymous: Product Interaction, not linked to the user. Remove the Device ID and User ID entries from the app's own privacy manifest answers. - Crash data counts as Other Diagnostic Data. - Tapll uses no IDFA and needs no App Tracking Transparency prompt. Do not describe the data as "fully anonymous" in the app's privacy copy unless the project is Strict Anonymous. ## Verify 1. The project builds (`xcodebuild` for the app scheme, or build in Xcode) with no new warnings from the integration. 2. Run a DEBUG build in the simulator. With `logLevel = .debug` the SDK logs to the unified log under subsystem `app.tapll.sdk` (visible in the Xcode console). Invalid events are logged there with the reason — fix every one. 3. Background the app (or wait 10 s) to flush. In the Tapll panel, switch the environment to **Sandbox** — a DEBUG build is `development`, which the Production view never shows — and open **Live**. The session should appear within seconds. 4. Walk through each planned feature once and confirm its events show up under **Data** with the declared feature and role. ## Final report Finish with: - The files you changed. - The event catalog as a table: event | feature | role | properties | where it fires. - What you deliberately did **not** instrument, and why. - What is left for a human: App Store privacy answers, the `TAPLL_SERVER_KEY` CI secret for dSYM upload, connecting purchases in Tapll Settings, and anything in the plan they still need to decide. --- # Set up Tapll on your website You are adding the Tapll website script (https://github.com/tapll/tapll-web, v0.5.0). This is the generic version of the prompt; the one in the Tapll panel (Settings → SDK setup) is filled in with the project's write key, privacy mode and domains. ## Goal Measure this website with Tapll: visitors, page views, sources and campaigns, entry and exit pages, feature funnels and revenue. One script tag, no cookies by default, about 5 KB gzipped. Work in four phases and do not skip ahead: **explore → install → propose a feature plan and wait for approval → instrument and verify.** ## 1. Explore before you edit Report back before changing anything: - The framework and where the document `` is rendered (Next.js `app/layout.tsx` or `_document`, Astro layout, a plain `index.html`, a CMS theme). - Whether navigation is client-side (an SPA router). The script follows `history.pushState` itself — do not add a router hook. - Any existing analytics (Google Analytics, Plausible, PostHog, Segment). Their call sites map the moments worth measuring; do not remove them unless asked. - Whether the site takes payments, and with what: Stripe Checkout or Payment Links, or Paddle (Paddle.js). ## 2. Install Add exactly once, in the `` of every page: ```html ``` - Replace `av_…` with the project's client write key from Tapll → Settings → SDK write keys. - Tapll accepts page views only from the domains listed in Settings → Domains (when the list is not empty). Make sure the production domain is there. - `localhost`, `127.*` and `*.local` report as **development** (the Sandbox scope in the panel); everything else as production. `data-env="development"` forces it for a staging host. - Optional attributes: `data-version=""` (git SHA or release tag — turns on Releases), `data-exclude="/admin/*,/preview/*"`, `data-api="/_tapll"` to send through your own domain (see below). - In a framework, render the tag as raw HTML in the head (Next.js: ` ``` ## 3. Privacy mode **If the project is Strict Anonymous (the default):** the script stores nothing in the browser — no cookies, no localStorage — so no consent banner is needed for it. Visitors are recognised within one day only. Do **not** call `tapll.identify`; the server rejects identity in this mode. **If the project is Product Analytics:** the script keeps an anonymous id and the session in localStorage. Call `tapll.identify(userId)` after login with the app's own stable user id — never an email. - **Consent banner — optional, off by default.** The snippet collects from page load. Add a banner only if the site owner asks for one; do not add it on your own. `data-consent="banner"` shows Tapll's banner: until the visitor clicks **Accept**, nothing is stored and nothing is sent — events wait in memory and leave on Accept; **Decline** calls `union.optOut()`. The answer is remembered. Set `data-consent-policy=""` and, if the site is not in English, `data-consent-text`, `data-consent-accept`, `data-consent-decline`. Add a footer link `Privacy choices`. - If the site already has a consent tool (Cookiebot, OneTrust, Klaro, its own) and the owner wants Tapll behind it, use `data-consent="required"` and call `tapll.consent(true)` / `tapll.consent(false)` from that tool's accept/decline callbacks — `union('consent', true)` also works before the script has loaded. Never call `tapll.consent(true)` without the visitor's answer. ## 4. Page views — already done - Every page load and every client-side navigation sends a page view with its **path only**. Query strings and fragments are never sent; UTM parameters are read from the landing URL by the script itself. - Outbound links and file downloads are tracked automatically (`data-auto="false"` turns it off). - Do not send your own `page_view` event. ## 5. Feature plan — propose it, then wait for approval A feature in Tapll is a funnel built from events the code **declares**. A declaration from production creates the feature in the panel permanently. Write a plan as a table — `feature key | event name | role | where it fires | properties` — and **stop until the user approves it**. Then: ```js const signup = tapll.feature('signup'); signup.start('signup_started'); // opened the form with intent signup.success('signup_completed', { plan: 'pro' }); // the account exists signup.failure('signup_failed', { reason: 'email_taken' }); tapll.track('pricing_toggled', { period: 'yearly' }); // no feature ``` - Roles: `discovery` (saw it), `start` (attempted — the funnel's denominator), `use` (working inside it), `success` (got what they came for, once per attempt), `failure` (the attempt failed from the user's point of view). - One event name = one (feature, role) pair everywhere. 3–8 features for a typical site. - Without writing JavaScript, a button can carry `data-tapll-event="cta_clicked"` (plus `data-tapll-feature` and `data-tapll-role`). - `tapll` is safe to call before the script loads if you add `` above it and call `union('track', …)`. ## 6. Event names and properties - Names: lowercase `snake_case` matching `^[a-z][a-z0-9_]*$`, ≤ 64 characters, `object_action` in the past tense. The `$` prefix is reserved. - ≤ 32 properties; values are a string (≤ 256), a finite number or a boolean. Keep them low-cardinality — no ids, emails, timestamps or free text. - An invalid event is dropped with a console warning, never truncated. The script never throws into the page. ## 7. What must never be sent - Anything the visitor typed: search terms, form fields, messages. - Tokens, emails, order ids, full URLs or query strings. - Paths that contain personal data (`/users/ada@example.com`): add them to `data-exclude`. ## Revenue (Stripe) Only if the site takes payments with Stripe: ```js // client: send the visit id with the checkout request const visit = tapll.sessionId(); // the current visit; null until the first page view is acknowledged // server await stripe.checkout.sessions.create({ // … metadata: { tapll_session_id: visit ?? '', tapll_user_id: currentUser?.id ?? '' }, subscription_data: { metadata: { tapll_session_id: visit ?? '', tapll_user_id: currentUser?.id ?? '' } }, }); ``` - **Payment Links and the Pricing Table have no server call to put metadata on.** Append the visit to the link instead, as `client_reference_id`; Tapll reads it as the visit when the Checkout has no `tapll_session_id` metadata: ```js // client: Payment Link const link = new URL('https://buy.stripe.com/…'); const visit = tapll.sessionId(); if (visit) link.searchParams.set('client_reference_id', visit); // Pricing Table: set to the same value ``` - Revenue itself arrives through Stripe's webhook to Tapll — never send purchase amounts from the page. - The visit is what lets Tapll show **revenue per channel, referrer, campaign and entry page** (Traffic), with customers, conversion and revenue per visitor. A payment that names no visit is not lost: it is counted under **Not attributed**. - Credit goes to the customer's first known visit (Product Analytics) or to the visit that checked out (cookieless); renewals stay with the subscription's channel and refunds come off the channel of the original payment. It is a correlation, and the panel says so. ## Revenue (Paddle) Only if the site takes payments with Paddle (Paddle Billing, through Paddle.js): ```js // client, where the checkout is opened Paddle.Checkout.open({ items: [{ priceId: 'pri_…', quantity: 1 }], customData: { tapll_session_id: tapll.sessionId() ?? '', tapll_user_id: currentUser?.id ?? '' }, }); ``` - Revenue itself arrives through Paddle's notification to Tapll — never send purchase amounts from the page. - Paddle copies `customData` onto the transaction and the subscription it creates, so renewals carry the visit too. - The visit is what lets Tapll show **revenue per channel, referrer, campaign and entry page** (Traffic), with customers, conversion and revenue per visitor. A payment that names no visit is counted under **Not attributed**, never dropped. It is a correlation, and the panel says so. ## Verify 1. Open the site locally and check the browser console: no Tapll warnings. 2. In Tapll, switch to **Sandbox** and open **Live** — the visit appears within seconds. 3. Navigate between pages and confirm each path shows up; trigger each planned event once and find it under **Data** with its feature and role. 4. Deploy, open the production site, and confirm the visit in the **Production** scope. ## Final report - The files you changed. - The event catalog as a table: event | feature | role | properties | where it fires. - What is left for a human: the production domain in Settings → Domains, the consent decision (Product Analytics only), and the Stripe webhook or Paddle notification destination if revenue is wanted. --- # Tapll > See who is in your app and on its website right now, where visitors come from, how they use features, whether the new release helped, and what goes with revenue. One project for both, one ready-made panel, no analyst and no query builder. Key facts: - iOS, through a Swift package. Your own backend can send events too, through a separate server endpoint. Websites use one script tag, cookieless by default, and share a project with their app. Not published yet: Android, Flutter and React Native. - One project per product: an app and its website share a project, and the platform is a dimension of every view. Website money (Stripe, Paddle) is never summed with in-app money. - Not part of Tapll: session replay, heatmaps, Web Vitals, JavaScript error tracking, feature flags, A/B tests, SQL access, a public query API, self-hosting. - Plans (EUR per month, per workspace): Free €0 for 100,000 events; Solo €9 for 1,000,000 events; Pro €29 for 5,000,000 events; Studio €79 for 20,000,000 events. Every view and integration on every plan. ## Docs - [Install the iOS SDK](https://tapll.com/docs/ios-sdk.md): Add the Tapll Swift package to your iOS app, configure it with your write key, and see your first session in Live in a few minutes. - [Privacy modes](https://tapll.com/docs/privacy.md): Choose between product analytics and strict anonymous mode for your app and its website in Tapll, and what each one stores and answers. - [Add the website script](https://tapll.com/docs/web-sdk.md): Measure a website with Tapll through one script tag: page views, sources and campaigns, feature funnels and revenue, cookieless by default. - [Server-side events](https://tapll.com/docs/server-events.md): Send events to Tapll from your own backend: payments, background jobs, webhooks, and platforms with no SDK. - [Declare features](https://tapll.com/docs/features.md): Measure each feature of your iOS app as a funnel — discovery, adoption, success — by declaring its events and roles in the Tapll SDK. - [Upload dSYMs](https://tapll.com/docs/dsyms.md): Name crash stacks in Tapll by uploading each build’s dSYMs from Xcode Cloud, fastlane or an Xcode build phase. - [Crash reporting](https://tapll.com/docs/crash-reporting.md): How Tapll collects crashes and hangs from your iOS app, groups them into issues, and computes a crash-free rate that never looks better than it is. - [Connect revenue](https://tapll.com/docs/revenue.md): Bring subscription revenue into Tapll from App Store Server Notifications or RevenueCat, website payments from Stripe or Paddle, and link each purchase to the person who made it. - [Connect App Store Connect](https://tapll.com/docs/app-store-connect.md): Import downloads, impressions, product page views, proceeds, reviews and TestFlight into Tapll with an App Store Connect API key. - [REST API](https://tapll.com/docs/api.md): Read Tapll's numbers from your own scripts and tools with an organization API key: sessions, people, ASO, competitors, web traffic, crashes and revenue. - [Claude, ChatGPT and other AI apps](https://tapll.com/docs/mcp.md): Connect Tapll to Claude, ChatGPT, Cursor or any MCP client and ask about sessions, ASO keywords, competitors, web traffic, crashes and revenue in plain language. ## Integrations - [Website script](https://tapll.com/integrations/website-script): One script tag for your website: visitors, page views, sources and campaigns, cookieless by default. - [RevenueCat](https://tapll.com/integrations/revenuecat): Subscriptions, trials, renewals and refunds from RevenueCat webhooks, tied to the sessions that bought them. - [App Store Server Notifications](https://tapll.com/integrations/app-store-server-notifications): Revenue straight from Apple’s signed notifications, with your own backend still receiving every one. - [Stripe](https://tapll.com/integrations/stripe): Website payments and subscriptions from Stripe webhooks, tied to the visit and channel that brought them. - [Paddle](https://tapll.com/integrations/paddle): Website sales from Paddle Billing notifications, tied to the visit and channel that brought them. - [App Store Connect](https://tapll.com/integrations/app-store-connect): Downloads, impressions, product page views, proceeds, reviews and TestFlight from your App Store Connect key. - [Apple Search Ads](https://tapll.com/integrations/apple-search-ads): Search popularity for the phrases you track, from Apple’s own genre lists. - [Survicate](https://tapll.com/integrations/survicate): NPS answers from Survicate surveys, next to retention and revenue — the score, never the comments. ## Use cases - [Who is in my app right now?](https://tapll.com/use-cases/who-is-in-my-app-right-now): A live view of the people in your app, the screen each is on, and what just happened. - [Did my last release help or hurt?](https://tapll.com/use-cases/did-my-release-help): Each version’s adoption, crash-free rate and the app’s numbers in the days before and after it. - [Which features go with subscribers?](https://tapll.com/use-cases/which-features-go-with-subscribers): Feature adoption next to subscriptions and retention — labelled as correlation, never as cause. - [Is my new feature used to the end?](https://tapll.com/use-cases/is-my-feature-used): Discovery, adoption and success for each feature, with where people drop off. - [How does my App Store listing convert?](https://tapll.com/use-cases/app-store-listing-conversion): Search impressions to downloads per market, keyword positions, rivals and who sent the downloads. - [Which channels bring paying customers?](https://tapll.com/use-cases/which-channels-bring-paying-customers): Stripe and Paddle payments next to the channel and campaign of the visit that made them. - [Can I measure my site without a cookie banner?](https://tapll.com/use-cases/analytics-without-a-cookie-banner): Strict anonymous by default: nothing stored in the browser. A built-in consent banner when you want identity. - [How does my landing page feed my app?](https://tapll.com/use-cases/from-landing-page-to-app): The website and the app in one project, with a platform switcher, and the App Store’s referrers beside the site’s channels. ## Comparisons - [Tapll vs DataFast](https://tapll.com/compare/datafast): Revenue-first web analytics with many integrations and an API; Tapll measures the app and the site in one project. - [Tapll vs Plausible](https://tapll.com/compare/plausible): Simple, privacy-first web analytics with open code and many integrations; Tapll adds the app next to the site. - [Tapll vs visitors.now](https://tapll.com/compare/visitors-now): A tiny script with realtime, Web Vitals and revenue; Tapll adds native iOS, crashes and App Store data. - [Tapll vs Fathom](https://tapll.com/compare/fathom): Cookieless web analytics with CMS and framework integrations; Tapll adds the app, feature funnels and revenue. - [Tapll vs Firebase Analytics](https://tapll.com/compare/firebase-analytics): Free and everywhere, but you assemble the answers — and revenue and crashes are separate products. - [Tapll vs TelemetryDeck](https://tapll.com/compare/telemetrydeck): Privacy-first and multi-platform; Tapll adds live people, crashes, releases and revenue. - [Tapll vs Mixpanel](https://tapll.com/compare/mixpanel): A deep report builder for teams with an analyst; Tapll is a ready panel for an iOS app and its site. - [Tapll vs Amplitude](https://tapll.com/compare/amplitude): An enterprise analytics suite; Tapll is the small, ready-made panel for one iOS subscription app. - [Tapll vs PostHog](https://tapll.com/compare/posthog): An open-source all-in-one toolbox; Tapll is a focused panel for iOS subscription apps and their sites. - [Tapll vs Aptabase](https://tapll.com/compare/aptabase): Simple, open-source, anonymous analytics; Tapll adds people, features, crashes and revenue. ## Optional - [Pricing](https://tapll.com/pricing): plans, limits and retention. - [iOS SDK source](https://github.com/tapll/tapll-ios): the Swift package. - [Website script source](https://github.com/tapll/tapll-web): the script served from https://cdn.tapll.com/v0/u.js.