Blog · CTV

CTV runs at 65 percent of commercial capacity. Part of that gap is demand that could not read the request.

Omdia published Video Advertising KPI Benchmarks: Monetization on July 29. Ad-supported CTV could average $0.21 per hour of viewing if every available slot were sold; services are running at roughly 65 percent of commercial capacity and 32 percent of the regulatory ad capacity broadcast linear is permitted. The prescription is to grow the advertiser base to maximise fill rates and to improve advertising context protocols. The second half of that sentence is a bidstream statement, and it is the half nobody is going to write a trade headline about.

A fill rate cannot tell you why

Unfilled is one bucket in a seller's report and at least four different events in reality. Nobody wanted the impression at the floor. Somebody wanted it and ran out of time. Somebody wanted it and could not confirm who was selling it. Somebody wanted it and dropped the request before it ever reached a pricing model, because a field was missing, malformed, or carried a value their integration does not recognise.

Those are not the same problem and only one of them is solved by adding advertisers. OpenRTB has a mechanism for separating them: BidResponse.nbr, the no-bid reason code. It is optional, it is unevenly populated, and most exchanges do not pass it back to the seller in a form anyone reads. Where it is available, three values do most of the diagnostic work.

nbr  2   Invalid Request
         The bid request failed the bidder's own validation. This is
         demand that exists, refusing inventory it cannot parse.

nbr  6   Unsupported Device
         Often a device the buyer would happily buy, described in a way
         their device taxonomy does not resolve.

nbr  15  Insufficient Auction Time
         Not enough of tmax left by the time the request arrived. A path
         problem that reads as a demand problem in every report above it.

nbr  16  Incomplete SupplyChain
         schain.complete != 1. The buyer cannot establish authorisation,
         so the answer is no regardless of how much they want the spot.

None of those four are cured by onboarding another DSP. Three of them get worse as you add hops. If your unfilled bucket is not split by nbr, the 35 percentage points Omdia measured are unattributed in your own data too. Our low fill diagnostic order and the no-bid reason code guide cover the triage.

The two percentages describe different problems

Worth separating before drawing conclusions from either. Sixty-five percent of commercial capacity is a fill number: the slots are on sale and they do not sell. Fixing that alone is about one and a half times, not the two to three the report projects.

Thirty-two percent of linear's regulatory ceiling is a load number: streaming simply runs fewer ad minutes per hour than a broadcaster is legally allowed to. The rest of the upside lives there, and it means more ad slots per viewing hour rather than better fill on the slots that already exist.

For a bidstream, more slots per hour means one specific thing: more pods, and deeper ones. Which is unfortunate, because pod requests are the most structurally complex requests OpenRTB has, and the slot most likely to go unsold is the one whose request is most likely to be wrong.

Pod fields are where CTV requests break

Every field below arrived in OpenRTB 2.6. Each one exists on both imp.video and imp.audio, and each one is capable of producing a request that is internally inconsistent without being syntactically invalid.

podid          Ties the imps of one break together. Every imp in the pod
               must carry the same value. Different values across the
               break means the buyer sees unrelated impressions and
               competitive separation logic has nothing to work with.

podseq         Position of the pod in the stream. -1 last, 0 any, 1 first.

slotinpod      Position the seller can guarantee within the pod.
               -1 last, 0 any, 1 first, 2 first or last.
               Replaces the deprecated video.sequence. Sending both,
               or sending sequence alone, is a live source of confusion.

poddur         Total seconds to fill for a dynamic pod.
maxseq         Maximum number of ads in that pod.
               Dynamic pods need both. Sending poddur without maxseq
               leaves the buyer unable to size a bid to the break.

rqddurs        Array of exact durations the seller accepts.
               MUTUALLY EXCLUSIVE with minduration and maxduration.
               Sending all three is the single most common pod error,
               and the resolution is bidder-specific, which means the
               same request produces different behaviour per buyer.

mincpmpersec   Duration-based floor. A floor expressed per second and a
               floor expressed per impression are different numbers, and
               a bidder that reads the wrong one either overbids or
               filters the imp out before pricing.

The pattern across all of them is the same. None of these produce a parse failure. They produce a request a bidder can read and cannot act on with confidence, and the safe response to a request you cannot act on with confidence is to pass. Full field detail is in the CTV bid request reference, and the pod mechanics in CTV pod bidding.

The new capacity lands on enums that moved two weeks ago

Here is the part that connects the two halves of this. The load headroom is not going to come from more mid-rolls. Ad-supported tiers compete with an ad-free tier of the same service, and tripling mid-roll minutes is a churn experiment nobody wants to run. It comes from the CTV Ad Portfolio formats: pause, screensaver, overlay, squeezeback, in-scene, plus QR codes on any of them. IAB Tech Lab finalised the signaling guidance for those on July 22.

Those formats are described in a bid request by integers that were added to AdCOM on July 16, in release 1.0-202607.

video.plcmt           5 Pause  6 Screensaver  7 Overlay
                      8 Squeezeback  9 In-scene

video.pos             8 through 17 (partial screen, corners, frame,
                      double box, L shape, reversed L shape)

video.playbackmethod  8, 9   initiated by user pausing content
                      10, 11 initiated by idling

imp.video.battr       19 contains advertiser QR code
                      21 static visual  22 limited motion
                      23 full-motion video

A buyer whose value sets predate that release rejects every one of these. Not with an error a seller sees. With a no-bid, or with an nbr of 2 if the seller is lucky enough to receive one.

So the sequence is: the industry identifies its growth in new formats, standardises the signals for them, and every integration that has not refreshed its enum tables since mid-July filters that inventory out on arrival. In the seller's report, that is unsold. In Omdia's framing, it is part of the 35 points. It is not a demand shortfall at all, it is a version skew, and it resolves on a schedule set by whoever updates their catalogue last.

We are not throwing stones from a distance here. Our own value sets predated 202607 and failed valid pause-ad requests until we shipped the update, which we wrote up in full including the four errors we were producing. That post also covers the Content object field names that are wrong in both current tagged AdCOM releases, which is worth reading before you regenerate anything from a tag.

What actually gets a CTV request filtered before pricing

Ranked by how often we see it, and restricted to things a seller controls.

  • video.placement sent without video.plcmt. placement has been deprecated since 2.6-202303. Buyers who have completed the migration read plcmt and find nothing, which reads as unclassified inventory rather than as instream CTV. Send both while partners finish; never send only the old one. Background in placement vs plcmt.
  • A thin or absent content object. Content-level targeting is most of what a CTV buy is. Without content.genre, content.title, content.series, content.cat with a cattax, and content.livestream, a premium episode and an unlabelled loop are the same request. Note that content.livestream was redefined in 2.6-202606, covered in live event ad insertion.
  • Missing device.ifa with no lmt to explain it. No identifier means no frequency management, and frequency is the complaint every CTV buyer has. Absent-with-a-reason and absent-with-no-reason are treated very differently on the buy side.
  • Inconsistent app.bundle across exchanges. The same CTV app reaching a buyer under three different bundle identifiers through three paths is three separate blocklist and allowlist entries, and the two the buyer has not whitelisted look like unsold inventory.
  • schain.complete not equal to 1. An incomplete chain is nbr 16 at a growing number of buyers, and it is a hard filter rather than a discount.
  • tmax that does not survive the path. Every hop consumes budget. A request that arrives at the bidder with 40 milliseconds left is nbr 15, which is indistinguishable from disinterest in any report that does not carry the code. See tmax and timeouts.

Another classification layer arrives in eight days

The IAB Redefining Media Types standard is in public comment until August 8, 2026. It is explicitly designed to be encoded into OpenRTB bid requests, and its impression-level attributes map onto fields that already exist: the sound-state attribute lands on playbackmethod, which is one of the lists whose definitions were reworded on July 16 while the comment window was open.

The premise of RMT is sound. Buyers and sellers describe video inventory in incompatible vocabularies and a shared classification would help. The risk is the ordinary one for this industry: a new layer of meaning landing on fields that are populated inconsistently underneath, adding a second thing that can be wrong rather than fixing the first. Detail in our RMT walkthrough.

What to do this quarter

  • Split unfilled by nbr before you buy a demand-side solution to it. If your exchange does not return the code, ask. An unattributed 35 percent is a number you cannot act on.
  • Validate a real pod, not a single imp. Every pod field is a cross-impression consistency claim, and validating one impression in isolation tests almost none of it. Paste a full pod into the bid request tester.
  • Refresh AdCOM enums to 1.0-202607 on the receive side now. If you buy CTV and reject plcmt 5 through 9 or pos 8 through 17, you are declining the formats sellers are actively trafficking, and neither side can see that from their dashboard.
  • Pick one of rqddurs or the min/max duration pair, never both. This is the highest-frequency pod error we see and it has no consistent resolution across bidders.
  • Audit plcmt coverage across every path you sell through. Not just your direct integration. Curated and resold paths are where the field gets dropped.
  • Gate it in CI. Validation that runs once during an integration project does not survive the next SDK release. The CI guide covers the setup.

The honest limit of this argument

Bid request quality does not explain 35 percentage points. Floors above the clearing price, genuinely thin demand in smaller markets, and buyers who have not built CTV capability at all are real and probably larger. Nobody has published a decomposition and we are not going to invent one.

What is defensible is narrower and more useful. Some non-trivial share of what a seller books as unsold is demand that arrived, could not classify the request, and passed. That share is measurable with nbr, it is fixable with field-level validation, and it costs nothing to close compared with acquiring an advertiser. Every other line in the 35 percent needs a sales team, a market, or a price change. This one needs a schema check.

The creative-side half of the same report, covering what happens to a CTV slot that fills and then fails to render, is on vastlint.

Sources

OpenRTB and AdCOM are published by IAB Technology Laboratory under a Creative Commons Attribution 3.0 License. Field names and enumerated values are drawn from those documents; the analysis is our own. RTBlint is independent and not affiliated with IAB, IAB Tech Lab, or Omdia.