LinkedIn Ads
Run LinkedIn campaigns, B2B audiences, forecasts, conversions and lead gen forms.
LinkedIn is a second ad network behind the same /v1/ads endpoints as Meta. Everything on that page works here: the campaign tree, creatives, audiences, insights, lead forms. This page covers what is different, plus the endpoints only a business-to-business network answers — forecasts, conversion rules and the public ad library.
LinkedIn's Advertising API is a restricted product. Until it is approved on the deployment you are calling, GET /v1/ads/providers reports LinkedIn with configured: false and connecting answers 503.
What a connection can do
GET /v1/ads/providersEach network reports what it supports, so you never have to hard-code it:
{
"id": "linkedin",
"name": "LinkedIn Ads",
"configured": true,
"connectMethods": ["ads"],
"capabilities": {
"campaigns": true,
"boost": true,
"creatives": true,
"audiences": true,
"targetingSearch": true,
"insights": true,
"leadForms": true,
"pixels": true,
"externalAds": true,
"conversions": true,
"forecasts": true,
"adLibrary": true,
"catalogs": false
},
"targetingFacets": ["country", "industry", "job_title", "seniority"],
"trackingMacros": [
{ "token": "{{LINKEDIN_CAMPAIGN_ID}}", "description": "The campaign the click came from" }
]
}An endpoint whose capability is false answers 400 with error: "unsupported" rather than failing at the network.
Connect
POST /v1/ads/connections/linkedin/authorizeSame body as any network: workspaceId, an optional dashboard returnTo, and an optional method — LinkedIn offers one, ads. The user who calls it finishes the login in their own browser, because the callback checks that the same user came back.
Ids are URNs
Meta addresses objects with numbers and act_…; LinkedIn uses URNs, and FoPost passes them through unchanged:
| What | Shape |
|---|---|
| Ad account | urn:li:sponsoredAccount:123 |
| Organization (the Page equivalent) | urn:li:organization:123 |
| Campaign | urn:li:sponsoredCampaignGroup:123 |
| Ad set | urn:li:sponsoredCampaign:123 |
| Ad | urn:li:sponsoredCreative:123 |
| Creative | urn:li:share:123 |
| Audience | urn:li:adSegment:123 |
| Lead form | urn:li:leadGenForm:123 |
| Conversion rule | urn:li:conversion:123 |
LinkedIn's own tree is campaign group → campaign → creative. FoPost maps that onto the campaign → ad set → ad it uses everywhere else, so /v1/ads/campaigns, /v1/ads/ad-sets, /v1/ads/ads, /duplicate, /v1/ads/status and /v1/ads/accounts/{id}/tree behave exactly as they do on Meta. Read an id from a listing and pass it back as-is.
Creatives
format is image or video. A LinkedIn creative is the post the ad points at, so creating one publishes a post that is visible only as an ad and never appears in the organization's feed; POST /v1/ads/ads then puts it in an ad set. Carousels are not supported and answer 400 with error: "unsupported".
Boosting works the same way as on Meta, for a LinkedIn post FoPost already published.
Tracking parameters
urlTags is appended to the creative's destination, and LinkedIn expands its own macros at delivery:
| Macro | Value |
|---|---|
{{LINKEDIN_CAMPAIGN_ID}} | The campaign the click came from |
{{LINKEDIN_CAMPAIGN_NAME}} | The campaign name |
{{LINKEDIN_CREATIVE_ID}} | The creative the click came from |
{{LINKEDIN_ACCOUNT_ID}} | The ad account |
{{LINKEDIN_ACCOUNT_NAME}} | The ad account name |
trackingMacros on GET /v1/ads/providers is the list for whichever network you are on, so a UTM builder can read it rather than ship a copy.
Targeting
targeting keeps the same shape. countries, ageMin, ageMax, gender, locations and audienceIds mean what they do on Meta; LinkedIn buckets ages, so a range is widened to the buckets it overlaps. What is new is facets, a map keyed by the same type you searched with:
{
"countries": ["US"],
"ageMin": 25,
"ageMax": 54,
"gender": "all",
"facets": {
"job_title": [{ "id": "urn:li:title:100", "name": "Head of Marketing" }],
"company_size": [{ "id": "urn:li:staffCountRange:(201,500)", "name": "201-500" }],
"industry": [{ "id": "urn:li:industry:96", "name": "Software Development" }]
}
}GET /v1/ads/targeting/search?connection_id=&type=&q=On LinkedIn type is one of country, region, city, interest, company, company_size, company_category, industry, job_title, job_function, seniority, years_of_experience, skill, degree, field_of_study or member_group. Ask GET /v1/ads/providers for targetingFacets rather than hard-coding the list; a type the network does not serve answers 400 with error: "unsupported". Each result has an id and name you pass back as-is.
Audiences
CUSTOM (a customer list, emails hashed before they leave) and WEBSITE (visitors your Insight Tag saw, so pixelId is the tag id) work as they do on Meta. LOOKALIKE is not supported. Two more are:
| Subtype | Fields | What it is |
|---|---|---|
COMPANY_LIST | companies | A list of companies rather than people |
ENGAGEMENT | source, sourceId, retentionDays | People who engaged with a page, ad, lead_form or video |
Each company row needs at least one of name, domain, pageUrl or ticker, and may carry country:
curl -X POST https://api.fopost.com/v1/ads/audiences \
-H "X-API-Key: $FOPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"workspaceId": "7d2b8c11-4e5a-4a8f-b0d9-3c5e6f7a8b90",
"connectionId": "c4d5e6f7-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
"adAccountId": "urn:li:sponsoredAccount:512334455",
"name": "Target accounts Q4",
"spec": {
"subtype": "COMPANY_LIST",
"companies": [
{ "name": "Northwind Traders", "domain": "northwind.example" },
{ "pageUrl": "https://www.linkedin.com/company/contoso" }
]
}
}'POST /v1/ads/audiences/{id}/companies?workspace_id=&connection_id=adds more rows to a company list after it exists, in the same shape. The answer is { "added": n }. The rows travel with the request and are never stored.
Forecasts
Two reads that say what an audience costs before you commit to it. Both take workspaceId, connectionId, adAccountId, goal and a targeting block, and both are open to any network whose capabilities.forecasts is true.
POST /v1/ads/linkedin/bid-pricingadds an optional bidType of CPC, CPM or CPV and answers what the auction currently costs, in minor units of the ad account currency:
{
"data": {
"currency": "USD",
"suggestedBidMinor": 1450,
"minBidMinor": 800,
"maxBidMinor": 4200,
"dailyBudgetFloorMinor": 1000
}
}POST /v1/ads/linkedin/supply-forecastadds an optional budgetMinor and answers what that budget would deliver: impressions, clicks, spendMinor and the windowDays the numbers cover. ready is false while the network has no answer for that audience.
POST /v1/ads/reach-estimate still answers how many people the targeting reaches, on every network.
Conversions
A conversion rule is how the network attributes a sale or a sign-up back to an ad set.
GET /v1/ads/linkedin/conversion-rules?workspace_id=&connection_id=&ad_account_id=
POST /v1/ads/linkedin/conversion-rules
GET /v1/ads/linkedin/conversion-rules/{id}?workspace_id=&connection_id=
PATCH /v1/ads/linkedin/conversion-rules/{id}?workspace_id=&connection_id=
DELETE /v1/ads/linkedin/conversion-rules/{id}?workspace_id=&connection_id=Create one with workspaceId, connectionId, adAccountId, a name, a type of purchase, lead, sign_up, add_to_cart, download, install, key_page_view or other, an attribution of last_touch or each_campaign, and the attribution windows postClickWindowDays (default 30) and viewThroughWindowDays (default 7). valueMinor and currency set what one conversion is worth. DELETE turns the rule off rather than removing it, because the network keeps the history; it answers 204.
Attach it to an ad set
POST /v1/ads/linkedin/conversion-rules/{id}/associations?workspace_id=&connection_id=
DELETE /v1/ads/linkedin/conversion-rules/{id}/associations?workspace_id=&connection_id=Body is { "campaignId": "urn:li:sponsoredCampaign:123" } — an ad set on the same connection. Both answer the rule with its updated campaignIds.
Read what it recorded
GET /v1/ads/linkedin/conversion-rules/{id}/metrics?workspace_id=&connection_id=&since=&until=since and until are YYYY-MM-DD, inclusive. The answer is conversions, postClickConversions, viewThroughConversions, valueMinor and costPerConversionMinor.
Send conversions back
POST /v1/ads/linkedin/conversion-rules/{id}/events?workspace_id=&connection_id=For conversions that happen off the website, where no tag fires. Up to 100 events per call, each needing an email or a clickId:
curl -X POST "https://api.fopost.com/v1/ads/linkedin/conversion-rules/urn:li:conversion:987/events?workspace_id=$WS&connection_id=$CONN" \
-H "X-API-Key: $FOPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"happenedAt": 1758326400000,
"valueMinor": 249900,
"currency": "USD",
"eventId": "order-10482",
"email": "[email protected]"
}
]
}'happenedAt is epoch milliseconds. eventId is your own id, so sending the same event twice is counted once. The address is hashed inside the API: the network never receives it, and FoPost stores nothing about an event. The answer is { "accepted": n }.
Ad library
GET /v1/ads/library?connection_id=&countries=&q=&page_ids=&limit=&after=The same endpoint Meta serves: it searches the ads the network publishes for everyone, not the ads on your account. countries is required, a comma-separated list of two-letter codes. q searches the ad copy and the advertiser name, and page_ids narrows to named advertisers.
Each entry carries the advertiser, the creative bodies and titles, the dates it ran and a snapshotUrl into the library. LinkedIn's library discloses an impression band rather than spend, so the spend and impression range fields come back null. Pass nextCursor back as after for the next page. Available wherever capabilities.adLibrary is true.
Lead forms
Lead gen forms belong to an organization, so pageId is a urn:li:organization:…. Creating, reading, listing leads and archiving all work as on Meta.
The leads feed (POST /v1/ads/lead-pages) is webhook-driven and LinkedIn has no per-organization subscription, so subscribing a LinkedIn organization answers 400 with error: "unsupported". Read leads with GET /v1/ads/lead-forms/{formId}/leads instead.
Errors
A 400 with error: "unsupported" means the network cannot do what was asked — a carousel creative, a lookalike audience, a targeting type it does not serve. A 422 means the network refused the request and message carries its reason. A 503 means the Advertising API product is not approved on this deployment yet.
Next
Related documentation
- API Overview
Base URL, envelopes, pagination, and errors for the FoPost REST API.
- Authentication
API keys, scopes, and workspace binding.
- Publishing
Create a post, target accounts, publish it, and read the per-account result.
- Scheduling
Schedule a post, repeat it, and import a batch from a spreadsheet.
- Media
Upload files, list the media library, and attach media to a post.
- Validation
Check content, text length, and media against platform rules before a post exists.
- Accounts
List connected social accounts, check their health, and refresh credentials.
- Workspaces
Workspaces, labels, and how isolation works across them.