How Apple Search Ads Tracking Works
Apple Search Ads places ads at the top of App Store search results. Attribution works through two parallel systems:
- Apple Ads Attribution API -- A device-level attribution framework. After an app install, your app calls the Attribution API on first launch to determine if the install came from an Apple Search Ads campaign. The API returns campaign metadata (campaign ID, ad group ID, keyword, etc.) without exposing the user's identity.
- SKAdNetwork (SKAN) -- Apple's privacy-preserving attribution framework. The App Store sends a postback to the ad network (Apple) with a conversion value and source app/campaign information. SKAN does not require user consent under ATT.
The data flow for Attribution API: user searches the App Store, taps a Search Ads result, installs and opens the app. On first launch, your app calls AdServices.AAAttribution.attributionToken() (iOS 14.3+) or the legacy iAd framework. Apple returns an attribution record containing the campaign, ad group, keyword, and other metadata. Your app sends this to your backend or MMP for recording.
The data flow for SKAdNetwork: user taps the ad and installs. The App Store registers the install with SKAN. Your app optionally updates a conversion value (0-63 for SKAN 3.0, or fine/coarse for SKAN 4.0+). After a timer expires (24-48 hours for SKAN 4.0), Apple sends a postback to the ad network with attribution data and the conversion value.
Both systems work without requiring the user to opt into tracking via ATT. Apple Search Ads is one of the few ad platforms that maintains deterministic attribution on iOS regardless of ATT status.
Installing Attribution Tracking
AdServices Framework (iOS 14.3+)
The recommended attribution method. Call the AdServices API on first app launch:
import AdServices
func checkAttribution() {
if #available(iOS 14.3, *) {
do {
let token = try AAAttribution.attributionToken()
// Send token to your server for attribution lookup
sendTokenToServer(token)
} catch {
print("Attribution token not available: \(error)")
}
}
}
func sendTokenToServer(_ token: String) {
var request = URLRequest(url: URL(string: "https://api-adservices.apple.com/api/v1/")!)
request.httpMethod = "POST"
request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
request.httpBody = token.data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data,
let attribution = try? JSONDecoder().decode(AttributionRecord.self, from: data)
else { return }
// Process attribution record
handleAttribution(attribution)
}.resume()
}
Attribution Response
The API returns a JSON object:
{
"attribution": true,
"orgId": 123456,
"campaignId": 789012,
"conversionType": "Download",
"clickDate": "2026-03-01T12:00:00Z",
"adGroupId": 345678,
"countryOrRegion": "US",
"keywordId": 901234,
"keyword": "project management app",
"creativeSetId": 567890,
"adId": 112233
}
If attribution is false, the install was not from Apple Search Ads.
Legacy iAd Framework (iOS 13 and Earlier)
For backward compatibility with iOS 13:
import iAd
func checkLegacyAttribution() {
ADClient.shared().requestAttributionDetails { (attributionDetails, error) in
guard let details = attributionDetails else { return }
// details contains campaign info under "Version3.1" key
if let iadInfo = details["Version3.1"] as? [String: Any] {
let campaignId = iadInfo["iad-campaign-id"]
let keyword = iadInfo["iad-keyword"]
// Process attribution
}
}
}
The iAd framework is deprecated but still functional. Use AdServices for iOS 14.3+ and fall back to iAd for older versions.
SKAdNetwork Integration
SKAN 4.0 (iOS 16.1+)
SKAN 4.0 introduces multiple postbacks and hierarchical conversion values:
- First postback -- Sent 24-48 hours after install. Contains fine conversion value (0-63) for high-privacy-threshold campaigns, or coarse value (low/medium/high) for lower volume.
- Second postback -- Sent 3-7 days after install. Coarse value only.
- Third postback -- Sent 8-35 days after install. Coarse value only.
Configuring Conversion Values
Map post-install events to conversion values. Higher values indicate more valuable user actions:
import StoreKit
// SKAN 4.0: Update conversion value
if #available(iOS 16.1, *) {
SKAdNetwork.updatePostbackConversionValue(42, coarseValue: .high) { error in
if let error = error {
print("SKAN update error: \(error)")
}
}
}
// SKAN 3.0 fallback
if #available(iOS 15.4, *) {
SKAdNetwork.updatePostbackConversionValue(42) { error in
if let error = error {
print("SKAN update error: \(error)")
}
}
}
Conversion Value Mapping
Define a mapping scheme for your conversion values. Example for a subscription app:
| Value | Event | Revenue Proxy |
|---|---|---|
| 0 | Install only | $0 |
| 1-10 | Completed onboarding | $0 |
| 11-20 | Started free trial | $2.99 (estimated) |
| 21-40 | Engaged (3+ sessions in 24h) | $4.99 (estimated) |
| 41-55 | Subscribed (monthly) | $9.99 |
| 56-63 | Subscribed (annual) | $79.99 |
SKAN Postback Handling
Postbacks are sent to the URL registered in your Info.plist under SKAdNetworkItems. For Apple Search Ads, Apple handles the postback processing. You can also configure your MMP to receive and decode postbacks.
Postback payload example:
{
"version": "4.0",
"ad-network-id": "apple.com",
"campaign-id": 42,
"transaction-id": "unique-uuid",
"source-app-id": 0,
"fidelity-type": 1,
"conversion-value": 42,
"did-win": true,
"postback-sequence-index": 0,
"coarse-conversion-value": "high",
"source-identifier": "1234"
}
Campaign Management API
Apple Search Ads provides a REST API for programmatic campaign management.
Authentication
The API uses OAuth 2.0 with a client secret. Generate credentials in the Apple Search Ads UI under Settings > API.
# Get access token
curl -X POST "https://appleid.apple.com/auth/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&scope=searchadsorg"
Create a Campaign
curl -X POST "https://api.searchads.apple.com/api/v5/campaigns" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-AP-Context: orgId=YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "Brand Defense - US",
"budgetAmount": {"amount": "5000", "currency": "USD"},
"dailyBudgetAmount": {"amount": "200", "currency": "USD"},
"adamId": 123456789,
"countriesOrRegions": ["US"],
"status": "ENABLED",
"supplySources": ["APPSTORE_SEARCH_RESULTS"]
}'
Create an Ad Group with Keywords
curl -X POST "https://api.searchads.apple.com/api/v5/campaigns/CAMPAIGN_ID/adgroups" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-AP-Context: orgId=YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"name": "Exact Match - Brand Terms",
"defaultBidAmount": {"amount": "2.50", "currency": "USD"},
"startTime": "2026-03-01T00:00:00.000",
"targetingDimensions": {
"deviceClass": {"included": ["IPHONE", "IPAD"]},
"gender": {"included": ["M", "F"]},
"age": {"included": [{"minAge": 18}]},
"locality": {"included": ["US|NY", "US|CA"]}
},
"status": "ENABLED"
}'
Add Keywords
curl -X POST "https://api.searchads.apple.com/api/v5/campaigns/CAMPAIGN_ID/adgroups/ADGROUP_ID/targetingkeywords/bulk" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-AP-Context: orgId=YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '[
{
"text": "project management",
"matchType": "EXACT",
"bidAmount": {"amount": "3.00", "currency": "USD"},
"status": "ACTIVE"
},
{
"text": "task manager app",
"matchType": "BROAD",
"bidAmount": {"amount": "2.00", "currency": "USD"},
"status": "ACTIVE"
}
]'
Add Negative Keywords
Prevent ads from showing on irrelevant searches:
curl -X POST "https://api.searchads.apple.com/api/v5/campaigns/CAMPAIGN_ID/adgroups/ADGROUP_ID/negativekeywords/bulk" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-AP-Context: orgId=YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '[
{"text": "free project management", "matchType": "EXACT"},
{"text": "open source", "matchType": "BROAD"}
]'
Pull Campaign Reports
curl -X POST "https://api.searchads.apple.com/api/v5/reports/campaigns" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-AP-Context: orgId=YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"startTime": "2026-03-01",
"endTime": "2026-03-07",
"granularity": "DAILY",
"selector": {
"orderBy": [{"field": "localSpend", "sortOrder": "DESCENDING"}],
"pagination": {"offset": 0, "limit": 100}
},
"returnRowTotals": true,
"returnGrandTotals": true
}'
Report metrics include: impressions, taps, installs, newDownloads, redownloads, latOnInstalls, latOffInstalls, ttr (tap-through rate), avgCPA, avgCPT (cost per tap), localSpend.
MMP Integration
Mobile Measurement Partners (MMPs) like AppsFlyer, Adjust, Branch, Singular, and Kochava provide unified attribution across Apple Search Ads and other channels.
How MMPs Work with Apple Search Ads
- The MMP SDK calls the AdServices Attribution API on first app launch.
- The MMP receives the attribution token and sends it to Apple's attribution endpoint.
- Apple returns the attribution record to the MMP.
- The MMP combines this with SKAN postbacks and their own tracking to provide unified reporting.
MMP SDK Integration (AppsFlyer Example)
import AppsFlyerLib
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppsFlyerLib.shared().appsFlyerDevKey = "YOUR_DEV_KEY"
AppsFlyerLib.shared().appleAppID = "YOUR_APP_ID"
AppsFlyerLib.shared().waitForATTUserAuthorization(timeoutInterval: 60)
return true
}
func applicationDidBecomeActive(_ application: UIApplication) {
AppsFlyerLib.shared().start()
}
The MMP SDK handles AdServices attribution, SKAN conversion value updates, and postback decoding automatically. Configure your SKAN conversion value schema in the MMP dashboard.
Search Match vs. Keyword Targeting
Apple Search Ads offers Search Match, which automatically matches your ad to relevant search queries based on your app's metadata (title, description, keywords). This is useful for discovery but provides less control.
To analyze Search Match performance, pull the search terms report:
curl -X POST "https://api.searchads.apple.com/api/v5/reports/campaigns/CAMPAIGN_ID/searchterms" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-AP-Context: orgId=YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"startTime": "2026-03-01",
"endTime": "2026-03-07",
"granularity": "DAILY",
"selector": {
"orderBy": [{"field": "impressions", "sortOrder": "DESCENDING"}]
}
}'
Use search term data to find high-performing queries and add them as exact-match keywords. Add irrelevant queries as negative keywords.
Common Issues
| Issue | Cause | Fix |
|---|---|---|
Attribution API returning attribution: false for all installs |
App not calling the API on first launch, or calling it too late | Call AAAttribution.attributionToken() immediately on first launch in applicationDidBecomeActive. The token expires after 24 hours. |
| SKAN conversion value always 0 | updatePostbackConversionValue not being called, or called with value 0 |
Verify your conversion value update logic fires on post-install events. Remember SKAN values can only increase -- you cannot lower a value once set. |
| SKAN postback not received | Timer has not expired, or postback URL misconfigured | SKAN 4.0 first postback has a 24-48 hour random delay. Check that your Info.plist includes the correct SKAdNetworkItems entry for Apple (apple.com). |
| High CPA on broad match keywords | Broad match triggering on irrelevant searches | Pull the search terms report. Add irrelevant terms as negative keywords. Consider switching high-spend broad match keywords to exact match. |
| Low impression share | Bid too low for competitive keywords, or daily budget cap reached | Increase bids on target keywords. Check if daily budget is being exhausted early. Review the "Search popularity" metric for keyword volume. |
| Attribution discrepancy between Apple and MMP | Different attribution windows or methodologies | Apple uses a 30-day click-through window. MMPs may use different windows. Verify settings match across platforms. |
latOnInstalls vs latOffInstalls confusion |
LAT (Limit Ad Tracking) refers to users who opted out of tracking | latOnInstalls = users with tracking limited. latOffInstalls = users allowing tracking. Post-ATT, most installs are latOnInstalls. |
| Custom Product Page not showing | Creative set not associated with ad group, or product page not approved | Verify the Custom Product Page is approved in App Store Connect. Associate the creative set with the ad group in Campaign Manager. |
Apple Search Ads-Specific Considerations
ATT and Apple Search Ads
Apple Search Ads attribution works regardless of ATT status. The AdServices framework does not require requestTrackingAuthorization. This is because Apple considers Search Ads attribution to be first-party (Apple attributing installs within its own platform), not cross-app tracking.
This gives Apple Search Ads a significant measurement advantage over competing ad networks that rely on ATT consent for deterministic attribution.
Tap-Through vs. View-Through
Apple Search Ads uses tap-through attribution only. There is no view-through attribution for search result ads. A user must tap the ad for the install to be attributed.
For Today tab ads, Apple attributes installs within a tap-through window of 30 days.
Custom Product Pages
Create up to 35 Custom Product Pages in App Store Connect, each with different screenshots, app previews, and promotional text. Assign specific product pages to ad groups in Apple Search Ads for tailored landing experiences.
# Assign custom product page to ad group via API
curl -X PUT "https://api.searchads.apple.com/api/v5/campaigns/CAMPAIGN_ID/adgroups/ADGROUP_ID" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "X-AP-Context: orgId=YOUR_ORG_ID" \
-H "Content-Type: application/json" \
-d '{
"defaultBidAmount": {"amount": "2.50", "currency": "USD"},
"pricingModel": "CPC"
}'
Budget and Bidding
Apple Search Ads supports two bidding strategies:
- Max CPT bid -- You set a maximum cost per tap. Apple's algorithm bids up to this amount.
- Target CPA -- You set a target cost per acquisition (install). Apple's algorithm adjusts bids to achieve this target. Available only for campaigns with sufficient conversion history.
Budget pacing is automatic. Daily budgets are soft caps -- Apple may overspend on a given day by up to 20% but will not exceed the total campaign budget.
Geographic and Demographic Targeting
Target by:
- Countries/regions -- Campaign level.
- Localities -- State, city, or DMA level (US, UK, AU only).
- Age -- Ranges from 18+.
- Gender -- Male, Female, or All.
- Device -- iPhone, iPad, or both.
Note: demographic targeting is based on Apple ID registration data, not inferred or behavioral data. Users under 18 are not shown ads.
Related Guides
- Apple Search Ads Setup -- SDK integration for attribution
- Apple Search Ads Event Tracking -- Conversion value configuration
- Troubleshooting and Debugging -- Diagnosing attribution issues
- Apple Search Ads Integrations -- MMP and analytics connections