Skip to main content

LinkedIn Ads

The linkedin_ads connector reads ad accounts, campaign groups, campaigns, creatives, and ad analytics from the LinkedIn Marketing Developer Platform via Rest.li v2 REST endpoints. It runs on StreamWright's declarative HTTP Transport (transport = "http").

Auth & HTTP Configuration​

LinkedIn Ads uses OAuth Bearer tokens, API version headers (LinkedIn-Version), Rest.li 2.0 protocol headers, dotted parameter encoding, and offset-based pagination (start/count → paging.total):

source.yaml
kind: source
name: linkedin_ads_demo

spec:
config:
account_ids: { type: list, items: string, description: "Sponsored Ad Account IDs" }
api_version: { type: string, default: "202401", description: "LinkedIn Version header (YYYYMM)" }
start_date: { type: date, required: false, description: "Start date for performance metrics" }
secrets:
linkedin_access_token: { type: string }

auth:
provider: linkedin_ads
type: bearer
token: "{{ secrets.linkedin_access_token }}"

http:
base_url: https://api.linkedin.com
headers:
LinkedIn-Version: "{{ config.api_version }}"
X-Restli-Protocol-Version: "2.0.0"
params_encoding: dotted
paginator:
type: offset
offset_param: start
limit_param: count
page_size: 100
total_path: paging.total
records:
path: elements

Access tokens are registered as secrets at connection time and automatically redacted as *** in every log message.

Reading data​

Each stream queries its corresponding Rest.li endpoint, inheriting authentication, headers, and pagination:

streams/campaigns.yaml
partitions:
- { name: account_id, values: "{{ config.account_ids }}" }
retry: { max_attempts: 3, max_delay: 2m }

requests:
- name: raw_campaigns
http:
path: /rest/adCampaigns
method: GET
params:
q: search
search.account.values[0]: "urn:li:sponsoredAccount:{{ partition.account_id }}"

transform:
mode: page
steps:
- name: campaigns
select: |
SELECT partition->>'account_id' AS account_id,
(record->>'id')::BIGINT AS campaign_id,
record->>'name' AS campaign_name,
record->>'status' AS status,
record->>'type' AS campaign_type,
record->>'costType' AS cost_type
FROM raw_campaigns

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

Analytics metrics​

The campaign_performance stream queries /rest/adAnalytics using LinkedIn's structured parameters, converted automatically via params_encoding: dotted:

streams/campaign_performance.yaml
requests:
- name: raw_campaign_performance
http:
path: /rest/adAnalytics
method: GET
params:
q: analytics
pivot: CAMPAIGN
timeGranularity: DAILY
accounts:
- "urn:li:sponsoredAccount:{{ partition.account_id }}"
dateRange:
start:
year: "{{ window.start | date('%Y') | int }}"
month: "{{ window.start | date('%m') | int }}"
day: "{{ window.start | date('%d') | int }}"
end:
year: "{{ window.end | date('%Y') | int }}"
month: "{{ window.end | date('%m') | int }}"
day: "{{ window.end | date('%d') | int }}"

Reference streams (examples)​

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

StreamDescriptionEndpoint
ad_accountsSponsored ad accounts and organization references/rest/adAccounts
campaign_groupsCampaign groups, budgets, and run schedules/rest/adCampaignGroups
campaignsIndividual campaigns, budgets, and cost types/rest/adCampaigns
creativesCreatives, creative types, and content references/rest/adCreatives
campaign_performanceDaily impressions, clicks, spend, conversions, and leads/rest/adAnalytics
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.

Shared vocabulary​

linkedin_ads maps to the same canonical nodes as Google, Microsoft, and Meta Ads, allowing unified multi-channel reporting pipelines.