Lesson 03 · outer fringe

targetingCriteria

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


The shape

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).

The one rule people get wrong

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.

And the asymmetry

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.

Facets are typed

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.

FacetValue typeExample value
locationscountry, state, regionurn:li:country:us
titlestitleurn:li:title:4
senioritiesseniorityurn:li:seniority:7
employersorganizationurn:li:organization:1035
industriesindustryurn:li:industry:9
staffCountRangesstaffCountRangeurn:li:staffCountRange:(51,200)
audienceMatchingSegmentsadSegmenturn:li:adSegment:10001
dynamicSegmentsadSegmenturn: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.

Discovering facets and values

Never hardcode value lists. Three endpoints exist for this, and a real integration needs all three:

EndpointJob
/adTargetingFacetsWhich facets exist
/adTargetingEntitiesWhich values exist within a facet, and typeahead search
urn-to-name resolverTurn 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.

Illegal combinations

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.

RestrictionDetail
industriesemployersIndustries may not be AND'ed with any include clause targeting employers. Same for staffCountRanges.
titlesseniorities, jobFunctionsSeniorities and job functions may not be AND'ed with include clauses targeting job titles.
staffCountRangesInclusively or exclusively — never both in one campaign.
website retargeting (dynamicSegments)May not be AND'ed with any clause targeting member behaviour or interests.
memberBehaviorsMay not be AND'ed with contact audience-matching segments or website retargeting segments.
Include-only facetsageRanges, 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.


Practice

Exercise 1 · bug hunt

Click the broken line

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

Where can this facet appear?

Click include-only or both.

Exercise 3 · execution

Write the criteria

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.


Retaining this

  1. Say "AND of ORs" once, out loud. That single phrase compresses the whole structure. If you retain nothing else, retain the CNF framing — you can re-derive the JSON shape from it.
  2. Write the failure mode down where your team will see it: two facets in one or silently widen the audience instead of narrowing it. No error, real money.
  3. Tomorrow, from memory: how do you express "A and B"? How do you express "not (A and B)"? The second question has an interesting answer — you cannot, and knowing why tells you that you have understood the asymmetry.

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.