VASTlint
Back to blog
Standards/9 min read

The CTV Ad Portfolio Also Shipped a VAST 2.0 Path. It Is a Different Payload.

The pull request that landed the VAST 4.4 draft schema on 17 July contained two other files nobody has written about: standardised extension documents that deliver Pause, Screensaver, Overlay, Squeezeback, In-Scene and QR code metadata through VAST 2.0. The payload does not match the 4.x pattern, and validators built for one will not see the other.

Author

Alex Sekowski

Published

July 30, 2026

Reading time

9 min read

CTV Ad PortfolioVAST 2.0ExtensionsAdCOMValidation

Three files, not one

The VAST repository merged pull request 57 on 17 July 2026, from a branch called 4.4Development, titled CTV Ad Portfolio Updates. The file everyone looked at was vast_4.4.xsd, the draft schema we wrote about here five days later.

It was not the only file in that pull request. Two others landed alongside it: extensions/ctv_ad_portfolio.md and extensions/ctv_qrcode.md. Neither is a draft. Both define standardised extension payloads for VAST 2.0, and between them they cover the same six formats and the same QR code metadata as the draft schema, through a completely different mechanism.

That is not a redundancy. It is a reach decision, and it is probably the more consequential half of the release.

Why the standards body targeted a spec from 2008

VAST 4.4 is a working draft under discussion. VAST 4.2 and 4.3 are final but unevenly supported. VAST 2.0 is the version that parses essentially everywhere, including on the long tail of CTV devices that will still be in living rooms when 4.4 is finalised.

The problem is that VAST 2.0's NonLinear element has almost nothing in it. Its whole vocabulary is StaticResource, IFrameResource, HTMLResource, TrackingEvents, NonLinearClickThrough and AdParameters. A pause ad delivered through native VAST 2.0 is a JPEG, with no video, no duration, no interactive creative file and therefore no quartile tracking.

The CTV Ad Portfolio extension solves that without touching the schema. It adds an Extension element under InLine/Extensions with type="ctv_ad_portfolio", and puts a MediaFiles container inside it. Video files, image files, SIMID interactive creative files, a Duration, tracking events, icons, click tracking. The delivery model that 4.4 adds natively, back-ported into the version that already ships.

Players that do not recognise the extension ignore it, which is exactly what VAST extensions are for. The document also asks ad servers to include a native NonLinear StaticResource as a fallback where practical, so an unaware player still renders something rather than firing an error.

One release, two encodings of the same signals. Tooling that recognises the 4.x pattern will treat the 2.0 pattern as unstructured extension data, and report nothing at all.

vastlint standards note

The payload is not the 4.x payload

This is the part that matters for anyone who has already built against the signaling guidance. The 4.x pattern described in the finalised guidance carries one AdCOM signal per Extension element, each marked ext="adcom". The VAST 2.0 extension carries all of them inside a single named container, as plain child elements, alongside the media.

Same four signals, same AdCOM lists, same integers, different XML. A parser looking for Extension@type="plcmt" finds nothing in a 2.0 document, and a parser looking for Extension@type="ctv_ad_portfolio" finds nothing in a 4.x one.

The same pause ad, signalled two ways
xml
<!-- 4.x pattern: one Extension per AdCOM signal --><Extensions>  <Extension type="plcmt" ext="adcom"><plcmt>5</plcmt></Extension>  <Extension type="pos" ext="adcom"><pos>7</pos></Extension>  <Extension type="playbackmethod" ext="adcom"><playbackmethod>9</playbackmethod></Extension>  <Extension type="attr" ext="adcom"><attr>19</attr></Extension></Extensions> <!-- VAST 2.0 extension: one container, signals plus media --><Extensions>  <Extension type="ctv_ad_portfolio">    <CreativeId>creative-pause-qr-001</CreativeId>    <plcmt>5</plcmt>    <pos>7</pos>    <playbackmethod>9</playbackmethod>    <attr>19</attr>    <attr>21</attr>    <Duration>00:00:15</Duration>    <MediaFiles>      <MediaFile delivery="progressive" type="video/mp4" width="1920" height="1080" bitrate="8000" codec="H.264">        <![CDATA[https://cdn.example.com/pause/brand_cine_1080p.mp4]]>      </MediaFile>    </MediaFiles>  </Extension></Extensions>

CreativeId is doing load-bearing work

There is a structural problem the extension has to solve and it is worth understanding before you generate one. In VAST 2.0 the Extensions container hangs off InLine, not off Creative. So an extension that describes a specific creative has no inherent binding to it.

The document's answer is a CreativeId child element whose value matches the Creative@id attribute, required whenever the response contains more than one Creative. If it is omitted, the extension applies to the only NonLinearAds creative in the response.

Which means a multi-creative response that omits CreativeId is not a parse error. It is a silent misattribution: the media files, the duration, the format signal and the creative attributes all get applied to whichever creative the receiving platform decides to apply them to. Nothing in the XML is malformed. The wrong ad renders in the wrong slot, or the right ad renders with the wrong declared format, and the tag validates.

The QR extension takes the opposite approach and avoids the problem: it goes under Creative/CreativeExtensions, so it is already scoped to its creative. The document notes that if you do put QR metadata in an InLine-level Extension instead, it should carry a creative identifier, and that Creative/CreativeExtensions is the preferred placement.

The QR extension, and the coordinate system people will get wrong

CreativeExtension type="tl_qrcode" carries four things: QrCodeScanUrl, an optional QrCodeImageUrl, a QrCodePosition with xPosition and yPosition, and a QrCodeSize with size. Positions and size are percentages, and the QR code is assumed square.

The percentages are relative to the ad view, not the screen. The document works the example: an overlay lower third occupying a 1920 by 324 ad view at the bottom of a 1920 by 1080 screen resolves xPosition="10.0%" to 192 pixels within the ad view and yPosition="10.0%" to 32.4 pixels within it, while size="15.0%" resolves to a 288 by 288 square because it is measured against the ad view width. Anyone who assumes screen coordinates puts the code somewhere else entirely, and on a lower third the vertical error is the difference between inside the ad and outside it.

The other thing to internalise is that QrCodeScanUrl is not a clickthrough. The document is explicit that it does not replace ClickThrough or NonLinearClickThrough, that the two may differ, and that a scan destination is often a CTV-specific coupon or attribution endpoint while the clickthrough points at a general landing page. It also asks that measurement distinguish four separate things: the impression, the QR code rendering, the scan, and downstream advertiser-site activity. Presence of a QR code is not exposure, and exposure is not a scan.

Get VAST spec updates, platform guides, and release notes in your inbox.

What vastlint does with these today, honestly

We shipped seventeen rules for the CTV Ad Portfolio and the 4.4 draft on 25 July, including value checks for plcmt, pos, playbackmethod and the motion attributes, and structural checks for the QR block. Those rules were written against the 4.x pattern.

Run the extension documents' own examples through the current build and the result is not what we would want. The VAST 2.0 CTV Ad Portfolio examples come back valid with warnings that miss the point, and the QR example comes back with nothing at all.

None of the AdCOM value checks fire, because the integers are not where the rules look for them. None of the QR checks fire, because the block is in Creative/CreativeExtensions on a 2.0 document rather than in the 4.4 structure. What does fire is the generic extension check, telling you that a MediaFiles element inside an Extension is in an unusual place, which is true in general and wrong here: in this extension that is precisely where it belongs.

The squeezeback example collects one more: a version mismatch, because it declares version="2.0" while carrying a SIMID interactive creative file. Strictly that is correct, SIMID is a 4.x capability. In context it is the entire point of the extension, which exists to deliver 4.x capability inside a 2.0 document. A rule that is right in general is wrong on this path.

That is a gap, it is ours, and it is the direct consequence of building against one of two encodings that shipped in the same pull request. Rules for the 2.0 path are the next piece of work. Until they land, a 2.0 CTV Ad Portfolio tag gets structural VAST validation from us but no signal validation, and the misplaced-element and version warnings on that path can be read as noise rather than findings.

Current output on the IAB's own VAST 2.0 examples
text
Static Screensaver Ad          VAST 2.0   valid, 2 warnings  warning  VAST-2.0-extension-misplaced-element  Extension[0]/MediaFiles  warning  VAST-2.0-extension-misplaced-element  Extension[0]/TrackingEvents SIMID Squeezeback Ad           VAST 2.0   valid, 4 warnings  warning  VAST-2.0-extension-misplaced-element  Extension[0]/Duration  warning  VAST-2.0-extension-misplaced-element  Extension[0]/MediaFiles  warning  VAST-2.0-extension-misplaced-element  Extension[0]/AdParameters  warning  VAST-2.0-version-mismatch             /VAST Pause Ad with QR Code          VAST 2.0   valid, 0 findings  (no QR checks reached, no AdCOM signal checks reached)

The values are a live binding to a document that moves

Both extension documents make the same deliberate choice: they do not reproduce the AdCOM enumeration tables. They link to them, and they say that if a referenced AdCOM list is updated, implementations should follow the updated list without requiring a change to the extension document.

As a spec-maintenance decision that is clearly right. It stops four copies of the same table drifting apart. As an operational fact it means the meaning of an integer inside your VAST response is defined by a separate repository on a separate release cadence, and you are expected to track it.

Worth knowing what that repository did this month. AdCOM 1.0-202607 was tagged on 16 July, the day before this pull request merged, and it is the release that actually adds plcmt 5 through 9, pos 8 through 17, playbackmethod 8 through 11, and creative attributes 19 through 23. It also rewords five playback methods that already existed, and it carries a Content object with seven field names that were renamed by accident in March and corrected on the main branch on 22 July without the tag being re-cut.

So the honest summary of the binding is: follow the current list, and be aware that the current list and the current release are not always the same document.

If you are shipping CTV Ad Portfolio formats through VAST 2.0

  • Decide which encoding each partner expects, and ask rather than assume. The 4.x per-signal pattern and the 2.0 container pattern are both current and they are not interchangeable.
  • Always send CreativeId when the response has more than one Creative. It is the only thing binding the extension to its creative, and omitting it fails silently rather than loudly.
  • Include a native NonLinear fallback resource wherever practical, so a player that ignores the extension still has something to render instead of firing an error.
  • Treat QR position and size as ad-view relative, and check that the resolved rectangle fits inside the ad view and does not cover an AdChoices icon or a player control.
  • Keep QrCodeScanUrl and the clickthrough separate in your reporting, and do not let a rendered QR code be counted as a scan.
  • Pin the AdCOM release you validate enum values against, and diff it when a new one is tagged. The integers in your VAST response are defined somewhere you do not control.

Check a CTV Ad Portfolio tag

Paste a tag into the validator for the structural, media file, macro and tracking findings, plus the CTV Ad Portfolio and 4.4 rules on the 4.x signaling path. It runs locally in the browser and nothing is stored.

Open the validator

Sources and further reading

The extension document defining Extension type="ctv_ad_portfolio", its CreativeId binding rule, the MediaFiles delivery model and the AdCOM signal elements. Merged 17 July 2026.

The tl_qrcode CreativeExtension: scan URL, image URL, ad-view-relative position and size, and the measurement guidance separating rendering from scanning.

The third file in the same pull request, and the rules we shipped for it.

The finalised signaling guidance and what it asks a VAST response to declare.

What the integers in these extensions actually mean, what changed on 16 July, and why the tagged release and the current document disagree.

Every rule vastlint checks, including the CTV Ad Portfolio and VAST 4.4 rules shipped on 25 July.

Keep reading

Related stories

All posts