> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt # Linking Apple Ads as a source Canonical URL: https://posthog.com/docs/data-pipelines/sources/apple-search-ads > **Cloud region:** This page uses US Cloud hosts. For an EU Cloud project, use `https://eu.posthog.com` instead of `https://us.posthog.com` or `https://app.posthog.com` (app host for links). ## On this page - [Prerequisites](#prerequisites) - [Adding a data source](#adding-a-data-source) - [Sync modes](#sync-modes) - [Configuration](#configuration) - [Supported tables](#supported-tables) - [Troubleshooting](#troubleshooting) ![](https://res.cloudinary.com/dmukukwp6/image/upload/texture_tan_9608fcca70) ![](https://res.cloudinary.com/dmukukwp6/image/upload/texture_tan_dark_a92b0e022d) Let AI connect your sources for you Skip the manual setup — run this in your project and the wizard auto-detects your databases and APIs and connects them to PostHog. `npx @posthog/wizard warehouse` ![PostHog Wizard hedgehog](https://res.cloudinary.com/dmukukwp6/image/upload/wizard_3f8bb7a240.png) ![](https://res.cloudinary.com/dmukukwp6/image/upload/wizard_3f8bb7a240.png)Let AI connect your sources for you The Apple Ads connector syncs your campaigns, ad groups, keywords, and daily performance reporting into PostHog, so you can analyze ad spend next to your product data. Apple Ads was called Apple Search Ads until Apple renamed it, and it now covers ads on both the App Store and Apple Maps. PostHog reads version 1.0 of the [Apple Ads Platform API](https://developer.apple.com/documentation/apple-ads-platform-api). There are two ways to connect: - **Sign in with Apple** (default) – click a button, authorize PostHog in Apple's UI, and you're connected. The ad account list fills automatically. - **API key pair** – generate your own EC P-256 key pair and register it with Apple. Use this if your source is pinned to Campaign Management API version 5, which the OAuth flow does not support. Both methods require an Apple Ads user with an API role. An account admin grants one in [Apple Ads](https://ads.apple.com) under **Account Settings** > **User Management**. ## Prerequisites ### Sign in with Apple The only prerequisite is an Apple Ads user with an API role: 1. An account administrator signs in to [Apple Ads](https://ads.apple.com), goes to **Account Settings** > **User Management**, and gives the connecting user either **API Account Read Only** or **API Account Manager**. 2. In PostHog, choose **Sign in with Apple** as the authentication type and click the button. Authorize PostHog in Apple's UI. 3. Apple sends you back to PostHog. Pick your ad account from the list that appears. ### API key pair Use this method if your source is pinned to Campaign Management API version 5, or if you prefer to run your own API client. #### 1\. Create an API user An Apple Ads account administrator signs in to [Apple Ads](https://ads.apple.com), goes to **Account Settings** > **User Management**, and invites or edits a user with one of these roles: - **API Account Read Only** – read access to the account. This is all PostHog needs. - **API Account Manager** – read and write access. This works, but it grants more than PostHog uses. Apple revokes API access if the user later moves to a non-API role, which makes syncs start to fail. Use a user whose role you don't expect to change. #### 2\. Generate a key pair The API user generates an EC P-256 key pair. OpenSSL is already installed on macOS and Linux: ```bash openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem openssl ec -in private-key.pem -pubout -out public-key.pem ``` Keep `private-key.pem` secret. If it leaks, generate a new pair and upload the new public key to Apple. #### 3\. Upload the public key The API user signs in to Apple Ads, goes to **Account Settings** > **API**, pastes the contents of `public-key.pem` into the **Public Key** field, and clicks **Save**. Apple then shows three identifiers above that field. PostHog needs all three: ``` clientId SEARCHADS.aeb3ef5f-0c5a-4f2a-99c8-fca83f25a9 teamId SEARCHADS.hgw3ef3p-0w7a-8a2n-77c8-scv83f25a7 keyId a273d0d3-4d9e-458c-a173-0db8619ca7d7 ``` #### 4\. Find your ad account ID The Platform API scopes every request to one ad account, so PostHog needs your ad account ID. This is **not** your organization ID, because one organization can hold several ad accounts. The quickest path is to let PostHog look it up. In the connect form, leave **Ad account ID** blank and connect. PostHog stops and shows the ad account IDs your credentials can read. Enter one of the IDs in **Ad account ID** and connect again. If you would rather look it up yourself, call the [Get User ACL](https://developer.apple.com/documentation/apple-ads-platform-api/get-user-acls) endpoint: ```bash curl -H "Authorization: Bearer $ACCESS_TOKEN" https://api.ads.apple.com/v1/acls ``` Each entry in the response holds an `adAccount.id`. That value is your ad account ID. ## Adding a data source 1. In PostHog, go to the [Sources tab](https://app.posthog.com/data-management/sources) of the data pipeline section. 2. Click **\+ New source** and click **Link** next to this source. 3. Enter your credentials (see [Configuration](#configuration) below) and click **Next**. 4. Select the tables you want to sync, choose a sync method and frequency, then click **Import**. Once the syncs are complete, you can start querying this data in PostHog. ## Sync modes Each table can be synced in one of several modes, depending on what the source supports: - **Webhook** (when available) – the source pushes changes to PostHog in real time. Fastest freshness, lowest ongoing cost, and the only mode that reliably captures updates and deletes. - **Incremental** – only new or updated rows are synced on each run, using a cursor field (such as an `updated_at` timestamp). Cheaper than a full refresh, but deletes aren't captured. - **Append only** – new rows are appended using a cursor field; existing rows are never updated. Ideal for immutable, append-only tables like event logs. - **Full refresh** – the whole table is reloaded on every sync. Use it when a table has no reliable cursor or when you need deletions reflected. See [sync methods](https://posthog.com/docs/data-pipelines/sources.md#sync-methods) for a full explanation of how each mode works and how to choose between them. The `campaigns`, `ad_groups`, `keywords`, and `acls` tables use full refresh, because Apple's query endpoints expose no updated-since cursor. The three reporting tables sync incrementally by `date`. Each run re-reads a trailing window of recent days, because Apple restates recent reporting as attribution settles. Rows are merged away by primary key, so restatements replace earlier values instead of duplicating them. Apple serves daily reporting for the **last 90 days only**. PostHog starts a couple of days inside that boundary, because Apple applies it in the ad account's own reporting time zone. If you set a report start date older than the window, PostHog starts from the oldest day Apple still serves rather than failing the sync. To build a longer history, connect the source and let it sync regularly – PostHog keeps the rows it has already imported after they age out of Apple's window. ## Configuration | Option | Description | | --- | --- | | `Authentication type` **Type:** select **Required:** True | | | `Ad account ID` **Type:** credential-account-select **Required:** False | Connect your account above, or fill in the credentials, and this lists the ad accounts they can read. You can also type one in: it is `adAccount.id` from Apple's Get User ACL endpoint, `GET https://api.ads.apple.com/v1/acls`, and it is not the same as your organization ID. | | `Report start date` **Type:** text **Required:** False | Earliest day to pull reporting for. Apple serves daily reporting for the last 90 days, so anything older is read from that day instead. | | `Organization ID` **Type:** text **Required:** False | Only for sources still on Apple's Campaign Management API 5, which Apple stops serving on 26 January 2027. Those use the API key pair option. Leave this empty and pick an ad account instead. | ## Supported tables | Table | Description | Sync method | Incremental field | Primary key | | --- | --- | --- | --- | --- | | `acls` | Ad accounts the API credentials can read, with the organization and role names. | Full refresh | — | — | | `campaigns` | Campaigns in the account, with budget, serving status and countries or regions. | Full refresh | — | — | | `ad_groups` | Ad groups across every campaign in the account, with default bid and targeting. | Full refresh | — | — | | `keywords` | Targeting keywords across every ad group in the account, with match type and bid. | Full refresh | — | — | | `campaign_report` | Daily campaign performance: impressions, taps, installs, spend and derived rates. | Incremental, Full refresh | date | — | | `ad_group_report` | Daily ad group performance for every campaign in the account. | Incremental, Full refresh | date | — | | `keyword_report` | Daily keyword performance for every campaign in the account. | Incremental, Full refresh | date | — | ## Troubleshooting - **"Signing in with Apple only works with the Apple Ads Platform API"** means the source is pinned to Campaign Management API version 5, which the OAuth flow does not support. Switch the authentication type to **API key pair**, or move the source off Campaign Management API 5. - **401 errors** mean Apple rejected the access token. For key pair sources, check that the API client still exists under **Account Settings** > **API**, and that the private key in PostHog matches the public key uploaded there. For OAuth sources, try reconnecting through **Sign in with Apple**. - **403 errors** mean the API user can reach Apple but not this ad account. Check the user's role and the ad account ID. - **404 errors** mean Apple can't find the ad account. Re-read the ID from `adAccount.id` rather than using your organization ID. - **"The private key isn't a valid unencrypted EC (P-256) PEM"** means the key was pasted in the wrong format. Paste the whole contents of `private-key.pem`, including the `-----BEGIN EC PRIVATE KEY-----` and `-----END EC PRIVATE KEY-----` lines, and make sure the key isn't passphrase-protected. - **Reporting tables are empty for older dates** because of the 90-day daily reporting window described above. This is an Apple limit, not a sync failure. If your sync is failing or data looks wrong, see the [Data warehouse troubleshooting guide](https://posthog.com/docs/data-warehouse/troubleshooting.md). If that doesn't help, [contact support](https://us.posthog.com/#panel=support%3Asupport%3Adata_warehouse%3A%3Atrue) – we're happy to help.