Skip to main content

Overview

Connecting LinkedIn Ads lets the dashboard:
  • Create, update, pause, resume, and archive campaign groups (the budget container) and campaigns (targeting + delivery units).
  • Create creatives bound to a campaign and run a 2-step asset upload flow for images, videos, and documents.
  • Pull spend, impressions, clicks, conversions, and cost-in-USD analytics.
  • Run creative experiments by rotating multiple campaigns under one campaign group.
The integration uses the LinkedIn Marketing API v202402 with OAuth 2.0 authentication and refresh tokens.

Prerequisites

Before connecting, make sure you have:
  1. A LinkedIn Business account with at least one sponsored ad account.
  2. A user role on the sponsored account that allows campaign management.
  3. Marketing Developer Platform access. LinkedIn requires partner review of the connecting application before live writes are allowed; review can take 1 to 6 weeks. Until approval lands, the integration runs in dry-run mode and write operations return synthetic responses without touching the upstream API. Set the platform-side flag LINKEDIN_ADS_LIVE_MODE=true to flip writes live once approval is granted.
  4. An OAuth 2.0 application configured for the dashboard’s redirect URI. The client_id and client_secret are set on the platform side as LINKEDIN_ADS_CLIENT_ID and LINKEDIN_ADS_CLIENT_SECRET.

OAuth scopes

The connection requests the following scopes:
r_ads
r_ads_reporting
rw_ads
r_ads and r_ads_reporting cover list and analytics calls; rw_ads covers all create / update operations on campaign groups, campaigns, and creatives.

Connect

  1. Open the Paid UA → Integrations page in the dashboard.
  2. Click Connect on the LinkedIn Ads tile.
  3. Sign in with the LinkedIn user that has access to your sponsored account.
  4. Approve the requested scopes.
  5. After redirect, the dashboard exchanges the auth code for tokens and probes /adAccounts?q=search to confirm access and capture the connected sponsored accounts.
Access tokens are rotated automatically before expiry using the refresh token.

Naming map

LinkedIn’s domain model uses different terms from the dashboard’s cross-platform DTO. Internally the integration maps:
Dashboard DTOLinkedIn resourceNotes
campaigncampaignGroupThe budget container
adSetcampaignThe targeting + delivery unit
adcreativeBound to a campaign by URN
adAccountsponsoredAccountReferenced by URN: urn:li:sponsoredAccount:{id}

What happens behind the scenes

OperationAPI endpoint
List sponsored accountsGET /adAccounts?q=search
List / create campaign groupsGET / POST /adAccounts/{id}/adCampaignGroups
Update / pause / resume / archive campaign groupPOST /adAccounts/{id}/adCampaignGroups/{id} (Rest.li patch.$set)
List / create campaigns (= ad sets)GET / POST /adAccounts/{id}/adCampaigns
Update / pause / resume / archive campaignPOST /adAccounts/{id}/adCampaigns/{id} (Rest.li patch.$set)
Create / update creativePOST /creatives[/{id}]
Register asset uploadPOST /assets?action=registerUpload
Pull analyticsGET /adAnalytics?q=analytics&pivot=CAMPAIGN&...
Update operations send a Rest.li partial update envelope:
{ "patch": { "$set": { "name": "Renamed", "status": "PAUSED" } } }
Pause / resume / archive simply flip the status field (ACTIVE, PAUSED, ARCHIVED). LinkedIn does not hard-delete campaign groups or campaigns — the cancel verb is ARCHIVED. Every REST request carries the required versioning headers:
LinkedIn-Version: 202402
X-Restli-Protocol-Version: 2.0.0
Every write request additionally carries an Idempotency-Key HTTP header so retries do not produce duplicate resources, even if the connection drops mid-request.

Creatives & asset uploads

LinkedIn’s asset upload is a two-step flow:
  1. The dashboard calls POST /assets?action=registerUpload with the recipe URN (urn:li:digitalmediaRecipe:feedshare-image or urn:li:digitalmediaRecipe:feedshare-video) and the owner URN of the entity that will own the asset (typically the connected organization).
  2. The dashboard receives an uploadUrl plus an asset URN, and the bytes of the image / video are PUT directly to the returned URL.
Once the bytes land, the asset URN is referenced from a creative via POST /creatives with the asset URN bound into the creative payload.

Budget conversion

LinkedIn budgets are stored as { amount: "<whole-currency>", currencyCode: "USD" }. The dashboard sends USD amounts in its DTOs and serialises them into the LinkedIn currency-amount shape on the request. dailyBudget and totalBudget map to the DTO’s daily and lifetime budget types respectively.

Rate limiting

Requests are throttled per (client_id, ad_account_id) using a sliding window built on Redis. LinkedIn’s limits are partner-specific — 100 requests per day for unreviewed apps and up to several thousand per second for approved partners. If the platform reports HTTP 429, the request is automatically retried with exponential backoff. If Redis is unavailable, the rate limiter fails closed and the request is reported as a quota error to the autonomous-growth runner — preventing accidental over-quota fan-out under outage.

Reporting

Analytics are pulled hourly (or on demand) from /adAnalytics?q=analytics. The dashboard requests the CAMPAIGN_GROUP (or CAMPAIGN) pivot with DAILY granularity by default. Default fields include externalWebsiteConversions, impressions, clicks, and costInUsd. Spend is reported in USD natively for USD-denominated accounts; non-USD ad accounts have local-currency spend converted to USD on ingestion.

Experiments (multi-campaign rotation)

LinkedIn does not expose a native split-test API. The dashboard maps experiment variants onto multiple campaigns under a single campaign group, with each variant carrying its own targeting and creative. The control variant ships active; treatments ship paused until the experiment runner toggles them. Per-variant performance is read back from the analytics endpoint scoped to the parent campaign group.

Disconnect

Click Disconnect on the integration tile. The OAuth refresh token is removed from the dashboard. To fully revoke access, also remove the third-party app authorisation from your LinkedIn account’s connected-apps list.

Troubleshooting

  • “LinkedIn Ads /adAccounts returned 401” — the OAuth token has been revoked or has expired without a valid refresh token. Re-connect the integration.
  • “LinkedIn Ads create_campaign_group: 429 — …” — your app’s rate budget was breached. The dashboard backs off automatically; if the error persists, request a higher Marketing Developer Platform throughput tier.
  • Write returns dryRun: true — the integration is running in dry-run mode (default until Marketing Developer Platform review lands). Set LINKEDIN_ADS_LIVE_MODE=true on the platform side to flip writes live.
  • “webhooks not supported on linkedin_ads” — the Marketing API does not expose webhooks. State changes (campaign disapproved, daily cap hit) are detected via the hourly metrics-sync job.
  • registerUpload returns 400 with owner empty — the upload owner URN was not derivable from the supplied DTO. Pass the organisation URN (e.g. urn:li:organization:1234) in the DTO’s asset_id field, or call the register_upload adapter operation directly with the full body.
  • Update returns 405 Method Not Allowed — LinkedIn’s update verb is POST with a patch.$set envelope, not PATCH or PUT. The integration handles this automatically; if you see this error, please report it.