---
title: "AdOps"
slug: adops
category: business
term_status: established
evidence_status: verified
summary: "AdOps (advertising operations) turns sold or auctioned ad space into delivered, measured and billable impressions: it sets up and checks campaigns in the ad server, runs the auction plumbing, keeps fraud and unauthorised sellers out, and reconciles the numbers."
audience: [Beginners, Publisher ad ops and yield teams, Agency and advertiser trafficking teams, Engineers integrating ad tech, Revenue and media leaders]
last_verified: 2026-10-23
key_takeaways:
  - "Ad operations is an established job function (IAB runs a certification for it), but it has no single body of knowledge: its rules come from contracts, IAB Tech Lab specifications and MRC measurement guidelines."
  - "The core skill is making three records agree: what was sold (the order or deal), what the ad server and auctions decided, and what was counted and billed."
  - "ads.txt, sellers.json and the SupplyChain object make the programmatic supply chain checkable, but only when publishers keep them accurate and buyers enforce them."
  - "Discrepancies between ad servers are normal (Google documents up to 20% for third-party-hosted line items), so agree the controlling count and the tolerance before launch."
  - "Judge delivery, yield and trust together: pacing, fill and eCPM mean little if the measured rate is low, invalid traffic is high or unauthorised sellers carry your domain."
  - "Header bidding adds price competition, and also latency, line-item sprawl and new failure modes; set the timeout and price granularity from your own data."
  - "A small site with one ad network needs an owner and a checklist, not an AdOps team."
related: [martechops, marketingops, mediaops, privacyops, analyticsops, revops, contentops, webops]
---

## What AdOps is

### In 30 seconds

**AdOps (advertising operations, usually shortened to "ad ops") is the work that turns advertising that was sold, or put up for
auction, into ads that actually appear, are counted correctly and can be billed.** An ad ops team sets up orders, creatives and
targeting in an ad server, checks that everything renders and tracks, watches delivery daily, connects the auctions that let
outside buyers bid, keeps fraud and unauthorised resellers out, and reconciles counts with the buyer's before anyone invoices.

The open-source header-bidding project Prebid gives a practical definition of the role: "When we refer to ad ops, we're talking
about the people who login to the ad server software and manage the campaigns and accounts."[^prebid-hb] This page uses that
operational meaning and extends it to the supply-chain controls the job now depends on.

Written for beginners, publisher and agency ad ops teams, engineers who integrate ad tech, and leaders who own advertising
revenue or budgets. Sources were last verified on **2026-10-23**.

::: note Scope and emphasis of this page
The page centres on **the programmatic supply chain and what makes it trustworthy**: how an impression is decided, checked,
counted and reconciled. Campaign strategy, tag infrastructure, consent law and video delivery belong to
[neighbouring pages](#how-adops-relates-to-other-ops). Nothing here is legal advice; product behaviour is dated.
:::

### At a glance

| Question | Answer |
|---|---|
| Goal | Every sold or auctioned impression is delivered as agreed, measured to an agreed standard, sold only by authorised parties, and billed on reconciled numbers |
| Who does it | Publisher ad ops and yield teams; agency and advertiser trafficking teams; account and solutions teams at ad platforms |
| Inputs | Insertion orders (IOs) and deals, creatives and third-party tags, inventory definitions, price floors, consent signals |
| Outputs | Live line items and deals, QA evidence, delivery and discrepancy reports, invoices that survive scrutiny, an accurate ads.txt |
| Core systems | Publisher ad server, header-bidding wrapper, supply-side platform (SSP), demand-side platform (DSP), verification and consent tools |
| Prerequisites | Comfort with spreadsheets and basic HTML/JavaScript; no other Atlas page is required |
| Term status | **Established**: a recognised job function with an IAB professional certification and a large body of IAB Tech Lab and MRC standards, but no single framework document |

### The problem it solves

Digital advertising is sold in two very different ways, and both fail without operations.

- **Direct (guaranteed) sales.** A salesperson promises, say, 1.5 million impressions on the sports section in November.
  Someone must turn that into line items with the right targeting, dates, caps and creatives, check that the advertiser's tags
  fire, and steer delivery to the goal. A missed promise means a "make-good" (free replacement delivery) or a refund.
- **Programmatic (automated) sales.** Each impression is auctioned to many buyers in a fraction of a second. Someone must
  connect the auctions, set floors, map bids into the ad server, and make sure buyers get this publisher's inventory, not a
  counterfeit. In 2025 programmatic advertising in the US reached $162.4
  billion,[^iab-rev] by an IAB/PwC definition that also counts "publisher direct via a proprietary ad platform (i.e.
  self-serve)";[^iab-rev-full] in this Atlas's reading that includes social platforms, so the figure is not a measure of the open
  web alone.

The money at stake makes the failures visible. A study by the US Association of National Advertisers (ANA), based on log-level
data from 21 advertisers, concluded that "Only 36 cents of every dollar that enters a demand-side platform (DSP) effectively
reaches the consumer".[^ana] A UK study could not trace 15% of programmatic spend
([Scenario 2](#scenario-2-tracing-programmatic-spend-documented)), and fraud operations such as 3ve sold counterfeit inventory
under real publishers' names ([Scenario 1](#scenario-1-3ve-and-the-value-of-enforced-adstxt-documented)).

**Before and after.** Without ad ops discipline, creatives break after launch, under-delivery surfaces in the last week, ads.txt
lists long-gone partners, and month-end is an argument about numbers. With it, every campaign has a QA record, pacing is checked
daily, partners are verifiable, and the controlling count is agreed before launch.

### What AdOps is not

- **Not media planning or campaign strategy.** Audiences, budgets, channels and messages belong to
  [MarketingOps](../marketingops/); AdOps executes and verifies what was planned and sold.
- **Not the tag and data infrastructure.** Tag managers, server-side tracking and CDPs belong to [MarTechOps](../martechops/);
  AdOps owns which pixel fires on which ad.
- **Not privacy law.** Whether a lawful basis exists is for [PrivacyOps](../privacyops/) and counsel; AdOps makes sure the
  user's recorded choice travels with the ad request and is honoured.
- **Not video streaming engineering.** Encoding, packaging, ad markers and content delivery for video belong to
  [MediaOps](../mediaops/); AdOps covers what fills each ad break and how it is counted (see
  [video and CTV ad operations](#video-and-ctv-ad-operations)).
- **Not search-engine optimisation.** Organic search belongs to [SEOps](../seops/) and [SEO Operations](../seo-operations/);
  paid search is usually run by performance-marketing teams.
- **Not "DevOps for ads".** In this Atlas's reading the suffix comes from the job title "ad operations" (the sense in Prebid's
  definition above), not from the DevOps movement, though modern ad ops borrows its habits (version control, automated checks,
  post-incident reviews).

::: caution Two kinds of ad ops
**Sell-side ad ops** (publishers, app developers, streaming services) manages inventory, the ad server, header bidding and yield.
**Buy-side ad ops** (agencies and advertisers, often called trafficking) manages tags, DSP set-up, verification and
reconciliation. This page covers both and says which side it means.
:::

## Origins, vocabulary and neighbours

### Origins and evolution

In this Atlas's summary (no primary history of the job title was found), ad operations grew out of "trafficking" campaigns into
ad servers. The milestones below changed the job and can be dated from primary sources.

| When | What changed | Why it mattered to ad ops |
|---|---|---|
| November 2010 – January 2012 | OpenRTB began as a pilot of three DSPs and three SSPs; 2.0 (June 2011) unified display, mobile and video; 2.1 became an IAB Tech Lab standard (January 2012)[^ortb] | Interoperable real-time auctions to configure |
| August 2015 / October 2017 | MRC and IAB viewability guideline v2.0 (2015)[^mrc-view]; MRC invalid-traffic guidelines issued October 2015[^mrc-list]; IAB/MRC display impression guideline v1.1 (2017)[^mrc-display] | Billing disputes gained a reference definition |
| 2015 | Prebid.js launched as an open-source header-bidding wrapper[^prebid-about] | Line-item mapping and timeout tuning joined the job |
| 2016 | TAG launched its Certified Against Fraud programme[^tag] | Anti-fraud controls became certifiable |
| June 2017 | ads.txt 1.0 released by IAB Tech Lab[^adstxt-page] | Publishers declare authorised sellers |
| July 2019 | sellers.json and the OpenRTB SupplyChain object finalised[^sellers-page] | Buyers can identify every paid intermediary |
| January 2024 | Google required a Google-certified consent platform integrated with the TCF for personalised ads to EEA and UK users[^gam-cmp] | Consent signals became a delivery prerequisite |
| 2025–2026 | Chrome kept third-party cookies and retired most Privacy Sandbox APIs (October 2025)[^sandbox]; the European Commission fined Google over adtech self-preferencing (September 2025)[^ec]; a US court found Google monopolised open-web ad tech (April 2025)[^doj] and chose behavioural remedies (September 2026)[^adex] | Regulators now shape auction rules too |

**Is AdOps an established discipline?** Yes, as a job function. IAB lists a Digital Ad Operations Certification that
"Demonstrates knowledge and competency in the area of digital ad operations".[^iab-cert] Its 2017 study guide divides the job
into executing campaign launch (33% of exam weight), managing live campaigns (38%), supporting ad operations initiatives (19%)
and managing incidents (11%).[^daoc] IAB also ran a Mobile Ad Ops Working Group.[^iab-mobile] There is no single framework
document like ITIL; this page maps the contract templates, specifications and guidelines instead.

### Glossary and mental model

| Term | Meaning | Example |
|---|---|---|
| Inventory | Ad space a publisher can sell: ad units (slots), sizes, formats | A 300×250 slot on article pages |
| Insertion order (IO) | Contract for a direct campaign: dates, quantities, price, targeting, terms | 1.5 million impressions at $18 CPM |
| Line item | Ad-server object for one part of an order: type, priority, goal, targeting, creatives | "Standard", priority 6, goal 1.5 million |
| Ad server | Decides which ad fills each request; counts impressions and clicks | Google Ad Manager (sell side), Campaign Manager 360 (buy side) |
| Avails / sell-through | Forecast unsold impressions for given targeting / share of inventory sold | 200,000 sports avails in November |
| Key-values | Targeting pairs sent with the ad request | `section=sports`, `hb_pb=12.50` |
| Passback | A tag that cannot fill hands the impression to another ad server | Each hop adds discrepancy[^gam-disc] |
| Dynamic allocation / unified auction | Exchange and other non-guaranteed demand competing with line items in real time[^gam-li][^gam-ob] | AdX outbids a price-priority line item |
| First look / last look | A favoured source buys before others bid / sees competing bids before bidding (the EC described AdX being told the best rival bid in advance)[^ec] | Barred in the US proposed judgment[^adex-pfj] |
| SSP / DSP / exchange | Supply-side platform (sells a publisher's impressions) / demand-side platform (bids for buyers) / the marketplace where bids meet | A bid request flows SSP → DSP |
| RTB / OpenRTB | Real-time bidding / the IAB Tech Lab protocol for bid requests and responses[^ortb] | A JSON bid request with a 300 ms `tmax` |
| Header bidding | Collecting bids from several demand partners before calling the ad server, so they compete with it[^prebid-hb] | Prebid.js on the page, Prebid Server in the cloud |
| CPM / eCPM | Cost per thousand impressions / effective CPM = revenue ÷ impressions × 1000[^daoc] | $9,360 for 520,000 impressions = $18.00 eCPM |
| Viewable impression | At least 50% of an ad's pixels in view for at least one continuous second (display) or two seconds (video)[^mrc-view] | Measured by a verification tag |
| IVT (GIVT / SIVT) | Invalid traffic: general (found with lists and routine checks) or sophisticated (needs advanced analysis)[^mrc-ivt] | Data-centre bots (GIVT); spoofed domains (SIVT) |
| ads.txt / app-ads.txt | A public file listing the sellers a publisher authorises[^adstxt] | `ssp.example, 1001, DIRECT` |
| sellers.json | A public file in which an SSP lists the sellers it pays and whether each is a publisher or an intermediary[^sellers] | `"seller_type": "PUBLISHER"` |
| SupplyChain object (schain) | Every entity paid along the way, listed in the bid request[^schain] | Publisher's SSP node, then a reseller's |
| CMP / TCF / GPP | Consent management platform / the IAB Europe and IAB Tech Lab strings carrying user choices[^tcf][^gpp] | A TC string in the bid request |

#### A mental model: three records that must agree

Most ad ops problems are disagreements between three records (pacing, fill, make-good and discrepancy are defined in
[metrics and KPIs](#metrics-and-kpis)):

1. **What was promised**: the IO, the deal terms, the floor, the brand-safety rules.
2. **What was decided**: the line items, auctions and ad-server logs that chose an ad for each request.
3. **What was counted**: impressions, viewable impressions, clicks, invalid traffic and money, as measured by each party.

Trafficking aligns records 1 and 2; measurement standards align record 3 with reality; reconciliation aligns the parties'
versions of record 3 with each other and with record 1; supply-chain transparency (ads.txt, sellers.json, schain) checks that
whoever sold in record 2 had the right to.

### How AdOps relates to other Ops

::: figure AdOps and its neighbours: what crosses each boundary
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 120, "nodeSpacing": 14, "rankSpacing": 22, "padding": 8}}}%%
flowchart LR
  MK["MarketingOps: plans, budgets, campaign taxonomy"]
  MT["MarTechOps: tag manager, CDP, consent plumbing"]
  PV["PrivacyOps: lawful basis, consent policy"]
  RV["RevOps: revenue definitions, forecast"]
  A["AdOps: trafficking, auctions, supply-chain trust, reconciliation"]
  MD["MediaOps: video encoding, ad markers, delivery"]
  AN["AnalyticsOps: governed metrics and dashboards"]
  WB["WebOps: page speed and Core Web Vitals"]
  CT["ContentOps: content and pages to monetise"]
  MK --> A
  MT --> A
  PV --> A
  RV <--> A
  MD <--> A
  A --> AN
  A <--> WB
  CT --> A
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  class A core
  class MK,MT,PV,RV,MD,AN,WB,CT neutral
```
Text equivalent: AdOps sits in the centre. It receives plans and taxonomy from MarketingOps, tag and consent infrastructure from
MarTechOps, consent policy from PrivacyOps and pages from ContentOps. It exchanges revenue definitions with RevOps and
page-performance constraints with WebOps. With MediaOps the exchange is two-way: ad markers and ad-break inventory come in; ad
decisions and measurement requirements go back. Delivery data goes to AnalyticsOps.
:::

| Neighbouring page | Shared ground | AdOps focus | Neighbour's focus |
|---|---|---|---|
| [MarketingOps](../marketingops/) | Paid campaigns, budgets, naming taxonomy | Trafficking, pacing, verification, discrepancies | Campaign process, lead management, attribution |
| [MarTechOps](../martechops/) | Pixels, tags, consent signals | Which tags run on which ads; signals on ad requests | Tag manager and CDP architecture, server-side tracking |
| [PrivacyOps](../privacyops/) | Consent for personalised ads | Passing and honouring TCF/GPP signals | Lawful basis, notices, data-subject rights |
| [MediaOps](../mediaops/) | Ad breaks in video and CTV streams | VAST/VMAP, ad pods, insertion method, ad measurement | Encoding, packaging, ad markers, CDN delivery |
| [RevOps](../revops/) | Advertising revenue as a business line | Yield, fill, eCPM, billing accuracy | Revenue definitions, forecasting across functions |
| [AnalyticsOps](../analyticsops/) | Ad revenue dashboards | Producing trustworthy delivery data | Metric definitions and dashboard governance |
| [WebOps](../webops/) | Page weight and latency added by ads | Timeouts, lazy loading, ad density | Measuring and fixing Core Web Vitals |
| [ContentOps](../contentops/) | Pages and sections that carry ads | Ad units and targeting keys per section | Content lifecycle and structure |

The boundary with MarTechOps is the one most often blurred: **MarTechOps owns the tag infrastructure; AdOps owns delivery.** This
is an editorial distinction of this Atlas, not an industry standard; small teams often do both jobs.

## Principles and operating model

### Charter and principles

No industry body publishes an AdOps manifesto; the charter below is **this Atlas's proposal**, distilled from the sources cited.

> **Mission.** Deliver every impression that was sold or auctioned exactly as agreed, count it by an agreed method, sell it only
> through parties the owner has authorised, and bill it on numbers both sides can trace.

1. **The contract is the specification.** The IO or deal fixes targets, dates, counting method and tolerances before launch;
   the 4A's/IAB template, for example, names which count controls and what happens above a 10% gap.[^tandc]
2. **Count late, count once, count the same way.** An impression counts when the ad has loaded and begun to render, not when the
   server decides to send it.[^mrc-display]
3. **Every seller is declared and checkable** in your ads.txt or app-ads.txt, and every paid hop in the SupplyChain object and
   its system's sellers.json.[^adstxt][^schain]
4. **Consent travels with the ad request and is enforced where you control it.** Passing a TCF or GPP string lets each partner
   apply it but blocks no request by itself.[^tcf][^gpp][^pb-tcfcontrol]
5. **Every change is reversible and attributable,** so "what changed?" has an answer within minutes of a revenue drop.
6. **Revenue does not outrank the reader.** Ad density, page speed, malicious creatives and intrusive formats are operational
   risks.[^betterads]
7. **Investigate while it is live,** when evidence and options still exist.[^gam-disc]

**Non-goals.** Maximising today's CPM at the cost of trust; deciding whether a legal basis exists for personalised advertising
(that is [PrivacyOps](../privacyops/)); choosing audiences and creative strategy (that is [MarketingOps](../marketingops/)).

### Roles, ownership and RACI

| Role | Side | Main responsibilities |
|---|---|---|
| Ad ops lead | Either | Operating model, standards, staffing, escalation, partners |
| Trafficker / campaign manager | Either | Orders, line items, creatives; pre-launch QA |
| Yield or programmatic manager | Sell | Floors, deals, bidders, auction analysis, supply-partner review |
| Ad tech engineer | Sell | Tags, wrapper, consent integration, page performance, ads.txt publishing |
| Sales planner / account manager | Sell | Avails and forecasts; client communication |
| Buy-side trafficker | Buy | Third-party ad server, tags, DSP line items, verification |
| Billing / finance | Either | Invoices, credits, make-goods, audit trail |

The RACI below is a starting template for a publisher (R = responsible, A = accountable, C = consulted, I = informed).

| Activity | Ad ops lead | Trafficker | Yield mgr | Ad tech eng. | Sales / finance |
|---|---|---|---|---|---|
| IO intake and feasibility | A | R | C | I | C (sales) |
| Trafficking and QA | A | R | I | C | I |
| Daily pacing and fixes | A | R | C | I | I |
| Floors, deals, bidder mix | C | I | A/R | C | C (sales) |
| Wrapper and timeout changes | A | I | C | R | I |
| ads.txt and sellers.json audit | A | I | R | R | I |
| Consent-signal integration | A | I | C | R | C (privacy owner) |
| IVT or malvertising response | A | R | R | R | I |
| Discrepancy reconciliation | A | R | C | C | R (finance) |
| Invoice approval | C | C | I | I | A/R (finance) |

### The campaign and partner lifecycles

Ad ops runs two lifecycles side by side: per campaign (weeks) and per partner (years).

| Campaign stage | Main output | Exam domain in IAB's 2017 study guide[^daoc] |
|---|---|---|
| Intake and feasibility (avails, forecast) | Accepted IO with complete specification | Executing campaign launch |
| Trafficking | Orders, line items, creatives, targeting, frequency caps | Executing campaign launch |
| Pre-launch QA | Signed QA checklist, test-page evidence | Executing campaign launch |
| Launch and verify | First-hour delivery and tracking confirmed | Executing campaign launch |
| Monitor and optimise | Daily pacing, fixes, client updates | Managing live campaigns |
| Wrap and reconcile | Final counts, discrepancy analysis, make-good decision | Managing live campaigns |
| Bill and review | Invoice, post-campaign report, lessons | Managing live campaigns / Managing incidents |

Partners follow their own cycle: **evaluate** (terms, data and consent handling, TAG status) → **onboard** (ads.txt line,
adapter, staging test, sellers.json entry checked) → **operate** (monthly scorecard of bid rate, timeouts, IVT, revenue share) →
**review** quarterly (keep, renegotiate, remove) → **offboard** (remove adapter, line items and ads.txt line; confirm no further
requests). Keep the ads.txt diff and decision record at each step.

### Operating model and governance

A workable cadence for a mid-sized publisher (an Atlas proposal; scale it up or down):

- **Daily (15 minutes):** pacing outside 95–110%, zero-delivery line items, fill or eCPM jumps, wrapper errors and timeouts.
- **Weekly:** yield review (floors, deals, bidders, latency); creative incidents; open discrepancies.
- **Monthly:** billing close; partner scorecards; ads.txt and sellers.json audit (Lab 2).
- **Quarterly:** supply-path review, contract renewals, maturity self-assessment.

**Change control.** Priorities, pricing rules, wrapper timeouts and ads.txt can each move site-wide revenue; give each change an
approver, a ticket and a rollback step. Google warns that "Incorrectly configured pricing rules can
sometimes lead to ad delivery issues, such as low fill rates".[^gam-pricing]

**Escalation.** Tie severity to money and users: Sev 1 for site-wide loss of serving, malicious creatives or consent failure;
Sev 2 for a guaranteed campaign that cannot meet its goal or a large revenue drop; Sev 3 for single-line-item faults. Incident
mechanics in general are in [IncidentOps](../incidentops/).

## Architecture and workflow

### Reference architecture: the publisher ad stack and its components

The common open-web pattern for a publisher selling direct and programmatic. App and connected-TV (CTV) stacks swap the page
for an SDK or video player but keep the layers.

::: figure Publisher ad stack: from page to paid impression
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 130, "nodeSpacing": 16, "rankSpacing": 24, "padding": 8}}}%%
flowchart TB
  CMP["Page: consent platform (TCF or GPP string)"]
  HB["Page: header-bidding wrapper (Prebid.js)"]
  SSP["SSPs and exchanges"]
  DSP["DSPs bidding for buyers"]
  TXT["Public files: ads.txt, sellers.json"]
  TAG["Page: ad-server tag (e.g. GPT)"]
  AS["Publisher ad server: priorities, floors, auction"]
  VER["Verification: viewability, IVT, brand safety"]
  CMP --> HB
  CMP --> TAG
  HB --> SSP
  SSP --> DSP
  DSP -. "checks before bidding" .-> TXT
  HB -- "bids as key-values" --> TAG
  TAG --> AS
  AS -- "winning creative" --> TAG
  TAG --> VER
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  class AS,HB core
  class CMP,TAG,SSP,DSP neutral
  class VER,TXT decision
```
Text equivalent: On the page, the consent platform feeds the consent string to the header-bidding wrapper and the
ad-server tag. The wrapper asks SSPs, which ask DSPs, for bids, and passes them to the tag as key-values. The tag calls the
publisher ad server, which returns the winning creative; verification services measure it. DSPs check ads.txt and sellers.json
before trusting the inventory (dotted line).
:::

| Component | Responsibility | Trust boundary and trade-off |
|---|---|---|
| Consent platform | Encodes the user's choices for tags | Wrong signals mean regulatory exposure and, for Google, limited ads[^gam-cmp] |
| Header-bidding wrapper | Parallel bid requests, timeout, winners to the ad server | Third-party code and latency; server-side bidding (Prebid Server, Open Bidding[^gam-ob]) cuts page work |
| Ad-server tag | Requests, renders and counts ads | Runs creative code inside your page |
| SSP / exchange | Packages impressions as OpenRTB bid requests | Sees user data; may resell; more paths add competition and duplicates |
| Publisher ad server | Chooses between guaranteed, remnant and auction demand; counts | Holds commercial truth; the European Commission found Google's DFP dominant[^ec] |
| Verification | Independent viewability, IVT and brand-safety measurement | Adds tags, latency and a second count to reconcile |
| Public files | ads.txt (publisher domain), sellers.json (SSP domain) | Public; stale entries mislead buyers |

### How one impression is decided

::: figure One web impression with header bidding, step by step
```mermaid
sequenceDiagram
  participant AS as Ad server
  participant P as Page (CMP, Prebid, GPT)
  participant S as SSP or exchange
  participant D as DSP
  P->>P: read consent string
  P->>S: bid request (consent, floor, schain)
  S->>D: OpenRTB request, tmax
  D-->>S: bid price and creative
  S-->>P: bid (rounded later to a price bucket)
  P->>AS: ad request with hb_pb key-values
  AS->>AS: priorities, floors, unified auction
  AS-->>P: winning creative
  P->>P: render, count impression, fire trackers
```
Text equivalent: Participants from left to right: ad server, page, SSP or exchange, DSP. The page reads the consent string and
sends a bid request (consent, floor, schain) to the SSP, which sends an OpenRTB request with a time limit to the DSP. The bid
returns through the SSP to the page, which calls the ad server with the price bucket as key-values. The ad server applies
priorities, floors and its auction and returns the winner, which may not be the header bid. The page renders it, counts the
impression and fires trackers.
:::

Three details explain most surprises:

- **The ad server still decides.** Prebid's documentation is explicit: "Yes, you still need an ad server," and "The ad server
  will still determine the final winner for each ad unit."[^prebid-hb] Header bids enter as non-guaranteed line items matching
  key-values, a use Google's line-item documentation names.[^gam-li]
- **Priorities come before price for guaranteed demand.** In Google Ad Manager (as of October 2026), Sponsorship line items
  have priority 4 and Standard 6, 8 or 10; Network, Bulk, Price priority and Ad Exchange items have 12; House 16; "The lower the
  number, the higher the priority."[^gam-li] When dynamic allocation is on, non-guaranteed demand can still compete with
  guaranteed line items in real time.[^gam-li]
- **Win is not bill.** In OpenRTB the win notice is "not necessarily indicative of a delivered, viewed, or billable ad"; a
  separate billing notice fires when the impression becomes billable.[^ortb] A DSP's "wins" never match a publisher's
  "impressions" exactly.

### End-to-end workflow: from order to invoice

::: figure Order-to-invoice workflow with the main exception paths
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 120, "nodeSpacing": 14, "rankSpacing": 20, "padding": 8}}}%%
flowchart TB
  IO["IO or deal received"] --> FE{"Spec complete and avails OK?"}
  FE -- "no" --> BACK["Return to sales with gaps"]
  FE -- "yes" --> TR["Traffic line items and creatives"]
  TR --> QA{"QA passes on test page?"}
  QA -- "no" --> FIX["Fix tags, macros, targeting"]
  FIX --> QA
  QA -- "yes" --> GO["Launch; verify first hour"]
  GO --> MON{"Pacing 95-110% and tracking OK?"}
  MON -- "no" --> ACT["Diagnose: targeting, priority, creative, discrepancy"]
  ACT --> MON
  MON -- "yes, flight ends" --> REC{"Gap with buyer within tolerance?"}
  REC -- "no" --> DISP["Reconcile; make-good or adjusted invoice"]
  REC -- "yes" --> INV["Invoice on controlling count"]
  DISP --> INV
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  class IO,TR,GO,INV core
  class FE,QA,MON,REC decision
  class BACK,FIX,ACT,DISP risk
```
Text equivalent: An incomplete IO or one without avails returns to sales. Otherwise it is trafficked and tested, looping through
fixes until QA passes, then launched and checked in the first hour. Pacing outside 95–110% or broken tracking triggers diagnosis.
After the flight, a gap outside tolerance leads to reconciliation and a make-good or adjusted invoice; otherwise the invoice uses
the controlling count.
:::

**Happy path, with owners.** Sales submits the IO through the
[intake form](#reference-implementation-operating-artefacts); the trafficker checks it within one business day, builds, QAs and
launches; the daily stand-up watches pacing; finance invoices on the controlling count.

**Exception paths that deserve a written procedure.**

1. **Incomplete IO** (missing sizes, dates, counting terms): do not traffic "to be safe"; return it with the gaps listed.
2. **Creative fails QA** (non-secure calls, broken macros, wrong size, heavy file): reject with the evidence; never edit a buyer's
   tag without written consent.
3. **Under-delivery forecast** (the run rate needed exceeds what the targeting can supply): tell the client early with options
   (broaden targeting, extend dates, raise priority, make-good).
4. **Discrepancy above tolerance**: follow the [worked runbook](#worked-runbook-under-delivery-and-a-15-lower-buyer-count).
5. **Suspected IVT or malvertising**: pause the line item or creative first, then investigate (see
   [quality, security, privacy and compliance](#quality-security-privacy-and-compliance)).

### Video and CTV ad operations

Same three records, different plumbing: [MediaOps](../mediaops/) packages the programme and carries the ad markers; AdOps decides
what fills each break and how it is counted.

- **VAST and VMAP.** VAST is IAB Tech Lab's "universal XML schema for serving ads to digital video players";[^vast] VMAP lets a
  content owner that does not control the player define the ad breaks (timing, number, ad types); "What VMAP cannot do is define
  the ads themselves".[^vmap]
- **Client-side insertion (CSAI).** At a cue the player requests VAST, follows Wrapper responses to an InLine response, plays
  the ad and fires the trackers itself.[^vast43]
- **Server-side insertion (SSAI, "ad stitching").** A stitching service requests VAST, transcodes new creatives from their
  mezzanine files and stitches ads into one continuous stream. A creative it has not yet transcoded is skipped (VAST error 407), and it
  recognises creatives by `<UniversalAdId>`, so a changed creative needs a new ID. Tracking sent by the server comes from one IP
  address and can "look similar to invalid traffic", so stitchers must forward `X-Device-IP` and `X-Device-User-Agent`;
  impression reporting "may vary by implementation", while the MRC guidelines quoted in VAST favour client-initiated
  counting.[^vast43] OpenRTB 2.6 tells buyers which mode applies with `imp.ssai` (0 unknown, 1 all client-side, 2 assets
  stitched but trackers client-side, 3 all server-side).[^ortb]
- **Ad pods.** OpenRTB 2.6 sells a break as a pod: structured (fixed slots and durations), dynamic (total duration and maximum
  number of ads known) or hybrid. Key fields: `podid`, `podseq`, `slotinpod`, `poddur`, `maxseq`, `rqddurs` (exact durations,
  to avoid "dead air" in live TV) and `mincpmpersec`, a floor per second for the dynamic part. The seller builds the final pod
  and may use none, some or all of a buyer's bids.[^ortb][^ortb-pods]
- **Measurement and trust.** The Open Measurement SDK gives verification vendors one integration in apps and CTV;[^omsdk] MRC
  asks SIVT vendors to check spoofed CTV bundle IDs;[^mrc-ivt] app-ads.txt also covers "connected television app stores",[^appads] and
  the INVENTORYPARTNERDOMAIN variable is "expected to be used primarily for CTV/OTT inventory, where 'inventory sharing' is common".[^adstxt-guide]

**Operational checks** (an Atlas list): deliver creatives early enough to be transcoded; agree with the verification vendor
the `ssai` mode and headers; set pod floors per second; write into the IO whether impressions count at the stitcher or player.

## The programmatic supply chain and its trust mechanisms

This part is the centre of the page. Programmatic buying lets software buy from sellers the buyer has never met, through
intermediaries the publisher may not know. Deal terms, the public seller files, the SupplyChain object and the consent string
make that chain checkable; invalid-traffic filtering catches what they miss.

### Deal types: how much is agreed before the auction

| Deal type | Price | Volume | Who can buy | How it appears in OpenRTB |
|---|---|---|---|---|
| Programmatic guaranteed (automated guaranteed) | Fixed | Guaranteed | One buyer | Deal ID; fixed deal price (deal `at` = 3) |
| Preferred deal | Fixed | Not guaranteed | One buyer | Deal ID; fixed deal price (deal `at` = 3) |
| Private marketplace (PMP) | Auction | Not guaranteed | Invited buyers only | Deal IDs in `imp.pmp` |
| Open auction | Auction | Not guaranteed | Any buyer the exchange admits | No deal ID; `at` 1 = first price, 2 = second price plus[^ortb] |

In OpenRTB, a deal's `at` = 3 means its `bidfloor` "is the agreed upon deal price", which fits both fixed-price rows.[^ortb]
Definitions follow the IAB/PwC revenue report (preferred deals: "pricing is guaranteed but inventory volume is not").[^iab-rev-full]
The further down the table, the more a buyer depends on the supply-chain files below.

### ads.txt and app-ads.txt: who may sell this inventory

ads.txt ("Authorized Digital Sellers") is a plain-text file at `/ads.txt` on a publisher's root domain in which the publisher lists
every advertising system and account that may sell its inventory.[^adstxt] Its mission, in IAB Tech Lab's words, is to "Increase
transparency in the programmatic advertising ecosystem".[^adstxt-page] app-ads.txt applies the same idea to apps, "linking app store
listings to app developer websites": the buyer finds the file through the developer URL in the app store listing.[^appads]

Each record has three required fields and one optional field:[^adstxt]

```text
# <system domain>, <account ID>, <DIRECT|RESELLER>, <optional cert ID>
ssp-one.example, 1001, DIRECT, abc123
ssp-one.example, 2002, RESELLER
OWNERDOMAIN=news.example
MANAGERDOMAIN=yieldpartner.example
CONTACT=adops@news.example
```

- **DIRECT** means the publisher "directly controls the account" on that system; **RESELLER** means the publisher "has authorized
  another entity to control the account" and resell its space.[^adstxt]
- The account ID "must contain the same value used in transactions", typically `publisher.id` in OpenRTB.[^adstxt]
- **OWNERDOMAIN** (recommended since version 1.1, finalised July 2022 and listed by IAB Tech Lab as August 2022) names the business
  that owns the site and should equal the `domain` that publisher has in sellers.json files. **MANAGERDOMAIN** (optional) names an
  exclusive or primary monetisation partner such as a sales house, at most one per country; for impressions the manager sells,
  the specification expects its domain on the first node of a complete SupplyChain object.[^adstxt][^adstxt-page]
- The file must be served as `text/plain`. Redirects are followed only within the root domain plus one hop to a third party;
  consumers should cache for 7 days if no cache headers are set.[^adstxt]
- On a 404, "the advertising system can assume no declarations exist and that no advertising system is unauthorized", so a
  missing file authorises everyone; after any other error, crawlers keep the last file they retrieved successfully.[^adstxt]
- A site that authorises nobody publishes a placeholder record rather than an empty file, so crawlers can tell it apart from an
  error page.[^adstxt]

**Why it matters, in one number.** Google and White Ops reported that 80.3% of the bid requests the 3ve fraud operation
generated in early 2018 came from sellers not listed in the spoofed domain's ads.txt; with universal enforcement, they wrote, over
80% of 3ve's inventory "would be completely blocked" (see [Scenario 1](#scenario-1-3ve-and-the-value-of-enforced-adstxt-documented)).[^3ve]

**What ads.txt cannot do.** It does not name the account holder (sellers.json does), it does not list every intermediary (the
SupplyChain object does), and it cannot stop bot traffic sold through a real, authorised account.

### sellers.json and the SupplyChain object: who was paid along the way

**sellers.json** is published by an SSP or exchange at `/sellers.json` on its own domain. For every `seller_id` it says who is
paid and in what capacity:[^sellers]

- `seller_type` **PUBLISHER**: the named entity owns the site or app and is paid directly; **INTERMEDIARY**: it does not own
  the inventory or is not paid directly; **BOTH**: both kinds flow through the account.
- `seller_id` is "the same ID that appears in an ads.txt file and in the SupplyChain.nodes array sid property"; each ID must map
  to a single paid entity; `is_confidential` may hide the name.

**The SupplyChain object** (often "schain") travels inside each bid request and lists the entities "involved in the direct flow of
payment" for that impression.[^schain] The rules are simple:

- The originating seller creates it with `complete` = 1 and itself as the only node.
- A reseller copies the object, keeps `complete` as it was, and appends its own node; a reseller that receives no object creates one
  with `complete` = 0.
- In a complete chain, the first node is the owner's system and seller ID, and the last node is the entity sending this request.
- It sits in `source.schain` in OpenRTB 2.6 and later, `source.ext.schain` in 2.5.[^schain]

Both were finalised on 31 July 2019; IAB Tech Lab tells buyers to keep buying through ads.txt-authorised paths.[^sellers-page]

::: figure How a buyer checks one bid request against the public files
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 120, "nodeSpacing": 14, "rankSpacing": 20, "padding": 8}}}%%
flowchart TB
  BR["Bid request: domain or app, publisher.id, schain"] --> A1{"Seller account listed in the domain's ads.txt?"}
  A1 -- "no" --> X1["Unauthorised: do not bid"]
  A1 -- "yes" --> A2{"schain complete = 1?"}
  A2 -- "no" --> X2["Incomplete chain: lower trust or skip"]
  A2 -- "yes" --> A3{"Every node found in that system's sellers.json?"}
  A3 -- "no" --> X3["Unknown intermediary: skip or investigate"]
  A3 -- "yes" --> A4{"First node = OWNER- or MANAGERDOMAIN?"}
  A4 -- "no" --> X4["Resold or mislabelled: lower priority or review"]
  A4 -- "yes" --> OK["Verifiable path: bid on its merits"]
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  class BR,OK core
  class A1,A2,A3,A4 decision
  class X1,X2,X3,X4 risk
```
Text equivalent: Four checks on a bid request. Is the seller account in the domain's ads.txt? If not, do not bid. Is the chain
complete? If not, trust is lower. Is every node in its system's sellers.json? If not, an unknown intermediary is involved. Does
the first node's sellers.json domain equal the publisher's OWNERDOMAIN, or the MANAGERDOMAIN declared for the user's country? If
not, treat the path as resold or mislabelled: lower its priority or review it. If all pass, the path is verifiable and the buyer
bids on the impression's merits.
:::

The fourth check combines two rules in ads.txt v1.1. In complete chains the first node "should have sellers.domain that matches
the OWNERDOMAIN"; but for "ad opportunities monetized by the manager", the specification expects the MANAGERDOMAIN to be that of
the node representing the originating publisher.[^adstxt] The 1.1 implementation guide turns this into a rule of thumb for
the seller's sellers.json domain: equal to OWNERDOMAIN, the seller is the owner; equal to MANAGERDOMAIN (for that country), the
manager; anything else, a reseller. It also leaves the exact interpretation of MANAGERDOMAIN to each buyer.[^adstxt-guide]
Buyers apply these checks with different strictness, so publishers should make all four pass for the paths they rely on.

::: tip The publisher's side of the same checks
Before adding any partner, ask for its exact ads.txt line, find that seller ID in the partner's sellers.json, and confirm the
`seller_type` matches the relationship you are declaring (DIRECT lines usually point to PUBLISHER entries with your domain; a
declared manager's entries carry its MANAGERDOMAIN). Lab 2 automates the comparison.
:::

### Consent and privacy signals on ad requests

- **IAB Europe's Transparency and Consent Framework (TCF)** is "an accountability tool that relies on standardisation to facilitate
  compliance with certain provisions of the ePrivacy Directive and the GDPR". Version 2.3 (April 2025) made the "Disclosed Vendors"
  section mandatory; "TCF participants have until 28 February 2026 to adopt TCF v2.3".[^tcf]
- **Global Privacy Protocol (GPP)**, from IAB Tech Lab, carries IAB Europe TCF, IAB Canada TCF, US national and US state strings
  in one container; an MSPA-alignment update was in public comment until 11 September 2026, so check its final status.[^gpp]
  OpenRTB 2.6 carries them in `regs.gdpr` and `regs.gpp`.[^ortb]
- **Google's requirement.** AdSense, Ad Manager and AdMob publishers need a Google-certified CMP integrated with the TCF to serve
  personalised ads in the EEA and UK (from 16 January 2024) and Switzerland (from 31 July 2024); other traffic may get only
  non-personalised or limited ads. Google "does not check CMPs for full compliance with the TCF or applicable privacy laws".[^gam-cmp]
- **Header bidding.** Prebid splits the job. Its TCF consent management module reads the consent data and passes it to
  adapters (and is not legal advice).[^pb-tcf] Enforcement needs the separate, optional **TCF control module** (`tcfControl`, in
  the build), which "Actively restricts" user syncs, ID modules, bidders and other activities according to consent, requiring a
  legal basis for Purposes 1 and 2 by default.[^pb-tcfcontrol] The consent module's page names TCF v2.0 and v2.2; check that
  your CMP and Prebid version handle the v2.3 strings users now send.
- **Children.** Google does not send requests marked child-directed under the US COPPA law to Open Bidding partners;[^gam-ob]
  check each partner's handling of the flag.

The legal analysis (lawful basis, notices, retention) belongs to [PrivacyOps](../privacyops/); the CMP and tag-firing architecture
to [MarTechOps](../martechops/).

### Invalid traffic and ad fraud

The Media Rating Council (MRC) separates invalid traffic (IVT) into two categories:[^mrc-ivt]

- **General invalid traffic (GIVT)**: "traffic identified through routine means of filtration executed through application of lists
  or with other standardized parameter checks", such as known invalid data-centre traffic and declared bots and spiders.
- **Sophisticated invalid traffic (SIVT)**: "more difficult to detect situations that require advanced analytics, multi-point
  corroboration/coordination, significant human intervention", including domain and app misrepresentation and domain
  laundering; an April 2024 MRC memo adds checks of declared against actual property IDs, including CTV bundle IDs.[^mrc-ivt]

Controls by layer:

| Layer | Control | Source |
|---|---|---|
| Lists (GIVT) | IAB Tech Lab Spiders and Robots list, used in a "dual pass" (valid browsers, then known robots); TAG's monthly Data Center IP List | [^spiders][^tag] |
| Certification | TAG Certified Against Fraud: 100% of monetisable transactions filtered for IVT by a qualified third party; publishers may meet this through a CAF-certified SSP | [^tag] |
| Supply-chain files | ads.txt / app-ads.txt enforcement, sellers.json and schain checks | [^adstxt][^sellers][^schain] |
| Measurement | Accredited IVT detection and filtration; SIVT checks compare declared property IDs with where ads actually ran | [^mrc-ivt] |
| Behavioural heuristics | Google and White Ops: if the fraud rate falls but click-through rate does not, "it's likely that your solution isn't quite working correctly" | [^3ve] |

::: warning Malicious creatives are an ad ops incident, not only a security one
A creative that redirects users or loads hostile scripts is delivered through your ad server, on your domain. Pause first
(creative, line item, partner), capture evidence, then investigate with [SecOps](../secops/) and report it to the demand partner.
:::

### Counting rules: impressions and viewability

- **Impression.** The IAB/MRC display guideline requires counting "as late as possible": "The ad must be loaded and at minimum
  begin to render"; "count on decision" at the server is not acceptable, and the ad need not be visible.[^mrc-display] Google Ad Manager states that it "counts an impression each time a creative is
  downloaded and has begun to load".[^gam-count]
- **Viewable display impression.** At least 50% of the ad's pixels on an in-focus browser tab in the viewable space, for at least
  one continuous second after render; for ads of 242,500 pixels or more (970×250 and larger), 30% of pixels.[^mrc-view]
- **Viewable video impression.** 50% of pixels and 2 continuous seconds of play.[^mrc-view]
- **Reporting.** Disclose viewable, non-viewable and undetermined, and compute measured and viewable rates separately
  ([metrics](#metrics-and-kpis)); MRC warns against calling the viewable share of all impressions the "viewable rate".[^mrc-view]
  The 2015 guideline is for desktop; MRC's mobile guidance followed in June 2016.[^mrc-list]

## Practices, frameworks and tools

### Core practices and playbooks

| Practice | Goal | Trigger | Owner | Output | Verification |
|---|---|---|---|---|---|
| Trafficking QA | Ads render, track and target as sold | Launch; creative swap | Trafficker | QA record | Test-page evidence |
| Pacing management | Goals met without free over-delivery | Daily | Trafficker | Exceptions list | 95–110% at flight end |
| Header-bidding tuning | Competition without latency loss | Monthly; new partner | Yield mgr + engineer | Timeout, bidder settings | Revenue per session |
| Floors and yield | Price without starving fill | Weekly | Yield mgr | Pricing-rule notes | Revenue per request before/after |
| ads.txt hygiene | Only current, correctly labelled sellers | Partner change; monthly | Yield mgr + engineer | Versioned ads.txt | Lab 2 check passes |
| Discrepancy control | Counts agree within tolerance | Daily while live | Trafficker + finance | Discrepancy log | Gap within tolerance |
| Creative security | No malicious or non-compliant ads | New creative; alerts | Trafficker + SecOps | Blocks, partner reports | No repeat offenders |
| Ad experience | Ads readers tolerate | Layout change; quarterly | Ad ops lead + product | Ad-density rules | Page speed, complaints |

#### Playbook: trafficking QA before launch

1. Compare line items to the IO line by line: dates and time zone, goal and type, rate, targeting, frequency cap, sizes,
   competitive separation.
2. Preview each creative: right size, secure (HTTPS) calls, correct click-through URL.
3. Confirm every tracker fires once (impression, click, viewability, verification); a click tracker without an impression
   tracker causes "clicks but no impressions".[^gam-count]
4. Use the ad server's matching creative type for third-party ad-server tags (in Google Ad Manager, the Campaign Manager 360 tag
   type: under 2% discrepancy expected instead of up to 20%).[^gam-disc]
5. Record the evidence, launch, and re-check delivery and trackers within the first hour.

#### Playbook: daily pacing

Lab 1 computes pacing and the run rate needed. For Standard line items "Delivery pacing is adjusted to meet the defined goal",
Google notes,[^gam-li] but no ad server creates inventory the targeting excludes. When a line item is behind:

1. Check eligibility (status, dates, approved creatives, targeting that matches real inventory) with the Troubleshoot tab and
   Delivery Inspector.[^gam-unfilled][^gam-count]
2. Check competition: higher-priority line items or sponsorships on the same inventory; over-tight frequency caps.
3. Check the creative: a third-party creative that fails to load may count on one side only.
4. Decide with the account manager: widen targeting, extend the flight, raise priority within policy, or plan a make-good.

#### Playbook: header-bidding set-up and tuning

- **Line items.** Each price bucket needs a matching line item. Prebid rounds bids down into buckets passed in `hb_pb`; rounding
  affects only the ad-server auction, not the price paid. "Dense" needs 425 line items per bidder, "high" 2,001. For custom
  buckets, set the cap near your 95th-percentile bid, with finer increments where bids cluster.[^pb-gran]
- **Send all bids or top bid.** Sending all bids is Prebid's default and helps reporting on every bidder; sending only the top bid
  needs far fewer line items.[^pb-sendall]
- **Timeouts.** Prebid calls the auction timeout "a delicate balance": too short loses bids, too long loses users before the ad
  call. It suggests a server-side timeout of about 50–75% of the auction timeout and measuring how long consent and identity
  modules take before bids are sent.[^pb-timeout] Consent waits count: with `actionTimeout` set, Prebid waits up to that long
  for a new user's choice before the auction starts.[^pb-tcf]
- **Supply chain.** If a managed-service provider runs your wrapper and handles payments, it must add its own schain node; since
  Prebid 10 the schain is treated as first-party data in `ortb2.source.ext.schain`.[^pb-schain]

#### Playbook: floors and yield

In Google Ad Manager (as of October 2026), pricing rules centralise floors for non-guaranteed demand, the higher of two
overlapping floors applies, floors apply to the publisher's net after Google's share, and publishers "can set floors for available
demand sources or specific programmatic bidders".[^gam-pricing] Change one rule at a time on a slice of inventory and compare
revenue per thousand requests, not eCPM alone (see the [worked calculation](#metrics-and-kpis)). Expect floor behaviour to change
when the US remedies take effect (see [advanced topics](#advanced-topics-and-emerging-direction)).

### Methods, standards and frameworks

| Source | Body | Kind | What it governs |
|---|---|---|---|
| ads.txt 1.1, app-ads.txt | IAB Tech Lab | Specification (voluntary; enforced by many platforms) | Authorised sellers[^adstxt][^appads] |
| sellers.json 1.0, SupplyChain object 1.0 | IAB Tech Lab | Technical specification | Seller identity; payment chain[^sellers][^schain] |
| OpenRTB 2.6 | IAB Tech Lab | Protocol specification | Bid requests and responses, notices, deals[^ortb] |
| VAST 4.x, Open Measurement SDK | IAB Tech Lab | Specification / SDK | Video ad serving; measurement in apps and CTV[^vast][^omsdk] |
| Impression, viewability, IVT, brand-safety guidelines | MRC with IAB | Measurement guidelines; basis of accreditation | What counts, is viewable, is invalid, is safe[^mrc-display][^mrc-view][^mrc-ivt][^mrc-bs] |
| Certified Against Fraud | TAG | Certification programme | Anti-fraud controls[^tag] |
| TCF v2.3 | IAB Europe | Framework (Google requires it for personalised ads in EEA/UK/CH) | Consent signalling[^tcf][^gam-cmp] |
| GPP | IAB Tech Lab | Protocol | Multi-jurisdiction privacy strings[^gpp] |
| Standard Terms and Conditions v3.0 | 4A's and IAB | Contract template (2010; often renegotiated) | Controlling counts, discrepancies, make-goods[^tandc] |
| Better Ads Standards | Coalition for Better Ads | Industry standard based on consumer research | Ad experiences users reject[^betterads] |
| Digital Ad Operations Certification | IAB | Professional certification | Individual competence[^iab-cert] |

None of these is law; laws apply on their own terms, and frameworks help show compliance without guaranteeing it.[^gam-cmp]

### Tooling landscape

| Tool category | Examples (named in this page's sources) | Selection criteria | Lock-in and risks |
|---|---|---|---|
| Publisher ad server | Google Ad Manager[^gam-li] | Scale, programmatic integrations, reporting API, video | Deep lock-in; auction rules set by vendor and regulators[^ec] |
| Buy-side ad server | Campaign Manager 360[^gam-disc] | DSP and verification integration | Counts differ from publishers' |
| Header-bidding wrapper | Prebid.js, Prebid Server[^prebid-about] | Adapters, consent and schain modules, build control | Page weight; upgrades |
| SSP / exchange, DSP | Many vendors; check each SSP's sellers.json | Demand quality, fees, IVT controls, TAG status, path enforcement | Duplicate paths; opaque fees |
| Verification | MRC-accredited vendors | Accreditation scope (display, video, app, CTV) | Extra tags; a second count |
| Consent management | Google's certified CMP list[^gam-cmp] | TCF/GPP version support, performance | Signal errors block demand |
| Supply-chain checks | IAB Tech Lab's open-source crawler, which it calls "only an example prototype crawler", and parser[^adstxt-page]; Lab 2 | Coverage, alerting | Prototypes and unmaintained scripts give false comfort |
| Data and reporting | Governed metrics (see [AnalyticsOps](../analyticsops/)) | Log-level access, freshness | Metric drift between teams |

### Automation and AI

**Automation that pays for itself.** Bulk header-bidding line items (Lab 4); scheduled ads.txt and sellers.json checks (Lab 2);
daily pacing and discrepancy exceptions (Labs 1 and 3); alerts on jumps in fill, eCPM, timeouts or IVT; wrapper configuration
released like code ([ReleaseOps](../releaseops/)).

**Guard-rails.** Automation that writes to the ad server can change site-wide revenue in one run: require a dry run, a cap on
objects changed, an approver for priority and pricing changes, and a tested rollback.

**Where language models help, with limits.** Drafting a trafficking ticket from an emailed IO (a human checks every field);
summarising delivery anomalies; sorting creatives for review. Not approving creatives, changing floors or settling discrepancies
without a human. Standards for AI agents that trade inventory are emerging ([advanced topics](#advanced-topics-and-emerging-direction)).

## AdOps in practice

### Reference implementation: operating artefacts

For ad ops the reference implementation is a set of operating artefacts plus a little configuration, assuming Google Ad Manager
and Prebid.js; the ideas carry to other ad servers.

**1. A naming convention** that makes reports and bulk changes safe:

```text
Order:      <Advertiser>_<Agency>_<IO number>_<YYYYMM>
Line item:  <IO line>_<Type>_<Format>_<Size>_<Targeting>_<Start YYYYMMDD>
            0420-03_STD_DISPLAY_300x250_SPORTS-UK_20261101
Header bid: HB_<bidder|ALL>_<bucket>      e.g. HB_ALL_02.35
Creative:   <Advertiser>_<Concept>_<Size>_<v#>
```

**2. A trafficking intake form** that refuses incomplete orders. Keep it as structured data so a script can check it.

_Example status: syntax-checked (YAML), not executed against an ad server._

```yaml
io_number: "IO-2026-0420"
advertiser: "Example Outdoor Ltd"
agency: "Example Media Agency"
flight: {start: "2026-11-01", end: "2026-11-30", time_zone: "Europe/London"}
lines:
  - line: "0420-03"
    type: standard            # sponsorship | standard | price_priority | pg_deal
    goal_impressions: 1500000
    rate_cpm: 18.00
    sizes: ["300x250", "320x50"]
    targeting: {section: "sports", geo: ["GB"]}
    frequency_cap: {impressions: 3, per: "day"}
measurement:
  controlling_count: "third_party"   # per the IO terms
  third_party_ad_server: "Campaign Manager 360"
  discrepancy_tolerance_pct: 10
  viewability_vendor: "MRC-accredited vendor named in IO"
creative_due: "2026-10-27"
contacts: {sales: "sales@publisher.example", agency: "trafficking@agency.example"}
```

**3. A wrapper configuration** reviewed like code. Values are placeholders; the keys follow Prebid's documented `setConfig`
options (`bidderTimeout`, `priceGranularity`, `consentManagement`, first-party `ortb2` data carrying the schain), which are
unchanged in the Prebid.js 10 and 11 documentation (11 is the current major release as of October 2026; 10 is maintained as
"legacy").[^pb-setconfig][^pb-tcf][^pb-schain][^pb-npm] The configuration **passes** consent; Prebid **enforces** it only if the
build includes `tcfControl` (for example `gulp build --modules=consentManagementTcf,tcfControl,<bidders>`).[^pb-tcfcontrol]
Prebid 11 also adds a pre-auction wait for viewability measurement (`auctionOptions.viewabilityMeasurement`); include it in
latency tests.[^pb-setconfig]

_Example status: syntax-checked with `node --check`, not executed in a browser (requires a page with Prebid.js and an ad server)._

```javascript
// Prebid.js 10/11 configuration sketch for a wrapper run by a managed-service provider.
// Signal passing only: enforcement needs the tcfControl module in the build.
const pbjs = (globalThis.pbjs = globalThis.pbjs || { que: [] });
pbjs.que.push(() => {
  pbjs.setConfig({
    bidderTimeout: 1000, // measure first; see the timeouts playbook
    priceGranularity: 'dense', // 425 line items per bidder in the ad server
    consentManagement: {
      // timeout: wait for the CMP's first answer. No actionTimeout here: with one
      // (Prebid's example: 10000), the auction waits that long for a new user's choice.
      gdpr: { cmpApi: 'iab', timeout: 500, defaultGdprScope: true },
    },
    ortb2: {
      source: {
        ext: {
          schain: {
            ver: '1.0',
            complete: 1,
            nodes: [{ asi: 'wrapper-provider.example', sid: 'PUB-123', hp: 1 }],
          },
        },
      },
    },
  });
});
```

**4. A one-page QA checklist and a discrepancy log** (see [Checklists](#checklists) and [Templates](#templates)).

**5. A supply-partner register**: partner, ads.txt lines, sellers.json seller ID and type, contract owner, TAG status, last
review, decision; the source for ads.txt and the quarterly review.

### Hands-on labs

Four labs on **synthetic data**: Python 3.8 or later and a bash shell, standard library only, no accounts, no cost, no network
access. Domains end in `.example`, which is reserved for documentation. Each lab was executed on 2026-10-23 exactly as printed
(Python 3.8.10, Ubuntu Linux); the expected outputs are copied from those runs.

#### Lab 1 (beginner): media math and pacing

**Objective.** Calculate pacing, run rates, eCPM, CTR and the two MRC viewability rates for one guaranteed line item.
**Prerequisites.** Python 3.8+. **Time.** 15 minutes. **Risk.** None.

1. Create a folder: `mkdir -p ~/adops-labs/lab1 && cd ~/adops-labs/lab1`
2. Save the script as `pacing.py`.

_Example status: verified by execution on 2026-10-23 (Python 3.8.10)._

```python
"""Media math and pacing for one guaranteed line item (standard library only)."""
from datetime import date

GOAL = 1_500_000            # impressions sold on the insertion order
START, END = date(2026, 11, 1), date(2026, 11, 30)
TODAY = date(2026, 11, 13)  # report run in the morning: 12 full days delivered
DELIVERED = 520_000         # impressions counted by the ad server so far
CLICKS = 1_040
REVENUE = 9_360.00          # booked value of the delivered impressions (USD)
VIEWABLE, NON_VIEWABLE, UNDETERMINED = 286_000, 156_000, 78_000

flight_days = (END - START).days + 1
days_done = (TODAY - START).days
expected = GOAL * days_done / flight_days
pacing = DELIVERED / expected
remaining_days = flight_days - days_done
run_rate_needed = (GOAL - DELIVERED) / remaining_days
run_rate_so_far = DELIVERED / days_done

measured = VIEWABLE + NON_VIEWABLE
print(f"Flight {flight_days} days, {days_done} done, {remaining_days} left")
print(f"Expected to date      = {expected:,.0f} impressions")
print(f"Pacing                = {pacing:.1%}  (100% = on schedule)")
print(f"Daily run rate so far = {run_rate_so_far:,.0f}")
print(f"Daily run rate needed = {run_rate_needed:,.0f}")
print(f"eCPM                  = ${REVENUE / DELIVERED * 1000:.2f}")
print(f"CTR                   = {CLICKS / DELIVERED:.2%}")
print(f"Measured rate         = {measured / DELIVERED:.1%}")
print(f"Viewable rate         = {VIEWABLE / measured:.1%}")
print(f"Viewable share of all = {VIEWABLE / DELIVERED:.1%}  (not the viewable rate)")
```

3. Run `python3 pacing.py`.

**Expected output.**

```text
Flight 30 days, 12 done, 18 left
Expected to date      = 600,000 impressions
Pacing                = 86.7%  (100% = on schedule)
Daily run rate so far = 43,333
Daily run rate needed = 54,444
eCPM                  = $18.00
CTR                   = 0.20%
Measured rate         = 85.0%
Viewable rate         = 64.7%
Viewable share of all = 55.0%  (not the viewable rate)
```

**Validation.** By hand: 1,500,000 × 12 ÷ 30 = 600,000 expected; 520,000 ÷ 600,000 = 86.7%; (1,500,000 − 520,000) ÷ 18 = 54,444
per day, 26% above the current 43,333. Measured and viewable rates follow MRC's formulas; reporting 55.0% as "the viewable rate"
is the error MRC warns against.[^mrc-view] A 26% run-rate increase rarely happens by itself: start the
[pacing playbook](#playbook-daily-pacing). **Troubleshooting.** Wrong pacing when run in the evening: decide whether
`TODAY` counts as a delivered day and keep the convention. **Teardown.** `rm -rf ~/adops-labs/lab1`

#### Lab 2 (intermediate): lint ads.txt and cross-check sellers.json

**Objective.** Find syntax errors in an ads.txt file and relationship mismatches against SSPs' sellers.json entries.
**Prerequisites.** Python 3.8+, Lab 1 folder conventions. **Time.** 30 minutes. **Risk.** None; in real use, fetch only public
files and respect each site's crawl policies.

1. Create the folder and the two input files (an ads.txt and a combined extract of three SSPs' sellers.json files).

_Example status: verified by execution on 2026-10-23 (bash, Linux)._

```bash
mkdir -p ~/adops-labs/lab2 && cd ~/adops-labs/lab2
cat > ads.txt <<'EOF'
# ads.txt for news.example (synthetic lab data)
ssp-one.example, 1001, DIRECT, abc123
ssp-one.example, 2002, RESELLER
ssp-two.example, pub-77, DIRECT
ssp-two.example, pub-77, DIRECT
ssp-three.example, 9, PARTNER
reseller-x.example 55 RESELLER
ssp-four.example, 404, RESELLER
OWNERDOMAIN=news.example
CONTACT=adops@news.example
EOF
cat > sellers.json <<'EOF'
{
  "ssp-one.example": {"sellers": [
    {"seller_id": "1001", "seller_type": "PUBLISHER", "name": "News Example Ltd",
     "domain": "news.example"},
    {"seller_id": "2002", "seller_type": "INTERMEDIARY", "name": "Yield Partner Inc",
     "domain": "yieldpartner.example"}]},
  "ssp-two.example": {"sellers": [
    {"seller_id": "pub-77", "seller_type": "INTERMEDIARY", "name": "Yield Partner Inc",
     "domain": "yieldpartner.example"}]},
  "ssp-four.example": {"sellers": []}
}
EOF
```

2. Save the checker as `check_adstxt.py`.

_Example status: verified by execution on 2026-10-23 (Python 3.8.10)._

```python
"""Lint an ads.txt file and cross-check it against sellers.json extracts (stdlib only)."""
import json
import re

VALID_REL = {"DIRECT", "RESELLER"}
VAR = re.compile(r"^\s*([A-Za-z]+)\s*=\s*(.+?)\s*$")

def parse(path):
    records, variables, problems, seen = [], {}, [], set()
    with open(path, encoding="utf-8") as f:
        for n, raw in enumerate(f, 1):
            line = raw.split("#", 1)[0].strip()
            if not line:
                continue
            m = VAR.match(line)
            if m and "," not in line:
                variables.setdefault(m.group(1).upper(), []).append(m.group(2))
                continue
            fields = [x.strip() for x in line.split(",")]
            if len(fields) < 3:
                problems.append(f"line {n}: expected 3-4 comma-separated fields")
                continue
            domain, account, rel = fields[0].lower(), fields[1], fields[2].upper()
            if rel not in VALID_REL:
                problems.append(f"line {n}: relationship {fields[2]!r} is not "
                                "DIRECT or RESELLER")
                continue
            key = (domain, account, rel)
            if key in seen:
                problems.append(f"line {n}: duplicate record {domain}, {account}, {rel}")
                continue
            seen.add(key)
            records.append((n, domain, account, rel))
    return records, variables, problems

def cross_check(records, owner, sellers_by_system):
    findings = []
    for n, domain, account, rel in records:
        system = sellers_by_system.get(domain)
        if system is None:
            findings.append(f"line {n}: no sellers.json collected for {domain}")
            continue
        seller = next((s for s in system["sellers"] if s["seller_id"] == account), None)
        if seller is None:
            findings.append(f"line {n}: seller_id {account} not in {domain}/sellers.json")
            continue
        stype = seller["seller_type"].upper()
        if rel == "DIRECT" and (stype == "INTERMEDIARY" or seller.get("domain") != owner):
            findings.append(f"line {n}: DIRECT but sellers.json has {stype} "
                            f"{seller.get('domain')}")
        if rel == "RESELLER" and stype == "PUBLISHER":
            findings.append(f"line {n}: RESELLER but sellers.json says PUBLISHER")
    return findings

records, variables, problems = parse("ads.txt")
owner = (variables.get("OWNERDOMAIN") or ["(missing)"])[0].lower()
with open("sellers.json", encoding="utf-8") as f:
    sellers = json.load(f)
print(f"{len(records)} valid records; OWNERDOMAIN={owner}")
for p in problems:
    print("SYNTAX ", p)
for c in cross_check(records, owner, sellers):
    print("CHECK  ", c)
```

3. Run `python3 check_adstxt.py`.

**Expected output.**

```text
4 valid records; OWNERDOMAIN=news.example
SYNTAX  line 5: duplicate record ssp-two.example, pub-77, DIRECT
SYNTAX  line 6: relationship 'PARTNER' is not DIRECT or RESELLER
SYNTAX  line 7: expected 3-4 comma-separated fields
CHECK   line 4: DIRECT but sellers.json has INTERMEDIARY yieldpartner.example
CHECK   line 8: seller_id 404 not in ssp-four.example/sellers.json
```

**Validation.** Line 4 matters most: the publisher claims direct control, but the SSP says a yield partner is paid, so the line
is probably RESELLER (or the SSP's record is wrong; ask). Line 8 names an account the SSP does not list. Only DIRECT and RESELLER are defined today ("Other types may be added in the future"), so line 6 is invalid.[^adstxt]
The specification does not address duplicate records; they signal unmanaged edits. **Extension.** In real use, fetch each SSP's
live `/sellers.json`, cache it and alert on changes. **Troubleshooting.** `JSONDecodeError`: re-create the file; a quotation mark
was lost. **Teardown.** `rm -rf ~/adops-labs/lab2`

#### Lab 3 (intermediate): reconcile a discrepancy before invoicing

**Objective.** Compare a publisher's counts with the agency's third-party counts day by day and apply a 10% tolerance clause in
the style of the 4A's/IAB Standard Terms and Conditions v3.0.[^tandc] **Prerequisites.** Python 3.8+. **Time.** 20 minutes.
**Risk.** None; the script illustrates a clause, and your IO decides.

1. Create the folder and the data.

_Example status: verified by execution on 2026-10-23 (bash, Linux)._

```bash
mkdir -p ~/adops-labs/lab3 && cd ~/adops-labs/lab3
cat > delivery.csv <<'EOF'
date,publisher_imps,thirdparty_imps
2026-11-01,51000,47900
2026-11-02,50500,47300
2026-11-03,52000,44100
2026-11-04,49800,41200
2026-11-05,50700,42000
2026-11-06,51200,47600
2026-11-07,50300,46900
EOF
```

2. Save the script as `reconcile.py`.

_Example status: verified by execution on 2026-10-23 (Python 3.8.10)._

```python
"""Discrepancy check in the style of the 4A's/IAB Standard Terms v3.0 (illustrative)."""
import csv

CPM = 12.00          # agreed rate, USD
THRESHOLD = 0.10     # 10% over the invoice period, per the T&Cs pattern
CONTROLLING = "thirdparty_imps"   # assumed: agency's ad server is the controlling count
OTHER = "publisher_imps"

with open("delivery.csv", newline="") as f:
    rows = list(csv.DictReader(f))

for r in rows:
    ctrl, other = int(r[CONTROLLING]), int(r[OTHER])
    gap = (other - ctrl) / other
    flag = "  <-- investigate" if gap > THRESHOLD else ""
    print(f"{r['date']}  pub={other:>6}  3p={ctrl:>6}  gap={gap:6.1%}{flag}")

ctrl_total = sum(int(r[CONTROLLING]) for r in rows)
other_total = sum(int(r[OTHER]) for r in rows)
period_gap = (other_total - ctrl_total) / other_total
print(f"Period: publisher={other_total:,} third-party={ctrl_total:,} "
      f"gap={period_gap:.1%}")
if period_gap > THRESHOLD:
    adjusted = round(ctrl_total * (1 + THRESHOLD))
    print(f"Over {THRESHOLD:.0%}: reconcile first. If unresolved, the agency may")
    print("treat it as under-delivery (make-good) or pay the controlling")
    print("count plus a 10% upward adjustment:")
    print(f"  invoice on {adjusted:,} imps = ${adjusted / 1000 * CPM:,.2f}")
else:
    print(f"Within {THRESHOLD:.0%}: invoice on controlling count "
          f"{ctrl_total:,} imps = ${ctrl_total / 1000 * CPM:,.2f}")
```

3. Run `python3 reconcile.py`.

**Expected output.**

```text
2026-11-01  pub= 51000  3p= 47900  gap=  6.1%
2026-11-02  pub= 50500  3p= 47300  gap=  6.3%
2026-11-03  pub= 52000  3p= 44100  gap= 15.2%  <-- investigate
2026-11-04  pub= 49800  3p= 41200  gap= 17.3%  <-- investigate
2026-11-05  pub= 50700  3p= 42000  gap= 17.2%  <-- investigate
2026-11-06  pub= 51200  3p= 47600  gap=  7.0%
2026-11-07  pub= 50300  3p= 46900  gap=  6.8%
Period: publisher=355,500 third-party=317,000 gap=10.8%
Over 10%: reconcile first. If unresolved, the agency may
treat it as under-delivery (make-good) or pay the controlling
count plus a 10% upward adjustment:
  invoice on 348,700 imps = $4,184.40
```

**Validation.** Three consecutive days jump from about 6–7% to 15–17%: something ran only on 3–5 November (a creative, a
placement, a tag edit). **A detail the template
leaves open:** the clause compares "the Controlling Measurement and the
other measurement" without naming the denominator; the script divides by the publisher's count (10.8%), while dividing by the
controlling count gives 38,500 ÷ 317,000 = 12.1%. Write the formula into your IO. **The baseline is a finding too:** a steady 6–7%
is far above Google's expectation for the Campaign Manager 360 tag type (QA playbook step 4), so it gets its own ticket (runbook
step 8). **Troubleshooting.** `KeyError`: keep the CSV column names. **Teardown.** `rm -rf ~/adops-labs/lab3`

#### Lab 4 (advanced): price buckets, line-item counts and a SupplyChain check

**Objective.** Reproduce Prebid's rounding of bids into price buckets, count the line items a granularity needs, and check
SupplyChain objects for structural errors. **Prerequisites.** Python 3.8+; Labs 2 and 3. **Time.** 30 minutes. **Risk.** None.

1. Create the folder: `mkdir -p ~/adops-labs/lab4 && cd ~/adops-labs/lab4`
2. Save the script as `buckets.py`.

_Example status: verified by execution on 2026-10-23 (Python 3.8.10)._

```python
"""Header-bidding price buckets and a SupplyChain (schain) check (stdlib only)."""
from decimal import Decimal, ROUND_FLOOR

# Prebid's documented "dense" preset: (increment, cap) ranges in CPM.
DENSE = [(Decimal("0.01"), Decimal("3.00")), (Decimal("0.05"), Decimal("8.00")),
         (Decimal("0.50"), Decimal("20.00"))]

def bucket(cpm, ranges=DENSE):
    cpm = Decimal(str(cpm))
    top = ranges[-1][1]
    if cpm >= top:
        return top                      # bids above the cap fall to the cap bucket
    for inc, cap in ranges:
        if cpm < cap:
            return (cpm / inc).to_integral_value(rounding=ROUND_FLOOR) * inc
    return top

def line_items(ranges=DENSE):
    total, floor = 0, Decimal("0")
    for inc, cap in ranges:
        total += int((cap - floor) / inc) + (1 if floor == 0 else 0)
        floor = cap
    return total

for bid in [0.237, 2.999, 3.07, 7.99, 12.74, 31.0]:
    print(f"bid {bid:>6} -> hb_pb {bucket(bid)}")
print("line items per bidder (dense):", line_items())

def check_schain(sc, final_seller):
    errs = []
    if sc.get("ver") != "1.0":
        errs.append("ver should be '1.0'")
    nodes = sc.get("nodes") or []
    if not nodes:
        errs.append("nodes is empty")
    for i, n in enumerate(nodes):
        for k in ("asi", "sid", "hp"):
            if k not in n:
                errs.append(f"node {i}: missing {k}")
        if n.get("hp") not in (None, 1):
            errs.append(f"node {i}: hp should always be 1 in v1.0")
    if nodes and (nodes[-1].get("asi"), nodes[-1].get("sid")) != final_seller:
        errs.append("last node is not the system sending this bid request")
    if sc.get("complete") == 1 and len(nodes) > 1:
        errs.append("review: reseller in a complete chain; "
                    "node 0 = owner or manager?")
    return errs or ["ok"]

schain = {"ver": "1.0", "complete": 1, "nodes": [
    {"asi": "ssp-one.example", "sid": "1001", "hp": 1},
    {"asi": "reseller-x.example", "sid": "aaa", "hp": 1}]}
print("good chain:", check_schain(schain, ("reseller-x.example", "aaa")))
schain_bad = {"ver": "1.0", "complete": 0,
              "nodes": [{"asi": "ssp-two.example", "sid": "9"}]}
print("bad chain:", check_schain(schain_bad, ("ssp-two.example", "9")))
```

3. Run `python3 buckets.py`.

**Expected output.**

```text
bid  0.237 -> hb_pb 0.23
bid  2.999 -> hb_pb 2.99
bid   3.07 -> hb_pb 3.05
bid   7.99 -> hb_pb 7.95
bid  12.74 -> hb_pb 12.50
bid   31.0 -> hb_pb 20.00
line items per bidder (dense): 425
good chain: ['review: reseller in a complete chain; node 0 = owner or manager?']
bad chain: ['node 0: missing hp']
```

**Validation.** The count of 425 matches Prebid's published table for "dense" (301 + 100 + 24).[^pb-gran] A $12.74 bid enters the
ad-server auction as $12.50 and can lose to a $12.51 line item, yet is paid at $12.74 if it wins: Prebid notes that rounding
"does not impact the price paid, only the auction on the ad server".[^pb-gran] The bad chain is missing `hp`, which the
specification requires; for version 1.0 it "should always be 1".[^schain] The good chain is flagged because a complete chain with a
reseller should start with the owner or its declared manager (the fourth buyer check). **Extension.** Six bidders with "send all
bids" need 6 × 425 = 2,550 line items, against one set of 425 when only the top bid is sent.[^pb-sendall] **Troubleshooting.** `decimal.InvalidOperation`: a bid was passed as text with a currency sign; pass
numbers. **Teardown.** `rm -rf ~/adops-labs`

### Use cases and scenarios

Two documented cases (fraud and spend tracing) and two illustrative ones (a header-bidding migration and an app publisher with a
sales house); each is labelled.

#### Scenario 1: 3ve and the value of enforced ads.txt (documented)

**Problem.** 3ve, analysed by Google and White Ops in November 2018, controlled over a million IP addresses, produced more than
10,000 counterfeit domains and, at its peak, between 3 and 12 billion or more bid requests a day.[^3ve] The takedown involved
Google, the FBI and White Ops, and the US Department of Justice unsealed a 13-count indictment against eight people, as reported
by The Hacker News.[^thn] **What ad ops learns.** Most of 3ve's requests failed the ads.txt check (the 80.3% figure above); the
authors also recommend scepticism of fraud metrics that look too good, and layered detection.[^3ve] **Trade-off.** Strict
enforcement also blocks legitimate supply with a stale ads.txt, so keep yours current.
**Pitfall.** Bots laundered through an authorised account pass the check; IVT detection still matters.

#### Scenario 2: tracing programmatic spend (documented)

**Problem.** In a 2020 UK study by ISBA, the Association of Online Publishers and PwC, 15% of programmatic spend could not be
attributed (the "unknown delta"); only 31 million of 267 million impressions could be matched from DSP to SSP, a 12% match rate.
**Method and result.** The follow-up (January 2023, covering 2022) used log-level data from all vendors: the match rate rose to
58%, the unknown delta fell to 3%, and 65% of spend reached media owners, up from 51%, as reported by VideoWeek.[^isba] **Lesson for ad ops.** Cross-company reconciliation needs shared identifiers and log-level data; without them a
gap cannot even be classified as fee, waste or fraud.

#### Scenario 3: from waterfall to header bidding (illustrative)

::: example Illustrative scenario: a news site adds a header-bidding wrapper
**Context.** A news site sells 40% of impressions direct and offers the rest to SSPs one after another (a "waterfall").
Prebid's answer: "Instead of offering impressions to one partner at a time, header bidding lets all partners bid
simultaneously."[^prebid-about] **Plan.** Five bidders; "send all bids" for a month, then top bid only; dense granularity
(5 × 425 = 2,125 scripted line items); timeouts of 800, 1,000 and 1,200 ms on equal traffic splits; consent and TCF control
modules in the build. **Success criteria.** Revenue per thousand sessions up at least 10% with no worse page-speed metrics
(agreed with [WebOps](../webops/)); guaranteed campaigns still pacing 95–110%. **Trade-offs.** More page work, data sharing,
line items and invoices. **Pitfall.** Header-bidding line items above guaranteed priority starve guaranteed campaigns.
:::

#### Scenario 4: an app publisher monetised by a sales house (illustrative; rules documented)

::: example Illustrative scenario: app-ads.txt with a management partner
**Context.** A games developer publishes three mobile apps and one CTV app and outsources all programmatic sales outside its home
country to a sales house. **Method.** The developer lists its website in each store listing; buyers find `app-ads.txt` on that
site.[^appads] It lists its own DIRECT accounts, the sales house's accounts, `OWNERDOMAIN=` its business domain, and
`MANAGERDOMAIN=` the sales house's domain with a country code for each market the partner manages exclusively (one manager per
country), as ads.txt v1.1 allows.[^adstxt] **Success.** In the managed markets, the first node of a complete chain carries either
the MANAGERDOMAIN (the expectation the specification states) or, where the sales house runs its own selling system and lists the
developer there as PUBLISHER, the developer's OWNERDOMAIN (the implementation guide's cases B and C walk through the ads.txt
lines, sellers.json entries and chains);[^adstxt-guide] both pass the buyer's fourth check. Lab 2's checks pass monthly. **Pitfalls.** The guide recommends MANAGERDOMAIN only
when one entity manages most of the inventory, and warns that buyers will likely treat other accounts selling the same inventory
as RESELLER paths.[^adstxt-guide] Changing the developer URL in a store listing moves the file buyers read; plan it like a domain
migration.
:::

## Measure, govern, pay

### Metrics and KPIs

| KPI | Definition and formula | Data source | Pitfall | Cadence |
|---|---|---|---|---|
| Pacing | Delivered ÷ expected to date (expected = goal × elapsed ÷ flight length) | Ad server | Even pacing assumed; adjust for planned weighting | Daily |
| Run rate needed | (Goal − delivered) ÷ days remaining | Ad server | Ignores inventory limits; compare with forecast avails | Daily |
| Fill rate | Impressions ÷ ad requests | Ad server | Separate intentional blanks from unfilled requests[^gam-unfilled] | Daily |
| eCPM | Revenue ÷ impressions × 1000[^daoc] | Ad server, SSP reports | Rises when fill falls; pair with revenue per 1,000 requests | Daily / weekly |
| Revenue per 1,000 requests or sessions | Revenue ÷ requests (or sessions) × 1000 | Ad server, analytics | Session definition must be stable | Weekly |
| CTR | Clicks ÷ impressions × 100[^daoc] | Ad server | Should fall when IVT filtering improves[^3ve] | Weekly |
| Measured rate | (Viewable + non-viewable) ÷ rendered impressions[^mrc-view] | Verification vendor | A low measured rate makes the viewable rate unreliable | Weekly |
| Viewable rate | Viewable ÷ (viewable + non-viewable)[^mrc-view] | Verification vendor | Not the same as viewable ÷ all impressions | Weekly |
| IVT rate | IVT impressions ÷ total measured impressions (state GIVT, SIVT or both) | Verification vendor, SSP | Vendors differ in SIVT methods; compare like with like[^mrc-ivt] | Weekly |
| Discrepancy | (Count A − count B) ÷ the denominator named in the contract | Both ad servers | Small volumes give large percentages[^gam-disc] | Daily while live |
| Bid rate / win rate / timeout rate | Bids ÷ requests; wins ÷ bids; timed-out requests ÷ requests, per bidder | Wrapper analytics | Timeouts cluster by geography and pre-auction modules[^pb-timeout] | Weekly |
| Authorised-supply share | Requests from ads.txt-authorised paths ÷ all requests for your domain | Buyer or SSP reports | Needs partners to share data | Monthly |

**Worked calculation (illustrative numbers).** A site serves 10,000,000 ad requests in a week; 8,800,000 are filled; revenue is
$17,600. Fill rate = 88%; eCPM = $17,600 ÷ 8,800,000 × 1000 = $2.00; revenue per 1,000 requests = $1.76. After a floor increase,
7,000,000 impressions earn $16,100: eCPM rises to $2.30, but revenue per 1,000 requests falls to $1.61, an 8.5% loss on the same
traffic. Revenue per request answers the business question; eCPM does not.

::: figure From ad request to billable, viewable impression: where volume is lost
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 140, "nodeSpacing": 12, "rankSpacing": 18, "padding": 8}}}%%
flowchart TB
  R["Ad requests: 10,000,000"] --> F["Filled impressions: 8,800,000 (fill 88%)"]
  F --> V["Valid after IVT filtering"]
  V --> M["Measured for viewability (measured rate)"]
  M --> VW["Viewable impressions (viewable rate)"]
  R -. "unfilled: no eligible ad" .-> U["Unfilled impressions"]
  F -. "invalid traffic removed" .-> I["GIVT and SIVT"]
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  class R,F,V,M,VW core
  class U,I risk
```
Text equivalent: A vertical funnel. Ten million ad requests lead to 8.8 million filled impressions (88% fill); the rest are
unfilled because no eligible ad existed. Filled impressions are filtered for general and sophisticated invalid traffic. Valid
impressions that a verification vendor could measure determine the measured rate, and those found in view determine the viewable
rate. Each step loses volume, and each step has its own denominator.
:::

**Targets.** Set targets from your own baseline and contract terms, not from industry averages: pacing 95–110% at flight end for
guaranteed lines (an Atlas suggestion), discrepancy within the IO tolerance, IVT and viewability thresholds as written in buyers'
terms. **Vanity metrics.** eCPM alone, "impressions served" without IVT filtering, and viewability without the measured rate.

### Quality, security, privacy and compliance

| Risk | Likelihood / impact | Signal | Mitigation | Owner |
|---|---|---|---|---|
| Malicious or non-secure creative | Medium / high (users harmed on your domain) | User reports, scanner alerts, redirects | Scanning, blocklists, pause first | Ad ops + SecOps |
| Unauthorised resellers of your inventory | Medium / high | Buyer reports of spoofed supply | Accurate ads.txt, OWNERDOMAIN | Yield mgr |
| Consent signal missing, wrong or not enforced | Medium / high (regulatory) | Personalised bids drop; CMP errors | Certified CMP, `tcfControl`, tests per release | Ad tech eng. + privacy owner |
| Child-directed inventory sent to ineligible partners | Low / high | Audit of request flags | Tag child-directed sections; check partner handling[^gam-ob] | Ad ops lead |
| Billing on invalid traffic | Medium / medium | IVT rate above contract | Accredited measurement, TAG-certified paths[^tag] | Ad ops + finance |
| Over-sharing user data with bidders | Medium / high | Data-flow review findings | Limit bidders, review adapters and ID modules | Privacy owner |
| Ad server access misuse | Low / high | Unexpected bulk changes | Least-privilege roles, MFA, change log review | Ad ops lead |

**Brand safety and suitability.** Buyers set avoidance categories and, as MRC puts it, "different tolerance levels for risk
depending on brand suitability". MRC's verification supplement separates property-level classification (a whole site or app)
from content-level classification (the page, video or post), and requires vendors to disclose when they classify by URL keywords
alone, which is not content-level measurement.[^mrc-bs] For a publisher, a keyword or domain match is therefore a weaker signal
than it looks: ask buyers which level their vendor measures before accepting a block, and leave the policy (which categories,
which tolerance) to the advertiser's [MarketingOps](../marketingops/) owners.

**Least privilege.** Separate ad-server roles for trafficking, yield, administration and reporting; automation credentials
reach only the objects they change. **Evidence.** Keep QA records, discrepancy logs, IOs and invoices as finance rules require,
and ads.txt history in version control. **Jurisdiction.** Legal questions belong to [PrivacyOps](../privacyops/) and
[LegalOps](../legalops/).

### Cost, capacity and sustainability

- **Intermediary fees.** Every hop takes a share. The ANA's programmatic work centres on made-for-advertising sites and
  streamlining supply-chain transaction costs;[^ana] ad ops helps by removing paths that add no unique demand.
- **People.** Trafficking scales with IOs and creatives, not impressions; automate bulk work before hiring.
- **Line items.** Granularity × bidders × formats multiplies objects (Lab 4); check your ad server's limits first.
- **Latency.** Each bidder and module delays the ad call; Prebid treats the timeout as a revenue trade-off.[^pb-timeout]
- **Sustainability.** Fewer redundant bid requests mean less compute; no source opened here quantifies it
  ([GreenOps](../greenops/)).

### Assurance of the operating model

- **Leading indicators:** complete IOs at intake; QA first-pass rate; time to traffic; ads.txt check results; discrepancy
  baselines against vendor expectations.
- **Lagging indicators:** make-good rate; disputed invoices; revenue per 1,000 requests; IVT rate; incidents by severity.
- **Independent checks:** MRC-accredited measurement for billing; TAG certification where buyers require it; an annual audit of
  ad-server access, change logs and ads.txt history.
- **Feedback loop:** a post-mortem with owned actions for every Sev 1 or Sev 2 incident.[^daoc]

## When things go wrong

### Common failures and troubleshooting

| Symptom | Likely cause | Diagnosis | Remediation and prevention |
|---|---|---|---|
| Guaranteed line item serves nothing | Paused, creative not approved, wrong dates or time zone, targeting matches no inventory | Troubleshoot tab, Delivery Inspector[^gam-unfilled][^gam-count] | Fix; add an "eligible to serve" QA check |
| Programmatic revenue drops site-wide overnight | ads.txt rewritten without a partner's line, wrong content type or off-domain redirect; CMP failure; wrapper error; new pricing rule | `curl -sI https://<your-domain>/ads.txt`, diff with the last version; change log; consent test | Roll back the last change; alert on ads.txt content and headers. (A deleted file authorises everyone under the specification;[^adstxt] buyers' own policies may differ) |
| Header bids never win | No line item matches `hb_pb`; wrapper granularity differs from line-item prices; wrong key names | Compare page bucket values with line-item targeting | Generate line items from the wrapper's granularity config[^pb-gran] |
| High bidder timeout rate | Slow partners, distant data centres, long consent or identity waits | Timeout rate by bidder and region; pre-auction time[^pb-timeout] | Adjust timeout; drop or deprioritise slow bidders; shorten module waits |
| Fill falls after a yield change | Floor above most bids; overlapping rules (the higher applies)[^gam-pricing] | Before/after fill and revenue per 1,000 requests | Roll back; test floors on a slice |
| Clicks but few impressions in third-party report | Click tracker trafficked without an impression tracker | Creative preview; tracker log[^gam-count] | Re-traffic with both trackers; QA both |
| Discrepancy above tolerance (or a steady one above the vendor's expectation) | Creative type, latency, ad blockers, passbacks, third-party targeting or caps[^gam-disc] | Daily comparison; find the change date | See the worked runbook below |
| Low viewable rate | Below-the-fold slots, slow loading, or a low measured rate | Check measured rate first, then viewability by slot[^mrc-view] | Fix measurement coverage, then layout and lazy loading |
| Users report redirects | Malicious creative from a demand partner | Capture creative and chain; identify partner and buyer | Pause, block, report; tighten creative scanning |
| Buyer reports your domain sold by an unknown seller | Spoofing or a stale reseller line | Check ads.txt history and the seller's sellers.json entry | Remove stale lines; publish OWNERDOMAIN; ask buyer to block the path |

### Worked runbook: under-delivery and a 15% lower buyer count

::: example Illustrative incident, using the numbers from Labs 1 and 3
On 13 November a Standard line item (goal 1,500,000, 1–30 November) is pacing at 86.7% (Lab 1), and the agency's ad server
counted 10.8% fewer impressions than the publisher in 1–7 November, with 15–17% gaps on 3–5 November (Lab 3). Another creative
rotation is due on 15 November.
:::

::: figure Runbook flow for a campaign that is behind and disputed
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 130, "nodeSpacing": 14, "rankSpacing": 20, "padding": 8}}}%%
flowchart TB
  T["Alert: pacing below 95% and gap above tolerance"] --> S["Set severity; notify account manager"]
  S --> B{"Same dates, time zone, line items; unfilled excluded?"}
  B -- "no" --> R1["Re-run reports correctly"]
  R1 --> B
  B -- "yes" --> C["Find the change date in daily data"]
  C --> L["Check change log and creatives for that date"]
  L --> FX["Fix: re-traffic creative with correct type and trackers"]
  FX --> P{"Run rate needed achievable?"}
  P -- "yes" --> MON["Monitor daily until gap and pacing recover"]
  P -- "no" --> OPT["Agree option with client: extend, widen, make-good"]
  OPT --> MON
  MON --> PM["Post-mortem: update QA checklist"]
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  classDef risk fill:#fbeceb,stroke:#b4332b,color:#122029
  class T,R1 risk
  class B,P decision
  class S,C,L,FX,MON,OPT,PM core
```
Text equivalent: After the alert, set severity and notify the account manager. Confirm both reports cover the same dates, time
zone and line items without unfilled impressions (re-run if not). Find the change date, check the change log and creatives, fix
the cause. If the needed run rate is achievable, monitor; if not, agree an option with the client first. Finish with a
post-mortem that updates the QA checklist.
:::

1. **Triage (first 30 minutes).** Severity 2: a guaranteed campaign is at risk and a dispute is forming. Tell the account manager
   now; investigate while the line item still runs.[^gam-disc]
2. **Rule out report errors.** Same dates and time zone, same line items, unfilled impressions excluded.[^gam-disc]
3. **Find the change dates.** Lab 3 shows a steady 6–7% gap on 1–2 and 6–7 November and 15–17% on 3–5 November. Something ran
   only on those three days. Do not treat 6–7% as normal: it is a second, older problem (step 8).
4. **Check the change log.** A second creative version ran from 3 to 5 November. It was trafficked as a generic third-party
   creative instead of the Campaign Manager 360 tag type (expected gaps: [QA playbook](#playbook-trafficking-qa-before-launch) step 4). A
   test page also shows that tag calling another ad server (a passback chain), which increases the expected discrepancy.
5. **Fix and verify before it recurs.** Traffic the 15 November rotation with the correct creative type and the agency's current
   tag; check on a test page that the publisher's and the agency's impression trackers each fire once; confirm the next day's gap
   falls back to at most the old 6–7%.
6. **Recover delivery.** Lab 1 needs 54,444 impressions a day against 43,333 so far. If the forecast is short, offer the client
   a written choice (wider geography, a three-day extension, a make-good); never silently raise the line item above other
   guaranteed commitments.
7. **Settle the money.** The three bad days push the week over a 10% tolerance. Share the daily evidence and propose the IO's
   remedy (in the 4A's/IAB template: reconcile, then a make-good or payment on the controlling count plus 10%).[^tandc]
8. **Post-mortem.** Root cause: a creative swap without full QA. Actions: creative changes use the launch checklist; the daily
   report flags step changes in the gap. Follow-up ticket for the 6–7% baseline (step 3): check that
   creative v1 uses the Campaign Manager 360 tag type, then compare geography, third-party targeting and frequency-cap settings
   with the agency.

### Anti-patterns and myths

| Anti-pattern or myth | Why it fails | Warning sign | Do instead |
|---|---|---|---|
| "eCPM went up, so yield went up" | Higher floors raise eCPM and lower fill | Revenue per 1,000 requests falls | Report revenue per request alongside eCPM |
| Adding every reseller line a partner sends | Duplicate paths, unknown intermediaries, buyer distrust | ads.txt grows every quarter; few lines remove | Partner register; require sellers.json evidence; remove unused lines |
| "Our ad server's count is the truth" | The contract names the controlling count[^tandc] | Arguments at month end | Agree counting terms at intake |
| Calling viewable ÷ all impressions the "viewable rate" | MRC defines it over measured impressions only[^mrc-view] | Viewability looks worse than vendors report | Report measured rate and viewable rate |
| Header-bidding line items above guaranteed priority | Guaranteed campaigns starve | Make-goods rise after a wrapper launch | Keep header bids at non-guaranteed priority |
| Fixing discrepancies at month end | Evidence is gone; fewer options[^gam-disc] | Disputes only at invoice time | Daily gap check |
| Accepting a steady discrepancy as "normal" | A constant gap can hide a set-up fault (wrong creative type, targeting or caps)[^gam-disc] | Same gap on every campaign with one partner | Compare with the vendor's documented expectation |
| Switching off consent handling to "recover revenue" | Regulatory exposure; Google limits ads from non-certified CMPs[^gam-cmp] | Personalised demand returns but legal review is skipped | Fix the CMP integration; involve the privacy owner |
| "ads.txt stops fraud" | It stops unauthorised sellers, not bots through authorised accounts[^3ve] | IVT rises with clean ads.txt | Layer IVT detection on top |

## Deciding and adopting

### When to use and when not to use

| Situation | Recommended approach | Why |
|---|---|---|
| Small site, one ad network that sells and serves | **No ad ops team:** one owner, the network's ads.txt line, a CMP, a monthly check | The network traffics; you keep the file, signal and trend |
| Growing publisher, several SSPs, occasional direct deals | **Part-time owner plus a managed service** for header bidding | Prebid notes many companies hire managed services for header bidding[^prebid-hb] |
| Publisher with a direct sales team, guaranteed campaigns, several formats | **In-house ad ops** (trafficking, yield, ad tech engineering) | Contracts, make-goods and reconciliation need daily owners |
| Agency or large advertiser buying across many publishers | **Buy-side ad ops** (trafficking, verification, reconciliation) | Every placement adds tags, counts and invoices to check |
| Advertiser buying only through self-serve platforms (social, search) | **Marketing operations, not ad ops** | The platform runs delivery; see [MarketingOps](../marketingops/) |

**When not to build an ad ops function.** When one network serves a minor revenue line well; when you cannot staff daily
monitoring (a half-run header-bidding stack can do worse than a simple, well-kept one); or when the business is leaving
advertising. Hidden costs: partner contracts, page-performance work, privacy reviews of each bidder, out-of-hours incidents.

**Exit criteria from a managed service.** Bring work in-house when you can name owners for wrapper releases, ads.txt, consent
integration and reconciliation, and when the managed-service fee exceeds the cost of those people plus tooling.

### Decision framework

::: figure Choosing an operating model for advertising
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 120, "nodeSpacing": 14, "rankSpacing": 20, "padding": 8}}}%%
flowchart TB
  Q1{"Do you sell your own ad inventory?"}
  Q1 -- "no, we only buy" --> Q5{"Buying outside self-serve platforms at scale?"}
  Q5 -- "no" --> MKT["MarketingOps covers it"]
  Q5 -- "yes" --> BUY["Buy-side ad ops: trafficking, verification, reconciliation"]
  Q1 -- "yes" --> Q2{"Direct sales with guaranteed campaigns?"}
  Q2 -- "yes" --> INH["In-house ad ops and yield team"]
  Q2 -- "no" --> Q3{"More than one or two programmatic partners?"}
  Q3 -- "no" --> OWN["One owner and monthly checks; the network delivers"]
  Q3 -- "yes" --> Q4{"Can you staff daily monitoring and engineering?"}
  Q4 -- "yes" --> INH
  Q4 -- "no" --> MAN["Managed service plus accountable owner"]
  classDef decision fill:#fdf3e3,stroke:#c9800f,color:#122029
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  class Q1,Q2,Q3,Q4,Q5 decision
  class INH,BUY,MAN core
  class OWN,MKT neutral
```
Text equivalent: Buyers only: MarketingOps covers self-serve buying; buying beyond it at scale needs buy-side ad ops. Sellers with
direct sales and guaranteed campaigns need an in-house ad ops and yield team. Without them, one or two programmatic partners need
only one owner and monthly checks; more partners need an in-house team if you can staff daily monitoring and engineering,
otherwise a managed service with an accountable internal owner.
:::

For the build-or-buy choice inside the "managed service" branch, score each option from 1 to 5 against weighted criteria. The
weights below are an Atlas suggestion for a mid-sized publisher; change them to match your situation.

| Criterion | Weight | In-house | Managed service |
|---|---|---|---|
| Control over partners, floors and data sharing | 25% | 5 | 3 |
| Speed to a working, monitored stack | 20% | 2 | 5 |
| Ongoing cost at current revenue | 20% | 2 | 4 |
| Transparency of fees and supply paths | 20% | 5 | 3 |
| Resilience to staff turnover | 15% | 2 | 4 |
| **Weighted score** | 100% | **3.35** | **3.75** |

Calculation: in-house = 0.25×5 + 0.20×2 + 0.20×2 + 0.20×5 + 0.15×2 = 3.35; managed = 0.25×3 + 0.20×5 + 0.20×4 + 0.20×3 + 0.15×4 =
3.75. A publisher that weights control and transparency at 35% each would reach the opposite answer; the weights are the decision.

### Maturity model

This is **the Atlas's own maturity model**, not an industry standard; use it to find the next step.

| Level | Observable behaviour and evidence | Exit criteria to the next level |
|---|---|---|
| 1. Ad hoc | Set-up by whoever is free; ads.txt edited when a partner asks; no QA records; month-end disputes | Named owner; intake form; QA checklist in use |
| 2. Repeatable | Standard trafficking and QA records; daily pacing report; ads.txt history in version control | Discrepancy log; partner register; consent tested |
| 3. Defined | RACI; change tickets for priorities, floors and wrapper; monthly ads.txt/sellers.json check | Weekly revenue per request and IVT; post-mortems |
| 4. Measured | KPIs with targets; quarterly supply-path decisions; accredited measurement for billing | Guarded automation; tested rollbacks |
| 5. Optimising | Experiment log for floors, timeouts, partners; incidents feed checklists; falling dispute rates | Sustain; revisit after major market or regulatory change |

::: figure Maturity progression and the artefact that unlocks each step
```mermaid
%%{init: {"flowchart": {"wrappingWidth": 110, "nodeSpacing": 12, "rankSpacing": 18, "padding": 8}}}%%
flowchart TB
  L1["1 Ad hoc"] -- "owner, intake form, QA checklist" --> L2["2 Repeatable"]
  L2 -- "discrepancy log, partner register" --> L3["3 Defined"]
  L3 -- "KPIs with targets, post-mortems" --> L4["4 Measured"]
  L4 -- "guarded automation, experiments" --> L5["5 Optimising"]
  classDef neutral fill:#f1f4f6,stroke:#5b7a87,color:#122029
  classDef core fill:#e8f3f2,stroke:#16756f,color:#122029
  class L1,L2 neutral
  class L3,L4,L5 core
```
Text equivalent: Five levels in sequence; each arrow names what unlocks the next: owner, intake form and QA checklist (to
Repeatable); discrepancy log and partner register (Defined); KPIs with targets and post-mortems (Measured); guarded automation
and experiments (Optimising).
:::

### Adoption roadmap: 30/60/90 days

For a publisher moving from level 1 or 2 to level 3. Adapt the order to your biggest pain.

| Window | Actions | Owner | Milestone and metric |
|---|---|---|---|
| Days 1–30 | Name the owner; partner register; ads.txt in version control with the Lab 2 check; intake form and QA checklist; daily pacing report | Ad ops lead | QA record for every new campaign; no unknown seller IDs |
| Days 31–60 | Counting terms in the IO template; discrepancy log for third-party-served campaigns, baselines compared with vendor expectations; consent tests per release; change control | Ad ops lead, finance, ad tech eng. | No dispute without a daily log; no untracked pricing-rule change |
| Days 61–90 | Weekly revenue per 1,000 requests, IVT, measured and viewable rates; first partner review; first post-mortem; scripted header-bidding line items if relevant | Yield mgr, ad ops lead | KPI pack in use; one supply path removed or justified; level-3 self-assessment |

**Dependencies:** report access, a privacy owner, finance, engineering time. **Quick wins:** fix ads.txt errors; remove a dead
partner; add the measured rate to viewability reports.

## Toolkit

### Checklists

**Quick start (new owner, first week)**

- [ ] I can log in to the ad server with my own named account and the right role
- [ ] I have the list of every demand partner, its contract owner and its ads.txt line
- [ ] `/ads.txt` (and `app-ads.txt` for apps) returns 200, `text/plain`, and includes OWNERDOMAIN
- [ ] I know which CMP runs, whether Google certifies it, which TCF/GPP strings it sends, and whether Prebid enforces them
- [ ] I can produce yesterday's impressions, fill, eCPM and revenue per 1,000 requests

**Pre-launch QA (every campaign and every creative change):** the five steps of the
[trafficking QA playbook](#playbook-trafficking-qa-before-launch), plus counting terms (controlling count, tolerance,
denominator) recorded in the ticket.

**Header-bidding release**

- [ ] Wrapper version and modules recorded; configuration diffed and reviewed
- [ ] Price granularity in the wrapper equals the line-item price set
- [ ] Timeout tested on a traffic split; consent and identity module wait times measured
- [ ] schain configured if a managed service handles payments
- [ ] `tcfControl` in the build if Prebid must enforce consent; rules reviewed with the privacy owner
- [ ] Rollback build available and tested

**Monthly governance**

- [ ] ads.txt / app-ads.txt checked against partners' sellers.json (Lab 2); unknown or mislabelled lines fixed
- [ ] Discrepancy log reviewed; open disputes have owners
- [ ] IVT, measured rate and viewable rate reviewed by partner and placement
- [ ] Ad-server user list reviewed; leavers removed; API keys rotated as policy requires

**Incident (revenue drop or malicious ad)**

- [ ] Severity set; account managers and finance informed
- [ ] Last changes listed (pricing rules, priorities, wrapper, ads.txt, CMP, site release)
- [ ] For malicious ads: creative paused and evidence captured before investigation
- [ ] Post-mortem scheduled with actions and owners

### Templates

**Discrepancy log entry**

```text
Campaign / IO:        IO-2026-0420 line 0420-03
Parties and counts:   Publisher ad server vs agency ad server (controlling)
Period:               2026-11-01 to 2026-11-07
Tolerance in IO:      10% (denominator: publisher count)
Daily gap baseline:   6-7% (above the <2% expected for CM360 tags: ticket <id>)
Exception days:       03-05 Nov (15-17%)
Checks done:          dates/time zone, same line items, unfilled excluded, creative type
Root cause:           creative v2 trafficked as generic third-party tag with passback
Action and owner:     re-traffic v3 as CM360 tag type (trafficker), due 2026-11-14
Commercial outcome:   invoice 348,700 (controlling 317,000 + 10%); agreed <date>
```

**Supply-partner review (one per partner per quarter)**

```text
Partner:              <SSP or reseller name>     Contract owner: <name>
ads.txt line(s):      <domain, account, DIRECT|RESELLER>
sellers.json entry:   seller_id <id>, domain <...>
                      seller_type <PUBLISHER|INTERMEDIARY|BOTH>
TAG status:           <certified programmes, date checked>
Last 90 days:         revenue share <%>, unique demand <% of its wins not seen via others>
                      bid rate <%>, timeout rate <%>, IVT rate <%>
Data sharing:         user IDs and modules enabled for this partner
Decision:             keep | renegotiate | remove (by <date>)   Approved by: <name>
```

**Post-incident review (one page)**

```text
Title / severity:     <what happened>  Sev <1-3>
Impact:               revenue <amount or %>, campaigns affected, users affected
Timeline (UTC):       detected / mitigated / resolved
Root cause:           <technical cause> and <process gap that allowed it>
What went well:       <...>
Actions:              <action> - <owner> - <due date>   (one line each)
Checklist updated:    yes/no, which item
```

### Deep comparisons

**Sell-side versus buy-side:** the sell side optimises yield (revenue per request, fill, sell-through), publishes ads.txt and
disputes under-delivery; the buy side optimises delivery to plan (reach, viewability, IVT, cost), enforces ads.txt, sellers.json
and schain, and disputes invalid or non-viewable impressions. Neighbouring Ops are compared in
[how AdOps relates to other Ops](#how-adops-relates-to-other-ops).

**Direct versus programmatic versus header bidding versus server-side**

| Approach | Strength | Weakness | Choose when |
|---|---|---|---|
| Direct guaranteed | Price and relationship control | Sales and trafficking effort | A sales team and distinctive audiences |
| One SSP | Simple | Less competition | Small sites |
| Client-side header bidding | Competition, visible bids | Page work, latency, line items | Web, with engineering support |
| Server-side bidding | Less page work | Fewer browser signals; operator dependence | Apps, CTV, latency-sensitive pages |

### FAQ

**Is AdOps the same as programmatic trading?** No. Traders decide what to buy and at what price in a DSP; ad ops sets up and
verifies the systems those trades flow through. In small teams one person does both.

**Do I need ads.txt if I only use one ad network?** Yes. Publish that network's line exactly as it documents it; buyers' platforms
check the file. A site that authorises nobody should publish the placeholder record.[^adstxt]

**Why do our numbers never match the agency's?** Counting points, IVT filters, geography, latency, ad blockers and passbacks
differ; Google calls discrepancies "common and expected".[^gam-disc] The 10% tolerance often quoted is a clause in the 4A's/IAB
template, not a standard;[^tandc] your IO decides.

**If Chrome keeps third-party cookies, can we ignore privacy changes?** No. Chrome kept user choice over third-party
cookies,[^sandbox] but consent requirements such as Google's own certified-CMP rule for the EEA, UK and Switzerland still
apply;[^gam-cmp] the legal analysis is in [PrivacyOps](../privacyops/).

**Should our header-bidding partner add reseller lines to our ads.txt?** Only lines you can trace to a contract and a sellers.json
entry, bringing demand you do not reach otherwise. Each line tells buyers the path is yours.

## Going further

### Advanced topics and emerging direction

**Established capability, still changing:**

- **Connected TV.** Ad pods, server-side insertion and the 2024 VAST CTV addendum are covered in
  [video and CTV ad operations](#video-and-ctv-ad-operations).[^vast]
- **Privacy signals.** GPP keeps adding US state sections;[^gpp] test signals on every CMP or wrapper upgrade.
- **Browser change.** On 17 October 2025 Google announced the retirement of Topics, Protected Audience, Attribution Reporting and
  other Privacy Sandbox technologies, while Chrome keeps third-party cookie choice.[^sandbox] Decommission integrations built for
  the retired APIs.

**Regulatory change with operational consequences:**

- The European Commission fined Google €2.95 billion in September 2025 for favouring AdX through DFP and its buying tools (for
  example, telling AdX the best competing bid in advance) and ordered it to end the self-preferencing.[^ec]
- In the United States, a federal court held in April 2025 that Google monopolised open-web digital advertising markets.[^doj]
  On 2 September 2026 the judge rejected a divestiture of AdX and ordered "most of the parties' proposed behavioral remedies";[^adex]
  her opinion was unsealed by court order on 16 September,[^vaed-docket][^adex-unseal] and in early October the parties filed their proposed final judgments
  in one document, disputed language in brackets. AdExchanger reported "no major disagreements on the core remedies":[^adex-pfj]
  - **Agreed:** AdX connected to Prebid; AdX answering bid requests from rival publisher ad servers on the terms it gives DFP; no
    reimplementation of first or last look; unified pricing rules, "which prevent publishers from setting different price floors
    for different buyers", added to the banned tactics; Google Ads bidding through AdX or Prebid, not directly into DFP; separate
    DFP and AdX contracts; export of DFP configuration and historical bid data within three months of the order taking effect.
  - **Disputed:** six or twelve months for the integrations; global effect at once or up to 24 extra months abroad; when
    publishers get their DFP data; the six-year monitor's powers.
  - **Timing:** nothing takes effect until 60 days after the judge signs an order; AdExchanger expects Google to appeal.

  **No signed final judgment had been reported by 23 October 2026;** the public docket copy lists none.[^vaed-docket] Check it
  before changing configurations: integrations, floors and data exports in the dominant publisher ad server will change on
  its schedule.

**Active research and vendor-led change (treat with care):**

- **AI agents in buying and selling.** IAB Tech Lab released an agentic real-time framework (ARTF) for public comment in November
  2025 and grouped its agentic work under the name AAMP in March 2026, with an agent registry, as summarised by IAB UK.[^iabuk]
  These are emerging standards; the operational questions (approval, logging, rollback) are those of any automation that writes
  to the ad server.
- **Attention metrics.** MRC issued Attention Measurement Guidelines in November 2025;[^mrc-list] treat them as additional
  signals, not replacements for impressions and viewability in contracts.

### Learning path

| Stage | Goal | Do this |
|---|---|---|
| Prerequisites | Read basic HTML and JavaScript; use spreadsheets fluently | A short web-fundamentals course |
| 1-day primer | Explain how one impression is decided and counted | Read the first three parts; draw the sequence diagram from memory |
| 1 week | Traffic and check like a professional | Labs 1 and 3; your own QA checklist; Google's discrepancy article[^gam-disc] |
| 30 days | Own supply-chain trust | Lab 2 on a public ads.txt and its partners' sellers.json; the three specifications[^adstxt][^sellers][^schain] |
| Advanced | Yield and header bidding, buy-side verification, or CTV | Lab 4; Prebid's timeout and granularity docs;[^pb-timeout][^pb-gran] MRC guidelines;[^mrc-view][^mrc-ivt] VAST 4.3[^vast43] |
| Credential (optional) | Show breadth | IAB Digital Ad Operations Certification[^iab-cert] |

**Portfolio projects.** (1) An ads.txt monitor that alerts on content-type, redirect and seller changes. (2) A discrepancy
dashboard that flags step changes and baselines above vendor expectations. (3) A timeout experiment write-up with revenue per
session and page-speed results. (4) A partner review of a public ads.txt from public files only.

## Wrap-up

### Executive recap and cheat sheet

| Topic | Remember |
|---|---|
| Three records | Promised (IO, deal) · decided (ad server, auctions) · counted (impressions, viewability, IVT, money) |
| Video and CTV | Know the insertion mode (`ssai`) · pod floors per second · where impressions are counted |
| Trust files | ads.txt / app-ads.txt (who may sell) · sellers.json (who is paid) · schain (every paid hop in this request) |
| Counting | Impression = loaded and began to render · viewable display = 50% pixels, 1 s (video 2 s) · viewable rate = V ÷ (V + NV) |
| Discrepancy | Normal up to the vendor's documented level; agree controlling count, tolerance and denominator in the IO |
| Header bidding | Ad server still decides · buckets need matching line items · timeout is a revenue trade-off |
| Consent | Pass TCF/GPP strings; enforce in the wrapper (`tcfControl`); certified CMP for Google in EEA, UK, Switzerland |
| Fraud | GIVT by lists, SIVT by analysis · ads.txt stops unauthorised sellers, not bots through real accounts |
| Watch-outs | eCPM without revenue per request · stale reseller lines · header bids above guaranteed priority · month-end reconciliation |
| Next actions | Owner and RACI · ads.txt in version control and checked monthly · daily pacing and discrepancy report · partner register |

### A judgement call: should we cut our ads.txt reseller lines from 60 to 15?

::: decision Dilemma: supply-path consolidation versus short-term revenue
**Situation (illustrative).** A publisher's ads.txt has 12 DIRECT and 60 RESELLER lines. A large agency's buyers prefer short,
verifiable paths; the yield manager proposes keeping 15 reseller lines; the CFO fears losing revenue in a weak quarter.

**Option A: keep all 60.** More paths can mean more bidders and higher clearing prices; nothing changes operationally.
**Option B: cut to 15.** Fewer intermediaries, simpler reconciliation, cleaner signals to buyers who score supply paths.

**Assumptions to make explicit.** Many reseller paths carry the same demand as your direct accounts; some carry demand you reach
nowhere else; buyers already skip some paths. None of this is known until measured.

**Decision process.** For 30 days, measure per reseller path: revenue, unique demand (wins where no direct path also bid), IVT
rate, and clean sellers.json and schain entries (Labs 2 and 4). Remove lines in three waves, worst first, watching site-level
revenue per 1,000 requests after each; keep a rollback list.

**Why the attractive choices can be wrong.** Keeping everything looks safe, but duplicate paths split the same buyers across
intermediaries that each take a fee, and unknown sellers weaken the signal your file sends. A round number looks decisive, but a
path with unique demand in one region or format can be worth more than its average suggests. **Uncertainty.** Buyers' path
scoring is not public, so any benefit of Option B appears slowly. **Recommendation.** Option B, staged and measured, with the
number set by the data rather than fixed at 15.
:::

### Learning objectives and mastery check

You should now be able to **explain** how one impression is decided, counted and billed; **check** ads.txt, sellers.json and
schain; **calculate** pacing, eCPM, measured and viewable rates; **investigate** a discrepancy with a runbook; **choose** an
operating model; and **evaluate** trade-offs such as floors versus fill.

::: answer 1. A line item has delivered 300,000 of 900,000 impressions on day 10 of a 30-day flight. Is it on pace, and what daily run rate is needed?
Expected to date = 900,000 × 10 ÷ 30 = 300,000, so pacing is 100%. Run rate needed = 600,000 ÷ 20 = 30,000 a day, equal to the
run rate so far (Lab 1 method).
:::

::: answer 2. A vendor reports 2,000,000 rendered impressions: 900,000 viewable, 600,000 non-viewable, 500,000 undetermined. What are the measured rate and viewable rate?
Measured rate = (900,000 + 600,000) ÷ 2,000,000 = 75%. Viewable rate = 900,000 ÷ 1,500,000 = 60%. Calling 45% (900,000 ÷ 2,000,000)
the viewable rate is the error MRC warns against.
:::

::: answer 3. Your ads.txt says "ssp.example, 555, DIRECT", but ssp.example's sellers.json lists 555 as INTERMEDIARY with a sales house's domain. What do you do?
Either the label is wrong (the sales house controls the account, so the line should be RESELLER) or the SSP's record is wrong. Ask
the SSP and the sales house, correct whichever is wrong, and record the decision in the partner register. Declaring the sales
house as MANAGERDOMAIN does not change the label: its SSP account is still a RESELLER line. Buyers may treat the
mismatch as a mislabelled path (Lab 2).
:::

::: answer 4. Why can a header bid of $4.38 lose in the ad server to a $4.36 line item when using 0.05 increments?
The bid enters the ad-server auction as the $4.35 bucket, so a $4.36 line item outranks it. Had it won, it would have been paid
$4.38: rounding affects only the ad-server auction (Lab 4).
:::

::: answer 5. Raising floors increased eCPM from $2.00 to $2.30 and fill fell from 88% to 70% on the same 10 million requests. Did revenue rise?
No: $17,600 before, $16,100 after, an 8.5% fall (the [worked calculation](#metrics-and-kpis)). Judge yield by revenue per request.
:::

::: answer 6. A guaranteed campaign is behind pace right after a header-bidding launch. Which setting do you check first?
The priority of the header-bidding line items. If they sit above guaranteed priority, they take impressions meant for the
guaranteed line items. In Google Ad Manager, header bids belong in non-guaranteed types such as Price priority (priority 12).
:::

::: answer 7. Your EEA traffic's personalised demand collapsed after a CMP change. What do you check?
Whether the new CMP is on Google's certified list (required for personalised ads in the EEA, UK and Switzerland), whether it emits
the current TCF string your partners read, whether Prebid's consent module receives it before its timeout, and, if the build
includes `tcfControl`, whether bidders are now being excluded because the new CMP's vendor list lacks them. Involve the privacy
owner before any workaround.
:::

### Conclusion

AdOps is old enough to have a certification and young enough to be rewritten by every new auction model, privacy rule and court
remedy. What lasts is the core: three records (promised, decided, counted) that must agree, and a supply chain that anyone who
pays can check. The specifications are short and public; the discipline is keeping them true every day. Most failures here are
not exotic: a stale reseller line, a creative swapped without QA, a floor raised without watching fill, a "normal" discrepancy
nobody questioned. And when one network and a well-kept ads.txt are enough, choosing that is good operations, not a lack of ambition.

## Sources and further reading

All sources were opened and checked on 2026-10-23 (evidence ledger: `research/adops-ledger.md`). Product behaviour and
regulatory status change often; re-check them.

**Role and market**

[^iab-cert]: [IAB Certification](https://www.iab.com/certification/) — Interactive Advertising Bureau, live page. Checked 2026-10-23.
[^daoc]: [IAB Digital Ad Operations Certification Study Guide](https://www.iab.com/wp-content/uploads/2017/10/IAB-Digital-Ad-Operations-Certification-Study-Guide-August-2017.pdf) — IAB, August 2017 (PDF). Checked 2026-10-23.
[^iab-mobile]: [Mobile Discrepancies: Exploring Common Root Causes](https://www.iab.com/wp-content/uploads/2015/07/IABMobileDiscrepancies.pdf) — IAB Mobile Ad Ops Working Group, September 2013 (PDF). Checked 2026-10-23.
[^prebid-hb]: [Introduction to Header Bidding](https://docs.prebid.org/overview/intro-to-header-bidding.html) — Prebid.org documentation. Checked 2026-10-23.
[^prebid-about]: [About Prebid](https://prebid.org/about/) — Prebid.org. Checked 2026-10-23.
[^iab-rev]: [Digital Ad Revenue Climbs to Nearly $300B as IAB Celebrates 30 Year Anniversary](https://www.iab.com/news/digital-ad-revenue-climbs-to-nearly-300b-as-iab-celebrates-30-year-anniversary/) — IAB press release, 2026-04-16. Checked 2026-10-23.
[^ana]: [ANA Programmatic Media Supply Chain Transparency Study (landing page)](https://ana.net/programmatic) — Association of National Advertisers, study issued December 2023. Checked 2026-10-23.
[^iab-rev-full]: [IAB/PwC Internet Advertising Revenue Report: Full-year 2025 results](https://iabhongkong.com/sites/default/files/2026-04/IAB_PwC_Internet_Ad_Revenue_Report_Full_Year_2025_April_2026.pdf) — IAB and PwC, April 2026 (PDF; full report read on an IAB Hong Kong mirror, survey definitions page). Checked 2026-10-23.
[^isba]: [Programmatic Advertising's "Unknown Delta" Drops to Three Percent in Second ISBA/PwC Study](https://videoweek.com/2023/01/18/programmatic-advertisings-unknown-delta-drops-to-three-percent-in-second-isba-pwc-study/) — VideoWeek, 2023-01-18. Checked 2026-10-23.

**Supply-chain specifications (IAB Tech Lab)**

[^adstxt-page]: [ads.txt – Authorized Digital Sellers](https://iabtechlab.com/ads-txt/) — IAB Tech Lab, updated 2026-09-14. Checked 2026-10-23.
[^adstxt]: [ads.txt Specification Version 1.1](https://github.com/InteractiveAdvertisingBureau/Supply-Chain-Validation/blob/main/ads.txt%20v1.1.md) — IAB Tech Lab (GitHub), final release July 2022 (listed on IAB Tech Lab's page as August 2022). Checked 2026-10-23.
[^adstxt-guide]: [ads.txt 1.1 Implementation Guide](https://iabtechlab.com/wp-content/uploads/2022/04/Ads.txt-1.1-Implementation-Guide.pdf) — IAB Tech Lab, released August 2022 (PDF). Checked 2026-10-23.
[^appads]: [Authorized Sellers for Apps (app-ads.txt)](https://github.com/InteractiveAdvertisingBureau/Supply-Chain-Validation/blob/main/app-ads.txt.md) — IAB Tech Lab (GitHub). Checked 2026-10-23.
[^sellers-page]: [sellers.json](https://iabtechlab.com/sellers-json/) — IAB Tech Lab, updated 2026-10-07. Checked 2026-10-23.
[^sellers]: [sellers.json specification](https://github.com/InteractiveAdvertisingBureau/Supply-Chain-Validation/blob/main/sellers-json.md) — IAB Tech Lab (GitHub), version 1.0, July 2019. Checked 2026-10-23.
[^schain]: [SupplyChain Object](https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md) — IAB Tech Lab (GitHub, OpenRTB). Checked 2026-10-23.
[^ortb]: [OpenRTB 2.6 specification](https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md) — IAB Tech Lab (GitHub). Checked 2026-10-23.
[^ortb-pods]: [OpenRTB 2.x implementation guide, 7.6 Pod Bidding for Video and Audio](https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/implementation.md) — IAB Tech Lab (GitHub). Checked 2026-10-23.
[^vast]: [VAST: Video Ad Serving Template](https://iabtechlab.com/standards/vast/) — IAB Tech Lab. Checked 2026-10-23.
[^vast43]: [VAST 4.3 specification](https://github.com/InteractiveAdvertisingBureau/VAST4.x/blob/main/4.3.md) — IAB Tech Lab (GitHub), sections 1.1.1–1.1.3. Checked 2026-10-23.
[^vmap]: [Digital Video Multiple Ad Playlist (VMAP)](https://iabtechlab.com/standards/video-multiple-ad-playlist-vmap/) — IAB Tech Lab. Checked 2026-10-23.
[^omsdk]: [Open Measurement SDK](https://iabtechlab.com/standards/open-measurement-sdk/) — IAB Tech Lab. Checked 2026-10-23.

**Measurement and invalid traffic**

[^mrc-display]: [Desktop Display Impression Measurement Guidelines (MMTF Final v1.1)](https://www.mediaratingcouncil.org/sites/default/files/Standards/Desktop-Display-Impression-Measurement-Guidelines-US%20%28MMTF%20Final%20v1.1%29.pdf) — IAB Tech Lab and MRC, October 2017 (PDF). Checked 2026-10-23.
[^mrc-view]: [MRC Viewable Ad Impression Measurement Guidelines, Version 2.0](https://www.mediaratingcouncil.org/sites/default/files/Standards/081815%20Viewable%20Ad%20Impression%20Guideline_v2.0_Final.pdf) — Media Rating Council with IAB, 2015-08-18 (PDF). Checked 2026-10-23.
[^mrc-list]: [Standards and Guidelines](https://www.mediaratingcouncil.org/standards-and-guidelines) — Media Rating Council (list of guidelines with issue dates). Checked 2026-10-23.
[^mrc-ivt]: [2024 IVT Interim Updates](https://www.mediaratingcouncil.org/sites/default/files/Standards/2024_IVT_Interim_Updates_FINAL.pdf) — Media Rating Council memo quoting the IVT 2.0 addendum, published 2024-04-24 (PDF). Checked 2026-10-23.
[^mrc-bs]: [MRC Supplement to IAB Guidelines for the Conduct of Ad Verification: Enhanced Content Level Context and Brand Safety](https://www.mediaratingcouncil.org/sites/default/files/Standards/MRC%20Ad%20Verification%20Supplement-%20Enhanced%20Content%20Level%20Context%20and%20Brand%20Safety%20%28Final%29.pdf) — MRC with 4A's, ANA and IAB, September 2018 (PDF). Checked 2026-10-23.
[^spiders]: [IAB Tech Lab Spiders and Robots List](https://iabtechlab.com/software/iababc-international-spiders-and-bots-list/) — IAB Tech Lab. Checked 2026-10-23.
[^tag]: [Fraud](https://www.tagtoday.net/fraud) — Trustworthy Accountability Group (TAG). Checked 2026-10-23.
[^betterads]: [Better Ads Standards](https://www.betterads.org/standards/) — Coalition for Better Ads. Checked 2026-10-23.
[^3ve]: [The Hunt for 3ve](https://services.google.com/fh/files/blogs/3ve_google_whiteops_whitepaper_final_nov_2018.pdf) — Google and White Ops, November 2018 (PDF). Checked 2026-10-23.
[^thn]: [Google and FBI shut down "3ve" ad-fraud operation](https://thehackernews.com/2018/11/3ve-ad-fraud-google.html) — The Hacker News (Swati Khandelwal), 2018-11-28. Checked 2026-10-23.

**Google Ad Manager (vendor documentation, as of October 2026)**

[^gam-li]: [Line item types and priorities](https://support.google.com/admanager/answer/177279?hl=en) — Google Ad Manager Help. Checked 2026-10-23.
[^gam-disc]: [Investigate report discrepancies](https://support.google.com/admanager/answer/6160380?hl=en) — Google Ad Manager Help. Checked 2026-10-23.
[^gam-count]: [Counting impressions and clicks](https://support.google.com/admanager/answer/2521337?hl=en) — Google Ad Manager Help. Checked 2026-10-23.
[^gam-unfilled]: [Troubleshoot unfilled impressions in Ad Manager](https://support.google.com/admanager/answer/1739296?hl=en) — Google Ad Manager Help. Checked 2026-10-23.
[^gam-pricing]: [Pricing rules](https://support.google.com/admanager/answer/9298211?hl=en) — Google Ad Manager Help. Checked 2026-10-23.
[^gam-ob]: [How Open Bidding works](https://support.google.com/admanager/answer/7128958?hl=en) — Google Ad Manager Help. Checked 2026-10-23.
[^gam-cmp]: [Google consent management requirements for serving ads in the EEA, the UK, and Switzerland (for publishers)](https://support.google.com/admanager/answer/13554116?hl=en) — Google Ad Manager Help. Checked 2026-10-23.

**Header bidding (Prebid.org documentation)**

[^pb-gran]: [Price Granularity](https://docs.prebid.org/adops/price-granularity.html) — Prebid.org. Checked 2026-10-23.
[^pb-sendall]: [Send All Bids vs Top Price](https://docs.prebid.org/adops/send-all-vs-top-price.html) — Prebid.org. Checked 2026-10-23.
[^pb-timeout]: [Prebid Timeouts](https://docs.prebid.org/features/timeouts.html) — Prebid.org. Checked 2026-10-23.
[^pb-schain]: [Supply Chain Object Module](https://docs.prebid.org/dev-docs/modules/schain.html) — Prebid.org. Checked 2026-10-23.
[^pb-tcf]: [Consent Management – TCF](https://docs.prebid.org/dev-docs/modules/consentManagementTcf.html) — Prebid.org. Checked 2026-10-23.
[^pb-tcfcontrol]: [TCF Control Module](https://docs.prebid.org/dev-docs/modules/tcfControl.html) — Prebid.org. Checked 2026-10-23.
[^pb-npm]: [prebid.js package metadata (dist-tags)](https://registry.npmjs.org/prebid.js) — npm registry: `latest` 11.43.0 (2026-10-19), `legacy-10.x` 10.29.2 (2026-10-06). Checked 2026-10-23.
[^pb-setconfig]: [pbjs.setConfig(options)](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html) — Prebid.org Publisher API reference. Checked 2026-10-23.

**Privacy signals and browsers**

[^tcf]: [Transparency & Consent Framework](https://iabeurope.eu/transparency-consent-framework/) — IAB Europe. Checked 2026-10-23.
[^gpp]: [Global Privacy Protocol](https://iabtechlab.com/gpp/) — IAB Tech Lab. Checked 2026-10-23.
[^sandbox]: [Update on Plans for Privacy Sandbox Technologies](https://privacysandbox.google.com/blog/update-on-plans-for-privacy-sandbox-technologies) — Google, 2025-10-17. Checked 2026-10-23.

**Contracts, competition law and emerging standards**

[^tandc]: [IAB/4A's Standard Terms and Conditions for Internet Advertising, Version 3.0](https://www.iab.com/wp-content/uploads/2015/06/IAB_4As-tsandcs-FINAL.pdf) — 4A's and IAB, 2010 (PDF). Checked 2026-10-23.
[^ec]: [Commission fines Google €2.95 billion over abusive practices in online advertising technology](https://ec.europa.eu/commission/presscorner/detail/en/ip_25_1992) — European Commission press release IP/25/1992, 2025-09-05. Checked 2026-10-23.
[^doj]: [Department of Justice Prevails in Landmark Antitrust Case Against Google](https://www.justice.gov/opa/pr/department-justice-prevails-landmark-antitrust-case-against-google) — US Department of Justice, 2025-04-17. Checked 2026-10-23.
[^adex]: [Google Won't Have To Break Up Its Ad Tech Business, Judge Brinkema Rules](https://www.adexchanger.com/antitrust/google-wont-have-to-break-up-its-ad-tech-business-judge-brinkema-rules/) — AdExchanger (Victoria McNally), 2026-09-02. Checked 2026-10-23.
[^adex-unseal]: [The Court Just Unsealed Judge Brinkema's Remedies Decision In The Google Ad Tech Antitrust Case](https://www.adexchanger.com/platforms/the-court-just-unsealed-judge-brinkemas-remedies-decision-in-the-google-ad-tech-antitrust-case-heres-your-tldr/) — AdExchanger (Allison Schiff), 2026-09-16. Checked 2026-10-23.
[^vaed-docket]: [United States v. Google LLC, 1:23-cv-00108 (E.D. Va.)](https://www.courtlistener.com/docket/66753787/united-states-v-google-llc/) — court docket, CourtListener RECAP copy: ECF 1860 (unsealing order, 2026-09-16); latest entry ECF 1861 (2026-10-02). Checked 2026-10-23.
[^adex-pfj]: [Google And The DOJ Filed Their Proposed Final Judgments In The Ad Tech Case – Here's What They're Still Arguing About](https://adexchanger.com/publishers/google-and-the-doj-filed-their-proposed-final-judgments-in-the-ad-tech-case-heres-what-theyre-still-arguing-about) — AdExchanger (Allison Schiff), 2026-10-05. Checked 2026-10-23.
[^iabuk]: [Is the future agentic? Understanding new standards for AI in digital advertising](https://www.iabuk.com/news-article/future-agentic-understanding-new-standards-ai-digital-advertising) — IAB UK, 2026-03-10. Checked 2026-10-23.

**Further reading:** [Basic Prebid.js example](https://docs.prebid.org/dev-docs/examples/basic-example.html), the official
minimal integration with Google Publisher Tag.

**Changelog.** Re-verify Google Ad Manager behaviour, the US remedies judgment and privacy-signal versions by 2027-01-23.

- 2026-10-23, first version (r0): research, evidence ledger, nine diagrams, four labs executed on synthetic data.
- 2026-10-23, r1 revision after independent review: MANAGERDOMAIN added to the buyer's fourth check and Scenario 4; US remedies
  updated to the October 2026 proposed final judgment; consent enforcement attributed to Prebid's `tcfControl`; runbook treats the
  6–7% baseline as a finding; new video and CTV section; brand-safety paragraph; glossary terms; Lab 4 re-run; repetition cut.
- 2026-10-23, post-score polish (`reviews/adops-polish.md`): unsealing date sourced to the court docket (16 September, ECF
  1860; no final judgment); discrepancy restatements cut.
