Skip to main content

Apple Ads

The apple_ads connector reads campaigns, ad groups, targeting keywords, and performance reports from the Apple Ads Platform API. It runs on StreamWright's declarative HTTP Transport (transport = "http").

Auth & HTTP Configuration​

Apple Ads uses OAuth Bearer tokens and requires an X-AP-Context header scoping requests to your ad account ID. Endpoints use POST-based queries with body-embedded pagination (in: body):

source.yaml
kind: source
name: apple_ads_demo

spec:
config:
ad_account_id: { type: string, description: "Apple Ads ad account ID (sent as X-AP-Context)" }
campaign_ids: { type: list, items: string, required: false, description: "List of campaign IDs for child streams" }
secrets:
apple_ads_token: { type: string }

auth:
provider: apple_ads
type: bearer
token: "{{ secrets.apple_ads_token }}"

http:
base_url: https://api.ads.apple.com
headers:
X-AP-Context: "adAccountId={{ config.ad_account_id }}"
paginator:
type: offset
in: body
offset_param: pagination.offset
limit_param: pagination.limit
page_size: 100
total_path: pagination.totalCount
records:
path: result

The access token is treated as a secret and automatically redacted in logs.

Reading data​

Each stream queries its corresponding Apple Ads POST query endpoint, inheriting account headers and body pagination:

streams/campaigns.yaml
description: Apple Ads campaigns and budget configurations.

requests:
- name: raw_campaigns
http:
path: /v1/campaigns/query
method: POST
json:
pagination:
pageSize: 100
fetchTotalCount: true

transform:
mode: page
steps:
- name: campaigns
select: |
SELECT (record->>'adAccountId')::BIGINT AS ad_account_id,
(record->>'id')::BIGINT AS campaign_id,
record->>'name' AS campaign_name,
record->>'status' AS status,
record->>'servingStatus' AS serving_status,
(record->>'$.dailyBudgetAmount.amount')::DOUBLE AS daily_budget,
record->>'$.dailyBudgetAmount.currency' AS currency,
record->>'startTime' AS start_time,
record->>'endTime' AS end_time
FROM raw_campaigns

export:
campaigns:
step: campaigns
primary_key: [campaign_id]

Reporting requests​

Query reporting aggregates using POST reporting endpoints with custom date ranges and result.rows records path override:

streams/campaign_reports.yaml
description: Apple Ads campaign reporting analytics (impressions, taps, installs, spend).

requests:
- name: raw_campaign_reports
records:
path: result.rows
http:
path: /v1/reports/apps/campaigns/query
method: POST
json:
pagination:
pageSize: 100
fetchTotalCount: true
timeRange:
startTime: "2026-01-01"
endTime: "2026-01-07"
granularity: DAILY

transform:
mode: page
steps:
- name: campaign_reports
select: |
SELECT COALESCE(record->'metadata'->>'id', record->>'campaignId')::BIGINT AS campaign_id,
record->'metadata'->>'name' AS campaign_name,
COALESCE((record->'totalMetrics'->>'impressions')::BIGINT, (record->>'impressions')::BIGINT) AS impressions,
COALESCE((record->'totalMetrics'->>'taps')::BIGINT, (record->>'taps')::BIGINT) AS taps,
COALESCE((record->'totalMetrics'->>'totalInstalls')::BIGINT, (record->>'installs')::BIGINT) AS installs,
COALESCE((record->'totalMetrics'->'localSpend'->>'amount')::DOUBLE, (record->>'$.localSpend.amount')::DOUBLE) AS spend
FROM raw_campaign_reports

export:
campaign_reports:
step: campaign_reports
primary_key: [campaign_id]

Reference streams (examples)​

The starter template (examples/sources/ads/apple_ads) includes the following reference streams:

StreamDescriptionMethodEndpoint
campaignsApple Ads campaigns & daily budgetsPOST/v1/campaigns/query
ad_groupsAd groups and default bids across campaignsPOST/v1/adgroups/query
keywordsTargeted keywords, match types, and bid amountsPOST/v1/keywords/query
campaign_reportsPerformance metrics (impressions, taps, installs, spend)POST/v1/reports/apps/campaigns/query
Customizable Streams

These streams are reference examples. Because streams in StreamWright are declarative, you can customize columns, adjust DuckDB queries, or add new endpoints to match your upstream reporting needs.