# Targeting

Targeting controls which impressions a line item — or one of its splits — is eligible to bid on. Both line item delivery and splits use the **same `Targeting` shape**: a single object keyed by attribute name, with each attribute carrying its own include/exclude flag.

```ts
type Targeting = {
  geography?:            { excluded: boolean, value: string[] };
  domain?:               { excluded: boolean, value: string[] };
  site?:                 { excluded: boolean, value: number[] };
  tagId?:                { excluded: boolean, value: string[] };
  page?:                 { excluded: boolean, value: string[] };
  device?:               { excluded: boolean, value: string[] };
  os?:                   { excluded: boolean, value: string[] };
  browser?:              { excluded: boolean, value: string[] };
  connection?:           { excluded: boolean, value: string[] };
  browserLanguage?:      { excluded: boolean, value: string[] };
  keyword?:              { excluded: boolean, value: string[] };
  firstId?:              { excluded: boolean, value: string[] };
  dayandtime?:           { excluded: boolean, value: DayAndTime[] };
  viewabilityThreshold?: { value: number };
  impData?:              DataTargeting[];
  siteAppData?:          DataTargeting[];
  userData?:             DataTargeting[];
};

type DataTargeting = {
  excluded: boolean;
  value: Record<string, string[]>;
};
```

All attributes use **AND logic** — every present attribute must pass for the line item to be eligible. Within a single attribute, `excluded: false` means the request value must be in `value`; `excluded: true` means it must not be.

## Attribute reference

### Geography & Inventory

| Attribute   | Matches against                                                        | Value type | Example                              |
| ----------- | ---------------------------------------------------------------------- | ---------- | ------------------------------------ |
| `geography` | Country, region, or city from the bid request (mixed levels supported) | `string[]` | `['US', 'FR-IDF', 'San Francisco']`  |
| `domain`    | `site.domain` (supports wildcards: `*.example.com`)                    | `string[]` | `['example.com', '*.publisher.net']` |
| `site`      | `imp.ext.nexx360.siteId`                                               | `number[]` | `[2097, 3045]`                       |
| `tagId`     | `imp.ext.nexx360.tagId`                                                | `string[]` | `['yqsc1tfj']`                       |
| `page`      | `site.page` (exact URL match)                                          | `string[]` | `['https://example.com/article']`    |

#### Domain matching rules

* Only the `*.` prefix form is supported. Bare `*`, `*example.com`, or `sub.*.com` will not match anything.
* `*.example.com` matches both `example.com` itself **and** any subdomain (`a.example.com`, `a.b.example.com`, …) — the apex is included, not just subdomains.
* A leading `www.` is stripped from both the request's `site.domain` and each targeted value before comparison, so `example.com` and `www.example.com` are interchangeable.

### Device & environment

| Attribute         | Matches against                                                    | Example                 |
| ----------------- | ------------------------------------------------------------------ | ----------------------- |
| `device`          | Device type from the bid request                                   | `['desktop', 'mobile']` |
| `os`              | `device.os`                                                        | `['iOS', 'Android']`    |
| `browser`         | Substring match against `device.ua`                                | `['Chrome', 'Firefox']` |
| `connection`      | `device.connectiontype` (numeric, stringified)                     | `['2']` (Wi-Fi)         |
| `browserLanguage` | `device.language` (prefix matching: `en` matches `en`, `en-US`, …) | `['en', 'fr']`          |

### Content & Identity

| Attribute | Matches against                                                       | Example             |
| --------- | --------------------------------------------------------------------- | ------------------- |
| `keyword` | `site.keywords` or `app.keywords` (comma-separated, case-insensitive) | `['sport', 'news']` |
| `firstId` | First-party ID from `user.ext.eids` (source `first-id.fr`)            | `['user-id-123']`   |

### Time & viewability

| Attribute              | Description                                                |
| ---------------------- | ---------------------------------------------------------- |
| `dayandtime`           | Day of week + hour ranges (e.g. Monday–Friday 09:00–17:00) |
| `viewabilityThreshold` | Minimum viewability percentage (`{ value: 0.5 }`)          |

### Data targeting

The data-targeting attributes carry an array of `{ excluded, value: Record<string, string[]> }` entries. Each entry is a conjunction across its keys — every targeted key must have at least one matching value in the request — and entries are AND-combined.

| Attribute     | Reads from                                      | Notes                                       |
| ------------- | ----------------------------------------------- | ------------------------------------------- |
| `impData`     | `imp.ext.data`                                  | Per-impression data (taxonomy, segments, …) |
| `siteAppData` | `site.ext.data`, falling back to `app.ext.data` | Site/app-level data                         |
| `userData`    | `user.ext.data`                                 | User-level data (audience IDs, segments)    |

If the request data record is absent, an `excluded: false` entry fails (cannot match) and an `excluded: true` entry passes (no overlap to block on).

## Examples

### Include specific countries

```json
{ "geography": { "excluded": false, "value": ["US", "CA", "GB"] } }
```

### Exclude specific domains

```json
{ "domain": { "excluded": true, "value": ["competitor.com", "*.spam.net"] } }
```

### Target mobile devices only

```json
{ "device": { "excluded": false, "value": ["mobile"] } }
```

### Combine multiple attributes (AND logic)

A line item with the following targeting bids only on impressions from France or Germany, on mobile devices, for the domain `publisher.com`:

```json
{
  "geography": { "excluded": false, "value": ["FR", "DE"] },
  "device":    { "excluded": false, "value": ["mobile"] },
  "domain":    { "excluded": false, "value": ["publisher.com"] }
}
```

### Day-and-time targeting

Target weekdays during business hours (UTC):

```json
{
  "dayandtime": {
    "excluded": false,
    "value": [
      { "day": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], "hours": { "start": "09:00", "end": "17:00" } }
    ]
  }
}
```

### User data targeting (`user.ext.data`)

`userData` reads `request.user.ext.data`, a `Record<string, string[]>` carrying user-level segments and audience signals (audience IDs, lifestyle categories, CRM cohorts, etc.). Each entry in the `userData` array is a `{ excluded, value: Record<string, string[]> }` rule. Within a single rule, **all** targeted keys must have at least one of their values present in the request — AND across keys, OR within a key's values.

#### Bid request reference

```json
{
  "user": {
    "ext": {
      "data": {
        "audience": ["premium", "loyalty"],
        "category": ["sports", "news"],
        "tier": ["gold"]
      }
    }
  }
}
```

#### Example 1 — single audience inclusion

Only bid when the user is in the `premium` audience:

```json
{
  "userData": [
    { "excluded": false, "value": { "audience": ["premium"] } }
  ]
}
```

#### Example 2 — multi-key conjunction (premium AND sports)

Both keys must match. The user above (`audience: ["premium", "loyalty"]`, `category: ["sports", "news"]`) passes. A user with `audience: ["premium"]` but `category: ["finance"]` fails.

```json
{
  "userData": [
    { "excluded": false, "value": { "audience": ["premium"], "category": ["sports"] } }
  ]
}
```

#### Example 3 — included + excluded coexist

Premium audience required, but block any user flagged as `adult` category:

```json
{
  "userData": [
    { "excluded": false, "value": { "audience": ["premium"] } },
    { "excluded": true,  "value": { "category": ["adult"] } }
  ]
}
```

The included entry requires `request.user.ext.data.audience` to contain `"premium"`. The excluded entry blocks if `request.user.ext.data.category` contains `"adult"`. Both rules are evaluated independently — if either fails, the line item is ineligible.

#### Example 4 — multi-value OR within a key

Any of `gold`, `platinum`, or `diamond` in the user's `tier` field passes:

```json
{
  "userData": [
    { "excluded": false, "value": { "tier": ["gold", "platinum", "diamond"] } }
  ]
}
```

#### Example 5 — split-level user data targeting

The `userData` shape is identical at split level. A line item can run a premium-only split and a fallback split off the same impression pool:

```json
{
  "splits": [
    {
      "id": 1,
      "percentage": 1,
      "cpm": 5,
      "targeting": {
        "userData": [{ "excluded": false, "value": { "audience": ["premium"] } }]
      }
    },
    {
      "id": 2,
      "percentage": 1,
      "cpm": 1,
      "targeting": {}
    }
  ]
}
```

#### Behaviour when `user.ext.data` is missing

| Rule type                    | Outcome                                                      |
| ---------------------------- | ------------------------------------------------------------ |
| `excluded: false` (included) | Line item ineligible — no data record means no key can match |
| `excluded: true` (excluded)  | Line item passes — no data record means nothing to block on  |

The reader also rejects malformed shapes (values that aren't string arrays, arrays containing non-strings) and treats them as missing data.

### Site/app data targeting (`site.ext.data` → `app.ext.data`)

`siteAppData` reads `request.site.ext.data` first; if `site` is absent (in-app inventory), it falls back to `request.app.ext.data`. The same `Record<string, string[]>` shape applies. This is the right place for publisher-level signals like content vertical, IAB taxonomy, brand-safety flags, or page sentiment.

#### Bid request reference (web)

```json
{
  "site": {
    "domain": "example.com",
    "page": "https://example.com/markets",
    "ext": {
      "data": {
        "vertical": ["finance"],
        "iab": ["IAB13", "IAB13-1"],
        "sentiment": ["positive"]
      }
    }
  }
}
```

#### Bid request reference (in-app)

```json
{
  "app": {
    "bundle": "com.example.news",
    "ext": {
      "data": {
        "vertical": ["news"],
        "iab": ["IAB12"]
      }
    }
  }
}
```

#### Example 1 — vertical inclusion

Only bid on finance content (works on both web and in-app inventory thanks to the fallback):

```json
{
  "siteAppData": [
    { "excluded": false, "value": { "vertical": ["finance"] } }
  ]
}
```

#### Example 2 — IAB taxonomy + sentiment

Bid on positive-sentiment news pages with the IAB12 (News) classification:

```json
{
  "siteAppData": [
    { "excluded": false, "value": { "iab": ["IAB12"], "sentiment": ["positive"] } }
  ]
}
```

#### Example 3 — brand safety exclusion

Block any inventory flagged with brand-unsafe categories:

```json
{
  "siteAppData": [
    { "excluded": true, "value": { "iab": ["IAB7-39", "IAB14-3"] } }
  ]
}
```

#### Example 4 — combined include + exclude

Stay in finance verticals but skip pages flagged with negative sentiment:

```json
{
  "siteAppData": [
    { "excluded": false, "value": { "vertical": ["finance"] } },
    { "excluded": true,  "value": { "sentiment": ["negative"] } }
  ]
}
```

#### Behaviour when both `site.ext.data` and `app.ext.data` are missing

| Rule type                    | Outcome              |
| ---------------------------- | -------------------- |
| `excluded: false` (included) | Line item ineligible |
| `excluded: true` (excluded)  | Line item passes     |

### Impression data targeting (`imp.ext.data`)

`impData` reads `imp.ext.data`, scoped to the **specific impression slot**, so different ad units on the same page can carry different targeting signals (placement type, viewability segment, refresh count, contextual keywords from the ad slot, etc.). Same `Record<string, string[]>` shape and same matching semantics as the other two.

#### Bid request reference

```json
{
  "imp": [
    {
      "id": "ad-slot-top",
      "banner": { "w": 300, "h": 250 },
      "ext": {
        "data": {
          "placement": ["atf"],
          "refresh": ["0"],
          "context": ["sports", "football"]
        }
      }
    }
  ]
}
```

#### Example 1 — above-the-fold only

```json
{
  "impData": [
    { "excluded": false, "value": { "placement": ["atf"] } }
  ]
}
```

#### Example 2 — first impression of the slot (no refresh)

The slot's `refresh` counter is `"0"` for the initial render and increments on each refresh. Targeting only the first render avoids paying for repeated views:

```json
{
  "impData": [
    { "excluded": false, "value": { "refresh": ["0"] } }
  ]
}
```

#### Example 3 — slot context match

The publisher annotates the ad slot with contextual keywords. Bid only on slots flagged as football content:

```json
{
  "impData": [
    { "excluded": false, "value": { "context": ["football"] } }
  ]
}
```

#### Example 4 — combined slot rules

ATF inventory only, on first render, in sports context:

```json
{
  "impData": [
    {
      "excluded": false,
      "value": { "placement": ["atf"], "refresh": ["0"], "context": ["sports"] }
    }
  ]
}
```

### Combining all three data scopes

`impData`, `siteAppData`, and `userData` are independent and AND-combined when present together. They allow targeting to be scoped at the **right level**: publisher signals on `siteAppData`, slot signals on `impData`, audience signals on `userData`.

```json
{
  "siteAppData": [{ "excluded": false, "value": { "vertical": ["finance"] } }],
  "impData":     [{ "excluded": false, "value": { "placement": ["atf"] } }],
  "userData":    [{ "excluded": false, "value": { "audience": ["premium"] } }]
}
```

The line item bids only when the publisher is finance, the slot is above the fold, and the user is in the premium audience — every layer must pass.

{% hint style="info" %}
A delivery-level line item must declare **at least one** targeting attribute to be eligible — an empty targeting object (`{}`) is rejected with reason `targeting:none`. Splits do not have this restriction; an empty split targeting matches all impressions and relies on `percentage` for traffic allocation.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://developer.nexx360.io/campaign-delivery/targeting.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
