Lesson 03 · outer fringe
It looks like fiddly nested JSON. It is conjunctive normal form with a handful of illegal combinations. Once you see the CNF you will never misread it again.
≈13 min · pinned to li-lms-2026-08
One field on the campaign — the level that owns targeting, as you correctly guessed in your calibration. Its value is a boolean expression over facets:
{
"targetingCriteria": {
"include": {
"and": [ // ← clauses, AND'ed together
{ "or": { // ← one clause
"urn:li:adTargetingFacet:locations": ["urn:li:country:de"]
}},
{ "or": {
"urn:li:adTargetingFacet:titles": ["urn:li:title:4"],
"urn:li:adTargetingFacet:seniorities": ["urn:li:seniority:7"]
}}
]
},
"exclude": {
"or": { // ← no "and" allowed here
"urn:li:adTargetingFacet:employers": ["urn:li:organization:1035"]
}
}
}
}
Read it as one sentence: (location = Germany) AND (title = X OR seniority = Y), minus (employer = 1035).
Inside a single or object you may list several different facets. They are OR'ed with each other, not AND'ed.
The expensive mistake
You want "CTOs in Germany". You write both facets inside one or object. You have just asked for everyone who is a CTO or anyone in Germany — an audience perhaps a thousand times larger than intended, which will happily spend the whole budget. Nothing errors. Nothing warns. The campaign simply performs terribly and the customer blames your product.
Two facets that must both hold go in two separate clauses of the and array:
✗ CTO or German
"and": [
{ "or": {
"…:titles": ["…:title:4"],
"…:locations": ["…:country:de"]
}}
]
✓ CTO and German
"and": [
{ "or": { "…:titles": ["…:title:4"] }},
{ "or": { "…:locations": ["…:country:de"] }}
]
So the structure is exactly CNF — an AND of ORs. Every clause must be satisfied; within a clause anything will do. Values inside one facet's array are also OR'ed, which is the only part that behaves the way people guess.
exclude takes a single or — no and array. You cannot express "exclude people who are both X and Y". Everything you list is OR'ed and subtracted wholesale.
Order of evaluation is fixed: include first (and it may not be empty), then exclude is subtracted from the result. And there is a floor — the resulting audience must exceed 300 members or the campaign will not serve. That surfaces later as CAMPAIGN_AUDIENCE_COUNT_HOLD in the servingHoldReasons you met in Lesson 01.
A facet URN is a key; its values are URNs of a facet-specific type. Getting the value type wrong is a common error and the API will tell you, unhelpfully.
| Facet | Value type | Example value |
|---|---|---|
| locations | country, state, region | urn:li:country:us |
| titles | title | urn:li:title:4 |
| seniorities | seniority | urn:li:seniority:7 |
| employers | organization | urn:li:organization:1035 |
| industries | industry | urn:li:industry:9 |
| staffCountRanges | staffCountRange | urn:li:staffCountRange:(51,200) |
| audienceMatchingSegments | adSegment | urn:li:adSegment:10001 |
| dynamicSegments | adSegment | urn:li:adSegment:10001 |
Note the range syntax: urn:li:staffCountRange:(51,200). Parentheses and a comma inside the URN id. Any URN parser that splits naively on : and takes the last field will mangle these. The ranges also use 2147483647 — INT_MAX — to mean "no upper limit".
The last two rows are the hook for Lesson 04, and they are strange: two different facets, identical value type. Hold that thought.
Never hardcode value lists. Three endpoints exist for this, and a real integration needs all three:
| Endpoint | Job |
|---|---|
| /adTargetingFacets | Which facets exist |
| /adTargetingEntities | Which values exist within a facet, and typeahead search |
| urn-to-name resolver | Turn stored URNs back into localised display names |
That third one is easy to forget and impossible to skip. You will store urn:li:title:4; your UI must show "Chief Technology Officer", in the customer's locale. There is no name in the targeting payload — only URNs.
A short list of pairs that cannot be AND'ed, plus a short list of facets that only work in include. These are not arbitrary — they mostly prevent contradictory or privacy-sensitive intersections.
| Restriction | Detail |
|---|---|
| industries ⊥ employers | Industries may not be AND'ed with any include clause targeting employers. Same for staffCountRanges. |
| titles ⊥ seniorities, jobFunctions | Seniorities and job functions may not be AND'ed with include clauses targeting job titles. |
| staffCountRanges | Inclusively or exclusively — never both in one campaign. |
| website retargeting (dynamicSegments) | May not be AND'ed with any clause targeting member behaviour or interests. |
| memberBehaviors | May not be AND'ed with contact audience-matching segments or website retargeting segments. |
| Include-only facets | ageRanges, genders, groups, interfaceLocales — these four cannot appear in exclude at all. |
Design implication
If your product offers a targeting builder UI, these rules are your validation problem. LinkedIn rejects the payload at create time, after your customer has filled in a whole form. Encode the restriction matrix client-side or you ship a UI that lets people build audiences the API refuses.
Exercise 1 · bug hunt
Each payload has a stated intent. One is correct.
A · intent: "senior engineers in the US"
"include": { "and": [ { "or": { "urn:li:adTargetingFacet:seniorities": ["urn:li:seniority:7"], "urn:li:adTargetingFacet:locations": ["urn:li:country:us"] }}]}
B · intent: "people in Germany, excluding two competitors"
{ "include": { "and": [ { "or": { "urn:li:adTargetingFacet:locations": ["urn:li:country:de"] }} ]}, "exclude": { "and": [ { "or": { "urn:li:adTargetingFacet:employers": ["urn:li:organization:1", "urn:li:organization:2"] }} ]}}
C · intent: "CTOs or VPs of Engineering, in the DACH region"
"include": { "and": [ { "or": { "urn:li:adTargetingFacet:titles": ["urn:li:title:4", "urn:li:title:19"] }}, { "or": { "urn:li:adTargetingFacet:locations": ["urn:li:country:de", "urn:li:country:at", "urn:li:country:ch"] }}]}
Exercise 2 · recognition
Click include-only or both.
Exercise 3 · execution
A customer wants: decision-makers at companies of 201–1000 staff, in the Netherlands or Belgium, excluding anyone who already follows their page. Sketch the targetingCriteria. Facet names matter; value IDs do not.
Next
Lesson 04 — Matched Audiences: your own customer data as a targetable audience. It plugs into the two facet rows we flagged above, and it is a separate API program with its own scope and its own vetting.
Sources: targetingCriteria Object, Targeting Criteria Facet URNs, Ad Targeting.