Lesson 04 · outer fringe
Your customer's own data, turned into something targetable. Two entities, one pipeline, a state machine that expires behind your back — and a separate API program you have to apply for.
≈15 min · pinned to li-lms-2026-08
Every facet you met in Lesson 03 targets attributes LinkedIn knows: job title, employer, seniority. Matched Audiences is the mechanism for targeting attributes your customer knows: their CRM contacts, their target account list, the people who visited their pricing page.
The mechanism is identity resolution. You hand LinkedIn a list of email addresses or company names; LinkedIn matches them against member profiles and company pages; you get back an opaque audience you can target but never read. You cannot ask "who matched" — that is the privacy boundary, and it is absolute.
Read this before you plan the work
Matched Audiences is a separate API program from the Advertising API, with a separate application and separate vetting. Access to the Advertising API grants you nothing here. Advertising API access is a prerequisite for even applying. The scope is rw_dmp_segments, and LinkedIn describes access as restricted to approved developers "subject to applicable data restrictions in their agreements."
Practical consequence for your roadmap: this is a business-development dependency with an unknown lead time, not an engineering task you can schedule. Start the application before you need the feature.
This is the core of the vocabulary, and it is the thing you cannot guess.
| Entity | URN | Role | You… |
|---|---|---|---|
| DMP Segment | urn:li:dmpSegment | The staging entity. Holds the raw input you push — users or companies. | create it, write to it, delete it |
| Ad Segment | urn:li:adSegment | The output. The matched, targetable audience. | never create it — you only read its URN and target it |
DMP is Data Management Platform. The asymmetry is the whole design: you own the input side, LinkedIn owns the output side. The Ad Segment is created for you when matching completes, and it arrives in an unexpected place — nested inside the DMP Segment's destinations array, under the key destinationSegmentId:
GET /rest/dmpSegments/11204
{
"id": 11204,
"name": "Test DMP Segment 2",
"account": "urn:li:sponsoredAccount:516848833",
"type": "USER",
"sourcePlatform": "PARTNER_API",
"destinations": [{
"status": "BUILDING",
"destinationSegmentId": "urn:li:adSegment:848336" // ← the targetable thing
}]
}
Note what this implies: the status you care about is not on the segment, it is on the destination. A "destination" is an ad exchange — and today the only accepted value is LINKEDIN. So the array always has one element and the nesting buys you nothing. It exists because the platform was designed to fan out to multiple exchanges. Write your accessor to handle the general case anyway; a hardcoded destinations[0] is the kind of shortcut that survives until it doesn't.
Same two entities, three different input mechanisms — distinguished by type and sourcePlatform on the DMP Segment.
| Mechanism | type | sourcePlatform | Scope |
|---|---|---|---|
| CSV list upload one-shot file of contacts or companies | USER_LIST_UPLOAD COMPANY_LIST_UPLOAD | LIST_UPLOAD | rw_ads |
| Streaming continuous add/remove via API | USER COMPANY | DIRECT_API AGENCY_API PARTNER_API | rw_dmp_segments |
| Predictive Audiences LinkedIn's AI extends a seed list | — | — | rw_dmp_segments |
Two details in that table deserve attention.
First, CSV list upload uses rw_ads — the scope you already have. It sits inside the Matched Audiences docs but does not need the Matched Audiences scope. If you want first-party list targeting before your rw_dmp_segments application clears, this is the path.
Second, sourcePlatform is a declaration about what kind of business you are, and you must pick correctly:
| Value | Means |
|---|---|
| DIRECT_API | An advertiser using matched audiences for its own ad accounts |
| AGENCY_API | An agency managing audiences on behalf of multiple clients |
| PARTNER_API | A platform enabling many advertisers to sync audiences at scale — not an agency |
For your integration
You described a multi-tenant product where many customers connect their own accounts. That is PARTNER_API — the "platform at scale, not an agency" case. It is also indexable alongside sourceSegmentId, an optional foreign key back to your ID for the segment. Set sourceSegmentId from day one: it is the only field that lets you find your own segments by your own identifier, and you cannot backfill what you never wrote.
Lesson 03 left you with a puzzle — two different facets with an identical value type:
urn:li:adTargetingFacet:audienceMatchingSegments → urn:li:adSegment:10001
urn:li:adTargetingFacet:dynamicSegments → urn:li:adSegment:10001
Here is the rule. The split is by provenance, not by type:
| Facet | Holds |
|---|---|
| audienceMatchingSegments | Contact lists, company lists — everything except retargeting |
| dynamicSegments | Website retargeting and engagement retargeting segments only |
Why this will bite you
An adSegment URN does not tell you which facet it belongs in. urn:li:adSegment:10001 is shaped identically whether it came from a CSV of email addresses or from the Insight Tag on a website. You must track provenance yourself — either from the dmpSegment.type you created it with, or by knowing it is a retargeting segment you did not create at all.
Your ad_segments table therefore needs a facet column that LinkedIn will never populate for you. Get that wrong and the campaign create fails, or worse, targets the wrong thing.
And the restrictions from Lesson 03 land squarely here: website retargeting segments may not be AND'ed with any clause targeting member behaviour or interests. So "people who visited our pricing page AND are interested in DevOps" is not expressible. Two facets, two different restriction profiles, one URN type.
Segments are not static rows. They age, and they expire without you touching them.
stateDiagram-v2 [*] --> BUILDING: create BUILDING --> READY: matched (up to 48h) BUILDING --> FAILED: list upload failed READY --> UPDATING: new list or streamed changes UPDATING --> READY: up to 24h READY --> ARCHIVED: 30 days unused BUILDING --> ARCHIVED: 30 days unused ARCHIVED --> BUILDING: added to an active campaign ARCHIVED --> EXPIRED: 90 days total unused EXPIRED --> BUILDING: new list upload only
Four things in there matter more than the diagram:
As of August 2026 a sponsored account is capped at 1,000 DMP segments, across Matched Audiences and Predictive Audiences combined. Exceeding it returns 429 with code SEGMENT_LIMIT_EXCEEDED.
This is recent and it is a real constraint for a platform product. If your design creates a segment per campaign, or per sync run, you will hit 1,000 on a large customer. Segments are a scarce, account-scoped resource — reuse them.
Deletion cascades, and it cascades further than you would expect:
DELETE /rest/dmpSegments/{id}
→ deletes the DMP segment
→ deletes its destinations
→ deletes the corresponding adSegment
→ deletes the uploaded list, if any
Deleting the staging entity destroys the targetable audience. But there is a guard: if the adSegment is in use by an active campaign, the delete fails with a 400. And you need write access to the owning sponsored account, or you get 403. So a "clean up old segments" job must handle both — and it is exactly the job you will need, given the 1,000 cap.
Worth naming because it is the answer to a question Lesson 01 left open. You asked about Lookalike Audiences; LinkedIn's counterpart is Predictive Audiences — you supply a seed (contact list or company list), LinkedIn's model extends it to members likely to convert. Same rw_dmp_segments scope, same 1,000-segment budget, and it produces an ordinary adSegment you target like any other.
Exercise 1 · execution
A customer wants their CRM contacts targetable, kept in sync. Click the steps in order.
Exercise 2 · recognition
Every one of these is a urn:li:adSegment. Pick the facet.
Exercise 3 · bug hunt
A · POST /rest/dmpSegments — a platform syncing CRM contacts for a customer
{ "name": "Acme CRM — marketing qualified", "account": "urn:li:sponsoredAccount:516848833", "type": "USER", "sourcePlatform": "LIST_UPLOAD", "destinations": [{ "destination": "LINKEDIN" }]}
B · reading the segment back to find the targetable audience
const seg = await getDmpSegment(id);const audience = `urn:li:adSegment:${seg.id}`;if (seg.destinations[0].status === 'READY') { await addToCampaignTargeting(campaignId, audience);}
C · POST /rest/dmpSegments — target account list, streamed
{ "name": "Acme — tier 1 target accounts", "account": "urn:li:sponsoredAccount:516848833", "type": "COMPANY", "sourcePlatform": "PARTNER_API", "sourceSegmentId": "acme/tier1/v3"}
Exercise 4 · recall
Exercise 5 · free recall
From memory: name three columns your ad_segments table needs that LinkedIn will never give you, and say why each is unrecoverable if you skip it.
Now unlocked
Between Lessons 01–04 you have the spine, the actors, the promotables, the targeting algebra, and audiences. The next natural fringe is status vs. serving — the three-layer distinction between what you asked for (intendedStatus), what the entity is (status), and why it isn't actually running (servingHoldReasons). That is the vocabulary your support team will live in.
Sources: Matched Audiences Overview, DMP Segments, Targeting Criteria Facet URNs, version li-lms-2026-08.