URL: /code/integrations/linkedin-ads · Id: `page:/code/integrations/linkedin-ads`

# LinkedIn Ads

> Your agents build LinkedIn campaigns on your ad account for the job titles and companies you approved, and qualify each lead form as it arrives.

Your agents build LinkedIn campaigns on your ad account for the job titles and companies you approved, and qualify each lead form as it arrives.

```text title="Run it · Ask"
Build a $100 LinkedIn test with my 3 ads, aimed at owners and property managers at the companies on my approved list.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Campaign.create({ venue: 'linkedin', objective: 'leads', audience: 'audiences/approved-property-managers', titles: ['Owner', 'Property Manager'], budget: { amount: 100, currency: 'USD', per: 'lifetime' }, ads: ['ads/linkedin-ledger', 'ads/linkedin-renewals', 'ads/linkedin-owner-statements'] })
```

```json title="Run it · Response"
{
  "$type": "Campaign",
  "mode": "try",
  "stored": false,
  "venue": "linkedin",
  "objective": "lead generation",
  "audience": {
    "matchedMembers": 2140,
    "minimum": 300
  },
  "budget": {
    "amount": 100,
    "currency": "USD",
    "per": "lifetime",
    "pacedAt": "about $14 a day for 7 days"
  },
  "checks": {
    "dailyMinimum": "passes $10",
    "lifetimeMinimum": "meets $100"
  },
  "ads": 3,
  "expect": {
    "clicks": "7 to 9",
    "readsAt": "$100"
  },
  "runsBy": "account"
}
```

- **Connect:** Sign in once, 5 grants
- **Runs through:** [Your account](/code/integrations#three-ways)
- **First run:** Read the ads a rival runs on LinkedIn, about 0.16¢
- **Sites:** [LinkedIn](/venues#linkedin)

## Connect once
1. Call `Integration.connect` with `system: 'ads'` and `venue: 'linkedin'`, then open the `connectUrl` it returns and sign in as yourself with LinkedIn.
2. Pick the ad account and the company page the ads run as; your role on the ad account must allow creating campaigns.
3. Set the spend on your standing approval with `Mandate.authorize`: the most your agents may spend a day and in all; LinkedIn bills your ad account, never the platform.

| Grant | What it unlocks | Scope |
| --- | --- | --- |
| Build and change ads | Campaign groups, campaigns, creatives, budgets and status | `rw_ads` |
| Read results | Spend, impressions, clicks and leads by campaign, creative and day | `r_ads_reporting` |
| Matched audiences | Uploading a company or contact list as a matched audience | `rw_dmp_segments` |
| Lead form responses | Each lead form response the moment it is submitted | `r_marketing_leadgen_automation` |
| Post as the page | The post a creative references, published as your company page | `w_organization_social` |

| To | Do this |
| --- | --- |
| Pause | `Integration.pause` stops every call through LinkedIn at once and keeps the token; `Integration.resume` picks up from the same cursor. |
| Reconnect | `Integration.reconnect` returns a fresh `connectUrl` that lasts one hour, and keeps the Integration's id and cursor, so nothing imports twice. |
| Disconnect | `Integration.revoke` revokes the token with LinkedIn; every record it brought in stays in your records. |

## What your agents do
| Ask | Verb | Runs through | Price |
| --- | --- | --- | --- |
| Upload my approved company list as a matched audience. | [`Audience.create`](/code/apps/ads/create-audience) | Your account | Free |
| Build a $100 test with 3 ads to owners and property managers at those companies. | [`Campaign.create`](/code/apps/ads/create-campaign) | Your account | Free |
| Add a document ad with the rent-ledger template. | [`Ad.create`](/code/apps/ads/create-ad) | Your account | Free |
| Pause the two weaker ads and keep the best one running. | [`Campaign.update`](/code/apps/ads/update-campaign) | Your account | Free |
| Show me spend, impressions, clicks and form fills by ad. | [`Metric.query`](/code/apps/analytics-and-experiments/query) | Your account | Free |
| Qualify each lead form response the morning it arrives. | [`Lead.qualify`](/code/apps/crm/qualify) | Your account | Free |

Calls on your own account are free; LinkedIn bills your spend directly.

Reading this site’s public data needs no account: [`Ad.find`](/code/data/ads/find) is a Data call (the [three ways a call runs](/code/integrations#three-ways)).

## What it holds
| Noun | The account’s object | When | Which side leads |
| --- | --- | --- | --- |
| [Campaign](https://startupbuilder.dev/business/nouns/campaign) | `adCampaignGroup and adCampaign` | Your agent builds it | Your records |
| [Ad](https://startupbuilder.dev/business/nouns/ad) | `creative` | Your agent builds it; LinkedIn’s review sets its status | Your records |
| [Audience](https://startupbuilder.dev/business/nouns/audience) | `dmpSegment` | Your agent uploads your list | Your records |
| [Lead](https://startupbuilder.dev/business/nouns/lead) | `leadFormResponse` | Someone submits the form | The account |
| [Entry](https://startupbuilder.dev/business/nouns/entry) | `adAnalytics: costInLocalCurrency` | Each day’s spend settles | The account |

## Events and webhooks
| Direction | Event |
| --- | --- |
| Emits | `Campaign.created`, `Campaign.updated`, `Ad.created`, `Audience.created`, `Lead.created`, `Lead.qualified`, `Entry.posted` |

Any Event can wake a Workflow, and a Watch delivers it to your endpoint, signed ([Events and webhooks](/code/events)).

## Limits
| What | Limit | Call |
| --- | --- | --- |
| Budget minimums | $10 a day for any ad format, and a new campaign’s lifetime budget is at least $100 | [`Campaign.create`](/code/apps/ads/create-campaign) |
| Matched audiences | Run from 300 matched members; under that the campaign refuses with `venue-rejected` | [`Audience.create`](/code/apps/ads/create-audience) |
| Ad review | LinkedIn reviews every ad; a rejection comes back as `venue-rejected` with the reason | [`Ad.create`](/code/apps/ads/create-ad) |
| Results | Read through the reporting API each morning; a day’s numbers settle overnight, so a stop rule fires on yesterday’s | [`Metric.query`](/code/apps/analytics-and-experiments/query) |
| Spend | Your standing approval holds the daily and total cap; a change past it waits with `over-mandate` | [`Campaign.update`](/code/apps/ads/update-campaign) |

## Start here
| Move | The rule |
| --- | --- |
| Structure | One campaign group, one campaign on the lead generation objective with LinkedIn’s own lead form, aimed at the companies on your approved list (at least 300 matched members), with 3 single-image ads in rotation. |
| Budget | $100 lifetime, LinkedIn’s minimum for a new campaign, paced at $10 to $15 a day over about a week. This first test prices your audience and finds the ad that gets clicked; it does not prove the channel. |
| Creative | 3 single-image ads at 1200 × 628, each with one claim from your evidence; intro text under 150 characters and a headline under 70. Make one a document ad if you have a real asset. |
| Kill rule | At $100, keep the ad with the best CTR only if it is above 0.4%; under 0.4% on all three, change the audience before the words. Run the next test at $50 a day for 2 weeks only with an ad above 0.6% (our planning number). |

| Benchmark | Source |
| --- | --- |
| $10 a day minimum; a new campaign needs at least $100 lifetime | LinkedIn Marketing Solutions |
| $11 to $14 a click for B2B SaaS prospecting; CTR 0.4% to 0.8% | Agency ad datasets, 2025 |
| LinkedIn’s lead forms convert 6% to 13% of clicks, against 2% to 5% for landing pages | Agency ad datasets, 2025 |
| $100 at $11 to $14 a click buys 7 to 9 clicks: enough to price the next test, too few to crown a winner | Our calculation |
| Matched audiences run from 300 members; 50,000 or more is recommended | LinkedIn help |

## Field map
| Our field | LinkedIn Ads’s field | Notes | Calls |
| --- | --- | --- | --- |
| `Campaign.objective` | `adCampaign.objectiveType` | `leads` is LEAD_GENERATION with LinkedIn’s lead form, `traffic` WEBSITE_VISIT, `conversions` WEBSITE_CONVERSION | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign), [`Ad.create`](/code/apps/ads/create-ad) |
| `Campaign.budget` | `adCampaign.dailyBudget or adCampaign.totalBudget` | `{ amount, currencyCode }`, the amount as a decimal string; a lifetime budget runs on lifetime pacing, which LinkedIn recommends | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign), [`Ad.create`](/code/apps/ads/create-ad) |
| `Campaign.startsAt` | `adCampaign.runSchedule.start` | Epoch milliseconds | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.endsAt` | `adCampaign.runSchedule.end` | None. | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.state` | `adCampaign.status` | `draft` is DRAFT, `live` ACTIVE, `paused` PAUSED | [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.targeting` | `adCampaign.targetingCriteria` | Job titles, industries, staff count ranges and locations, each a LinkedIn targeting facet | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.audiences` | `targetingCriteria: audienceMatchingSegments` | None. | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.bid` | `adCampaign.costType and adCampaign.unitCost` | `lowest-cost` lets LinkedIn bid to spend the budget; a `costPer` cap sets `unitCost` | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Ad.body` | `post commentary` | The creative references a post; 3,000 characters, 150 show before it truncates | [`Ad.create`](/code/apps/ads/create-ad) |
| `Ad.headline` | `post content.article.title` | 200 characters; 70 show before it truncates | [`Ad.create`](/code/apps/ads/create-ad) |
| `Ad.landingUrl` | `post content.article.source, or the lead form` | None. | [`Ad.create`](/code/apps/ads/create-ad) |
| `Audience.query` | `dmpSegment list upload` | A company list by name and domain, or contacts with each email hashed before it leaves your records | [`Audience.create`](/code/apps/ads/create-audience) |
| `Lead.interest` | `leadFormResponse answers` | None. | [`Lead.qualify`](/code/apps/crm/qualify) |

## What try does here
Reads your ad account, the matched count of each audience and the targeting facets, and checks the campaign against LinkedIn’s budget minimums; LinkedIn has no validate-only call, so nothing is sent to it and nothing is created.

## How long a connection lasts
A connection lasts 60 days per access token, renewed from a refresh token that LinkedIn ends 365 days after your first sign-in, however often it is used.

The platform asks you to reconnect 7 days before it runs out; until you do, a call that needs it refuses with [`needs-reauth`](/code/refusals#needs-reauth).

## Formats
| Site | Format | Size | Limits |
| --- | --- | --- | --- |
| [LinkedIn](/venues#linkedin) | Feed post | None. | 3,000 characters, with one image, a document, a video or several images |
| [LinkedIn](/venues#linkedin) | Single image ad | 1200 × 628, 1200 × 1200 or 720 × 900 | Intro text 3,000 characters, 150 before it truncates; headline 200, 70 before it truncates; JPG, PNG or GIF up to 5 megabytes |
| [LinkedIn](/venues#linkedin) | Document ad | None. | PDF, DOC, DOCX, PPT or PPTX up to 100 megabytes and 300 pages; LinkedIn advises under 10 pages |

## Plays and strategies
| Kind | Where |
| --- | --- |
| Plays | [Paid social](/plays/paid-social), [Buying committee](/plays/buying-committee), [Account ads](/plays/account-ads) |
| Strategies | [Paid](/strategy/paid) |

## Used by
- **Workflows:** [Ad test setup](/capabilities/run-ads/ad-test-setup) · [Post-engager harvest](/capabilities/find-customers/post-engagers)
- **Agent tasks:** [Launch day](/capabilities/earn-first-links/launch-day) · [Message test](/capabilities/run-ads/message-test)

## Reference

| Call | Path | Price |
| --- | --- | --- |
| [`Audience.create`](/code/apps/ads/create-audience) | `POST /audiences/create` | Free |
| [`Campaign.create`](/code/apps/ads/create-campaign) | `POST /campaigns/create` | Free |
| [`Ad.create`](/code/apps/ads/create-ad) | `POST /ads/create` | Free |
| [`Campaign.update`](/code/apps/ads/update-campaign) | `POST /campaigns/update` | Free |
| [`Metric.query`](/code/apps/analytics-and-experiments/query) | `POST /metrics/query` | Free |
| [`Lead.qualify`](/code/apps/crm/qualify) | `POST /leads/qualify` | Free |