<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aleksander Sekowski</title>
    <description>The latest articles on DEV Community by Aleksander Sekowski (@aleksuix).</description>
    <link>https://dev.to/aleksuix</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3866734%2F559a0a69-3c28-49e2-91c7-503810b941ba.png</url>
      <title>DEV Community: Aleksander Sekowski</title>
      <link>https://dev.to/aleksuix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aleksuix"/>
    <language>en</language>
    <item>
      <title>AAMP vs AdCP: The Two Standards Stacks Racing to Define Agentic Advertising</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sun, 05 Jul 2026 22:29:10 +0000</pubDate>
      <link>https://dev.to/aleksuix/aamp-vs-adcp-the-two-standards-stacks-racing-to-define-agentic-advertising-3ogn</link>
      <guid>https://dev.to/aleksuix/aamp-vs-adcp-the-two-standards-stacks-racing-to-define-agentic-advertising-3ogn</guid>
      <description>&lt;p&gt;Eighteen months ago, agent-driven ad buying was a keynote topic. Today it is two competing standards stacks with shipped software, published by two different organizations that are openly not coordinating.&lt;/p&gt;

&lt;p&gt;If you build adtech, both are about to show up in your pipes. Here is the short version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack one: IAB Tech Lab's AAMP
&lt;/h2&gt;

&lt;p&gt;AAMP (Agentic Advertising Management Protocols) launched in January 2026 and hit 2.0 in June. The philosophy: build on the rails that already move money. It wires OpenRTB, AdCOM, OpenDirect, and VAST together, with MCP and Google's Agent2Agent protocol as the agent interfaces.&lt;/p&gt;

&lt;p&gt;Version 1.0 could only transact direct deals. The 2.0 release added programmatic: a Buyer Agent SDK with a three-layer agent hierarchy (orchestration, channel specialists, functional agents), a Seller Agent SDK that turns a static media kit into a storefront that adapts pricing to the buyer, and a Deals Library as the system of record with OpenDirect 2.1 support. Approval gates and audit logs keep humans at the sign-off points.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack two: AAO's AdCP
&lt;/h2&gt;

&lt;p&gt;The Ad Context Protocol launched in October 2025 from a consortium including Yahoo, PubMatic, Optable, Scope3, Swivel, and Triton Digital. It is now governed by AgenticAdvertising.Org (AAO), a trade association with four equally weighted voting classes: brands, agencies, publishers, and technology providers. The reference sell-side implementation lives with the Prebid community.&lt;/p&gt;

&lt;p&gt;AdCP was designed agent-native from the start. It is built directly on MCP, runs asynchronously so humans can approve while agents negotiate, and version 3.0 (April 2026) covers the full campaign lifecycle: discovery, media buys, creative production with brand.json, governance, and reporting across 20 media channels. It is in production at Snap, Pinterest, Reddit, Netflix, and Vox Media.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual differences
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Governance.&lt;/strong&gt; Tech Lab is the incumbent that already maintains VAST and OpenRTB. AAO is a new association built specifically for agentic advertising.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design center.&lt;/strong&gt; AAMP layers agents onto existing programmatic rails. AdCP defines new MCP-native tasks first and treats legacy systems as integration targets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage.&lt;/strong&gt; AAMP is deepest at the transaction layer, with ARTF agent containers running inside bidder infrastructure. AdCP is broadest across the lifecycle, planning through reporting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interfaces.&lt;/strong&gt; Both name MCP as a core protocol. A tool exposed as an MCP server serves agents on either stack without modification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification.&lt;/strong&gt; Neither stack validates the creative payload. Deal state and context move between agents; the correctness of what ships is out of scope for both specs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The layer they share
&lt;/h2&gt;

&lt;p&gt;Follow a video buy through either stack and the endpoint is identical: an ad server returns VAST XML. An InLine or Wrapper, MediaFiles, Impression pixels, TrackingEvents. Twenty channels of protocol surface on one side, one XML document on the other.&lt;/p&gt;

&lt;p&gt;That document is where delivery fails. A missing Duration, an HTTP media URL on a CTV device that requires HTTPS, a wrapper chain past the player's depth limit: none of it is visible at the protocol layer. The deal confirms cleanly, the agents log success, and the impression dies at runtime with a VAST error code nobody is watching.&lt;/p&gt;

&lt;p&gt;Human traffickers used to be the backstop. In an agent-to-agent transaction there is no trafficker. Validation has to be a tool the agent calls before the deal confirms, not a QA pass after launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;You do not need to bet on a winner. MCP is the shared interface: AAMP's Buyer Agent SDK accepts additional MCP tool servers, and AdCP is MCP-native end to end. Anything you expose over MCP works in both stacks today.&lt;/p&gt;

&lt;p&gt;That is how we ship &lt;a href="https://vastlint.org" rel="noopener noreferrer"&gt;vastlint&lt;/a&gt;: 187 rules grounded in the IAB VAST, OMID, and SIMID specs, deterministic JSON output, available as a hosted MCP endpoint, CLI, and libraries. Point either stack's agent at it and gate deal confirmation on a clean tag.&lt;/p&gt;

&lt;p&gt;The protocols will keep changing. The payload has been VAST for two decades and will still be VAST when the governance questions settle.&lt;/p&gt;

&lt;p&gt;The full comparison, with sources, is on the vastlint blog: &lt;a href="https://vastlint.org/blog/aamp-vs-adcp-agentic-advertising-standards/" rel="noopener noreferrer"&gt;AAMP vs AdCP: What IAB Tech Lab and AAO Are Each Building for Agentic Advertising&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>advertising</category>
      <category>ctv</category>
      <category>ai</category>
      <category>iab</category>
    </item>
    <item>
      <title>The VAST Macros Cheat Sheet Every Ad Ops Engineer Need</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:46:17 +0000</pubDate>
      <link>https://dev.to/aleksuix/the-vast-macros-cheat-sheet-every-ad-ops-engineer-need-44pe</link>
      <guid>https://dev.to/aleksuix/the-vast-macros-cheat-sheet-every-ad-ops-engineer-need-44pe</guid>
      <description>&lt;p&gt;If you have ever stared at a tracking URL full of &lt;code&gt;[CACHEBUSTING]&lt;/code&gt;, &lt;code&gt;[TIMESTAMP]&lt;/code&gt;, and &lt;code&gt;[ADPLAYHEAD]&lt;/code&gt; and wondered which of those the player actually fills in, this post is for you.&lt;/p&gt;

&lt;p&gt;VAST macros are substitution tokens of the form &lt;code&gt;[MACRO]&lt;/code&gt; that ad servers and players replace inside tracking, click, error, impression, and media URLs at request time. They are how a static VAST tag carries dynamic, per-impression context: the device ID, the consent string, the playhead position, the error code. Get them wrong and you lose attribution, break frequency capping, or fire tracking pixels that a cache silently collapses into one.&lt;/p&gt;

&lt;p&gt;I have debugged enough broken tags to know the failure modes are always the same handful. Here is the working reference, grouped the way you actually reason about them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model
&lt;/h2&gt;

&lt;p&gt;A macro only has a defined value in the context where the spec says it does. &lt;code&gt;[ERRORCODE]&lt;/code&gt; means nothing inside an Impression pixel. &lt;code&gt;[REASON]&lt;/code&gt; means nothing outside a &lt;code&gt;verificationNotExecuted&lt;/code&gt; URI. If you drop a macro into the wrong element, the player either leaves the literal &lt;code&gt;[ERRORCODE]&lt;/code&gt; string in the URL or substitutes an empty value, and your reporting goes sideways.&lt;/p&gt;

&lt;p&gt;The second rule: anything that is itself a URL or contains reserved characters must be percent-encoded when nested inside another URL. &lt;code&gt;[PAGEURL]&lt;/code&gt;, &lt;code&gt;[ASSETURI]&lt;/code&gt;, &lt;code&gt;[TIMESTAMP]&lt;/code&gt;, and the various user-agent macros are the usual offenders.&lt;/p&gt;

&lt;p&gt;That is most of the bugs right there. Context and encoding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache busting and timing
&lt;/h2&gt;

&lt;p&gt;The two macros that belong on nearly every tracking URL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/cachebusting/" rel="noopener noreferrer"&gt;CACHEBUSTING&lt;/a&gt;&lt;/strong&gt; resolves to a fresh random 8-digit number per request. Without it, proxies and browser caches happily collapse repeated tracking calls into a single hit, and your impression counts come in low.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/timestamp/" rel="noopener noreferrer"&gt;TIMESTAMP&lt;/a&gt;&lt;/strong&gt; is the ISO 8601 time the request fired. Useful for ordering events and as a secondary cache buster. Percent-encode it, because the colons in &lt;code&gt;18:30:00Z&lt;/code&gt; are reserved.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Playhead: the one everyone gets wrong
&lt;/h2&gt;

&lt;p&gt;This is the single most common source of legacy-tag confusion.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/adplayhead/" rel="noopener noreferrer"&gt;ADPLAYHEAD&lt;/a&gt;&lt;/strong&gt; is the current position inside the ad creative, in &lt;code&gt;HH:MM:SS.mmm&lt;/code&gt;. This is the VAST 4.1 macro you should be using.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/contentplayhead/" rel="noopener noreferrer"&gt;CONTENTPLAYHEAD&lt;/a&gt;&lt;/strong&gt; is the pre-4.1 macro. It was ambiguous about whether it meant ad time or content time, so 4.1 deprecated it in favour of &lt;code&gt;[ADPLAYHEAD]&lt;/code&gt;. Newer players may not populate it at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your quartile reporting is empty on modern CTV players, a stale &lt;code&gt;[CONTENTPLAYHEAD]&lt;/code&gt; in your tracking URLs is the first thing to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors and verification
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/errorcode/" rel="noopener noreferrer"&gt;ERRORCODE&lt;/a&gt;&lt;/strong&gt; substitutes the numeric VAST error code (303, 401, and friends) and only has a defined value inside an &lt;code&gt;&amp;lt;Error&amp;gt;&lt;/code&gt; URI. Put it anywhere else and it stays literal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/reason/" rel="noopener noreferrer"&gt;REASON&lt;/a&gt;&lt;/strong&gt; carries why a verification script did not run (1 could not load, 2 could not verify, 3 rejected) and is only valid inside a &lt;code&gt;verificationNotExecuted&lt;/code&gt; tracking URI.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Identity and privacy
&lt;/h2&gt;

&lt;p&gt;Everything attribution and consent depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/ifa/" rel="noopener noreferrer"&gt;IFA&lt;/a&gt;&lt;/strong&gt; is the resettable device advertising identifier: IDFA on iOS/tvOS, AAID on Android, a platform ID on CTV. Empty when the user has opted out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/gdpr/" rel="noopener noreferrer"&gt;GDPR&lt;/a&gt;&lt;/strong&gt; is the 1/0 flag for whether GDPR applies, and &lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/gdprconsent/" rel="noopener noreferrer"&gt;GDPRCONSENT&lt;/a&gt;&lt;/strong&gt; carries the IAB TCF consent string. If &lt;code&gt;[GDPR]&lt;/code&gt; is 1 you should be sending a valid consent string alongside it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/limitadtracking/" rel="noopener noreferrer"&gt;LIMITADTRACKING&lt;/a&gt;&lt;/strong&gt; reports the device limit-ad-tracking setting. When it is 1, the &lt;code&gt;[IFA]&lt;/code&gt; is typically zeroed and personalised targeting must be suppressed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Context: where and how the ad served
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/deviceip/" rel="noopener noreferrer"&gt;DEVICEIP&lt;/a&gt;&lt;/strong&gt; is the end-user device IP, central to geolocation and fraud detection in server-side flows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/domain/" rel="noopener noreferrer"&gt;DOMAIN&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/pageurl/" rel="noopener noreferrer"&gt;PAGEURL&lt;/a&gt;&lt;/strong&gt; identify the supply. Remember to percent-encode &lt;code&gt;[PAGEURL]&lt;/code&gt; when it rides inside another URL as a query parameter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/playersize/" rel="noopener noreferrer"&gt;PLAYERSIZE&lt;/a&gt;&lt;/strong&gt; gives &lt;code&gt;width,height&lt;/code&gt; for viewability context and creative selection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Identifiers that tie the supply chain together
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/adservingid/" rel="noopener noreferrer"&gt;ADSERVINGID&lt;/a&gt;&lt;/strong&gt; is a single identifier from the InLine ad that every party in the chain can log, which is the fastest path to debugging a discrepancy across SSP, DSP, and verification logs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/universaladid/" rel="noopener noreferrer"&gt;UNIVERSALADID&lt;/a&gt;&lt;/strong&gt; uniquely identifies the creative across systems for creative-level frequency capping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://vastlint.org/docs/vast-macros/adcount/" rel="noopener noreferrer"&gt;ADCOUNT&lt;/a&gt;&lt;/strong&gt; is the ad's position within a pod, for pod-level reporting and pacing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The deprecated ones still hiding in production
&lt;/h2&gt;

&lt;p&gt;VAST 4.1 deprecated the older playhead macros. &lt;code&gt;[CONTENTPLAYHEAD]&lt;/code&gt; and &lt;code&gt;[MEDIAPLAYHEAD]&lt;/code&gt; were both folded into &lt;code&gt;[ADPLAYHEAD]&lt;/code&gt;. They still appear constantly in tags copied from older ad servers, and they are a silent failure: the URL looks fine, but the value never arrives on a player that only implements the 4.1 macro.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually catch these
&lt;/h2&gt;

&lt;p&gt;Reading macro tables by hand does not scale past a few tags. The cases that bite are the boring ones: a macro in the wrong element, an unencoded URL, a deprecated token nobody noticed. Those are exactly what a linter is good at.&lt;/p&gt;

&lt;p&gt;I run tags through &lt;a href="https://vastlint.org/" rel="noopener noreferrer"&gt;vastlint&lt;/a&gt;, an open VAST validator that flags unknown macros, lowercase casing mistakes, missing percent-encoding, deprecated tokens, and context violations (an &lt;code&gt;[ERRORCODE]&lt;/code&gt; outside &lt;code&gt;&amp;lt;Error&amp;gt;&lt;/code&gt;, a &lt;code&gt;[REASON]&lt;/code&gt; outside &lt;code&gt;verificationNotExecuted&lt;/code&gt;). The full per-macro reference, with the value each one resolves to and where it is valid, lives at &lt;a href="https://vastlint.org/docs/vast-macros/" rel="noopener noreferrer"&gt;vastlint.org/docs/vast-macros&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you maintain VAST tags, bookmark the macro index and wire the validator into CI. The discrepancies you prevent are the ones you never have to explain on a reconciliation call.&lt;/p&gt;

</description>
      <category>ctv</category>
      <category>adtech</category>
      <category>advertising</category>
      <category>iab</category>
    </item>
    <item>
      <title>VASTlint on PyPI: in-process VAST validation for Python backends</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sun, 28 Jun 2026 16:27:56 +0000</pubDate>
      <link>https://dev.to/aleksuix/vastlint-on-pypi-in-process-vast-validation-for-python-backends-hk0</link>
      <guid>https://dev.to/aleksuix/vastlint-on-pypi-in-process-vast-validation-for-python-backends-hk0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;vastlint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vast_xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;            &lt;span class="c1"&gt;# True / False
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 0
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# [Issue(...), ...]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A VAST XML validator that runs in-process, returns structured dataclasses, and shares one Rust core with the CLI, the Go binding, the Ruby gem, the MCP server, and the web validator. No subprocess. No HTTP call to anyone. No Rust toolchain at install time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem if you do video ad tech in Python
&lt;/h2&gt;

&lt;p&gt;VAST is the IAB standard a video player uses to fetch and track an ad. The trouble is that a broken VAST tag is still well-formed XML. It parses fine. It just does not serve.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing &lt;code&gt;&amp;lt;Impression&amp;gt;&lt;/code&gt;? You do not get paid for the view.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; over HTTP in a secure context? Blocked.&lt;/li&gt;
&lt;li&gt;A malformed wrapper five hops deep? Dead impression in production.&lt;/li&gt;
&lt;li&gt;VAST 2.0 through 4.3 each moved elements around, so your hand-rolled &lt;code&gt;lxml&lt;/code&gt; checks rot.
1
None of that throws. You learn about it from a revenue dip, not a traceback. A linter exists to close the gap between "parses" and "is actually valid VAST."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until now the Python options were: shell out to a binary, call an external service, or maintain your own pile of &lt;code&gt;lxml&lt;/code&gt; rules forever. All three have a tax.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;validate()&lt;/code&gt; takes &lt;code&gt;str&lt;/code&gt; or &lt;code&gt;bytes&lt;/code&gt; and returns a &lt;code&gt;Result&lt;/code&gt;. Everything is a frozen dataclass.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vast_xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each issue is specific enough to act on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"VAST-2.0-inline-impression"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;InLine&amp;gt; must contain at least one &amp;lt;Impression&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/VAST/Ad[0]/InLine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"spec_ref"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"IAB VAST 2.0 §2.2.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"col"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule ID, message, document path, the IAB spec citation, plus line and column. "Your VAST is invalid" helps nobody. "Line 4, your InLine has no Impression, per VAST 2.0 §2.2.1" is a thirty-second fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why in-process matters
&lt;/h2&gt;

&lt;p&gt;vastlint runs inside your process. Not a subprocess, not a microservice, not a call to vastlint.org.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Subprocess per request&lt;/td&gt;
&lt;td&gt;spawn cost, stdout parsing, lifecycle management, falls over under QPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External validation service&lt;/td&gt;
&lt;td&gt;network hop, timeouts, retries, a dependency that can take you down, and you ship every tag off-box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hand-rolled &lt;code&gt;lxml&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;you own the entire IAB spec across six versions, forever&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-process binding&lt;/td&gt;
&lt;td&gt;call a function, get a dataclass, no hop, no daemon, rules maintained upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The wheel bundles a platform-matched shared library (macOS and Linux, ARM and x86), so &lt;code&gt;pip install&lt;/code&gt; needs no compiler and no Rust. It wraps the stable &lt;code&gt;vastlint-ffi&lt;/code&gt; C API behind the same core everything else uses, so the result on your backend matches the web validator byte for byte.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drop it into FastAPI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidateRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;wrapper_depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;max_wrapper_depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="n"&gt;rule_overrides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/validate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ValidateRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;wrapper_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wrapper_depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_wrapper_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_wrapper_depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;rule_overrides&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rule_overrides&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result shape is JSON-compatible and stable, so it goes straight to a frontend. &lt;code&gt;to_dict()&lt;/code&gt; and &lt;code&gt;to_json()&lt;/code&gt; are on the result. Same story in Flask or a Django admin: it is just a library import.&lt;/p&gt;

&lt;p&gt;Things this unlocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gate creative onboarding before a tag enters your serving path.&lt;/li&gt;
&lt;li&gt;Lint at trafficking time with no new infrastructure.&lt;/li&gt;
&lt;li&gt;Batch-scan stored inventory to find how much is quietly out of spec.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A deterministic verifier for model and agent harnesses
&lt;/h2&gt;

&lt;p&gt;If you point a language model at "produce a valid VAST tag" (drafting, repair, template fill), you need a checker that is faster than the model, never hallucinates, and returns the same verdict every time. A generated tag can parse fine and still be wrong with no exception to catch it. That is what a linter is, and vastlint returns structured detail, not a vibe, so you can build a real signal.&lt;/p&gt;

&lt;p&gt;As a reward function in a training or rejection-sampling loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;vast_reward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generated_xml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generated_xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warnings&lt;/span&gt;   &lt;span class="c1"&gt;# partial credit, not just pass/fail
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Per-rule detail lets you shape the reward: hard-fail on errors, weight HTTPS heavier than a missing mezzanine, or reward ten errors down to one. Binary valid/invalid throws away signal the validator already computed.&lt;/p&gt;

&lt;p&gt;As a verifier in an agent repair loop, the issues are the feedback. Each one has a message, a path, a line, and a spec citation, exactly what a model can read and act on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;repair_loop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;broken_xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_turns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;broken_xml&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_turns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;xml&lt;/span&gt;
        &lt;span class="n"&gt;feedback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (line &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;): &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spec_ref&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;issues&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;xml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate, validate, feed the structured issues back, revise, repeat. The validator is the part of the loop that does not drift.&lt;/p&gt;

&lt;p&gt;Same shape works as an eval harness: run a model over a held-out set of dirty tags, score not just how many came out valid but which rules each model gets wrong most. And because validation is in-process and sub-millisecond, you can run thousands of rollouts without the validator becoming the bottleneck. A subprocess or network call per rollout would dominate the loop. A function call does not.&lt;/p&gt;

&lt;p&gt;Agent-shaped harness instead of a training loop? The same core is also an MCP server, so an agent can call validation as a tool. Same rules, two ways in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tune severities per environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;vast_xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rule_overrides&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VAST-2.0-mediafile-https&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VAST-4.1-mezzanine-recommended&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;off&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Map a rule ID to &lt;code&gt;error&lt;/code&gt;, &lt;code&gt;warning&lt;/code&gt;, &lt;code&gt;info&lt;/code&gt;, or &lt;code&gt;off&lt;/code&gt;. Full catalog at &lt;a href="https://vastlint.org/docs/rules/" rel="noopener noreferrer"&gt;vastlint.org/docs/rules&lt;/a&gt;, and those IDs are the ones you pass here.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wrapper_depth&lt;/code&gt; and &lt;code&gt;max_wrapper_depth&lt;/code&gt; control how far the validator follows wrapper chains.&lt;/p&gt;

&lt;h2&gt;
  
  
  The full API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wrapper_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_wrapper_depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule_overrides&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;
&lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Result&lt;/code&gt; exposes &lt;code&gt;.version&lt;/code&gt;, &lt;code&gt;.issues&lt;/code&gt; (list of &lt;code&gt;Issue&lt;/code&gt;), &lt;code&gt;.summary&lt;/code&gt; (&lt;code&gt;Summary&lt;/code&gt;), &lt;code&gt;.valid&lt;/code&gt;, &lt;code&gt;.to_dict()&lt;/code&gt;, and &lt;code&gt;.to_json()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it ships
&lt;/h2&gt;

&lt;p&gt;Published from GitHub Actions: vendor the platform libraries from the matching &lt;code&gt;vastlint&lt;/code&gt; release, run the tests, build sdist and wheel, &lt;code&gt;twine check&lt;/code&gt; the metadata, publish to PyPI with trusted publishing over OIDC. No long-lived token. The wheel you install is the artifact that passed the checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;vastlint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;vastlint&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vastlint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;version&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Web validator: &lt;a href="https://vastlint.org/validate" rel="noopener noreferrer"&gt;vastlint.org/validate&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Rules: &lt;a href="https://vastlint.org/docs/rules/" rel="noopener noreferrer"&gt;vastlint.org/docs/rules&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/aleksUIX/vastlint-python" rel="noopener noreferrer"&gt;github.com/aleksUIX/vastlint-python&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a Python ad tech backend and kept putting off VAST validation because every option was bad, it is one pip install and one function call now.&lt;/p&gt;

</description>
      <category>python</category>
      <category>adtech</category>
      <category>video</category>
      <category>opensource</category>
    </item>
    <item>
      <title>VMAP and DAAST Validation Just Landed in vastlint Core</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sun, 14 Jun 2026 03:01:14 +0000</pubDate>
      <link>https://dev.to/aleksuix/vmap-and-daast-validation-just-landed-in-vastlint-core-1fcj</link>
      <guid>https://dev.to/aleksuix/vmap-and-daast-validation-just-landed-in-vastlint-core-1fcj</guid>
      <description>&lt;p&gt;If you work in video or audio ad serving, you have met VAST: the IAB XML template that tells a player what ad to show and where to fire tracking. But VAST rarely travels alone. It usually arrives wrapped in a schedule (VMAP) or shows up with an audio sibling (DAAST), and those two formats break in production just as often as VAST does, with far less tooling watching them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;vastlint&lt;/a&gt; is an open-source, Rust-based linter for IAB ad tags. As of 0.5.0 it validates VMAP 1.0 and DAAST 1.0 alongside VAST 2.0 through 4.3, from the same CLI, library, and MCP server. The catalog is now 182 rules, 53 of them new for these two formats.&lt;/p&gt;




&lt;h2&gt;
  
  
  What shipped
&lt;/h2&gt;

&lt;p&gt;There is nothing new to install or wire up. The same &lt;code&gt;validate()&lt;/code&gt; entry point that handles a VAST tag now recognises a &lt;code&gt;&amp;lt;vmap:VMAP&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;DAAST&amp;gt;&lt;/code&gt; root and routes the document to the right rule chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;24 new VMAP 1.0 rules&lt;/strong&gt;: ad-break structure, &lt;code&gt;timeOffset&lt;/code&gt; / &lt;code&gt;breakType&lt;/code&gt; / &lt;code&gt;repeatAfter&lt;/code&gt; formats, &lt;code&gt;&amp;lt;AdSource&amp;gt;&lt;/code&gt; content constraints, CDATA requirements, VMAP tracking events, and conflict detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;29 new DAAST 1.0 rules&lt;/strong&gt;: required &lt;code&gt;&amp;lt;Category&amp;gt;&lt;/code&gt;, audio MediaFile attributes, &lt;code&gt;&amp;lt;AdInteractions&amp;gt;&lt;/code&gt;, the DAAST tracking event set, audio pricing models, and detection of VAST elements that do not belong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A new &lt;code&gt;document_type&lt;/code&gt; field&lt;/strong&gt; on the result (&lt;code&gt;Vast&lt;/code&gt;, &lt;code&gt;Vmap&lt;/code&gt;, or &lt;code&gt;Daast&lt;/code&gt;) so you always know which chain ran.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every rule keeps the vastlint contract: a stable ID, a default severity, a spec reference, and a docs page with examples and fix guidance.&lt;/p&gt;




&lt;h2&gt;
  
  
  VMAP: the where and when of ad breaks
&lt;/h2&gt;

&lt;p&gt;VMAP (Video Multiple Ad Playlist) is the IAB standard a content owner uses to describe ad-break structure when they do not control the player. It was published on July 19, 2012 and has had exactly one version since. The mental model: VMAP handles the &lt;em&gt;where&lt;/em&gt; and &lt;em&gt;when&lt;/em&gt; of ad placement (pre-roll, mid-rolls at specific offsets, post-roll), and VAST handles the &lt;em&gt;what&lt;/em&gt; (the creative inside each break).&lt;/p&gt;

&lt;p&gt;A VMAP document is a playlist of &lt;code&gt;&amp;lt;AdBreak&amp;gt;&lt;/code&gt; elements. Each break carries a &lt;code&gt;timeOffset&lt;/code&gt;, a &lt;code&gt;breakType&lt;/code&gt;, and an &lt;code&gt;&amp;lt;AdSource&amp;gt;&lt;/code&gt; that either embeds a VAST document inline, points at an ad tag URI, or carries custom data. That flexibility is exactly where it breaks.&lt;/p&gt;

&lt;p&gt;Here is a VMAP snippet that looks fine and is not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;vmap:VMAP&lt;/span&gt; &lt;span class="na"&gt;xmlns:vmap=&lt;/span&gt;&lt;span class="s"&gt;"http://www.iab.net/videosuite/vmap"&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;vmap:AdBreak&lt;/span&gt; &lt;span class="na"&gt;timeOffset=&lt;/span&gt;&lt;span class="s"&gt;"15:00"&lt;/span&gt; &lt;span class="na"&gt;breakType=&lt;/span&gt;&lt;span class="s"&gt;"linear"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;vmap:AdSource&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"mid-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;vmap:AdTagURI&lt;/span&gt; &lt;span class="na"&gt;templateType=&lt;/span&gt;&lt;span class="s"&gt;"vast3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        https://ads.example.com/vast?cb=[CACHEBUSTER]&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;pos=mid
      &lt;span class="nt"&gt;&amp;lt;/vmap:AdTagURI&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/vmap:AdSource&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/vmap:AdBreak&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/vmap:VMAP&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vastlint reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VMAP-1.0-adbreak-timeoffset-format&lt;/strong&gt; (error): &lt;code&gt;timeOffset="15:00"&lt;/code&gt; is not a valid &lt;code&gt;hh:mm:ss[.mmm]&lt;/code&gt;, &lt;code&gt;n%&lt;/code&gt;, &lt;code&gt;start&lt;/code&gt;, &lt;code&gt;end&lt;/code&gt;, or &lt;code&gt;#m&lt;/code&gt; value. The mid-roll will not schedule where you think.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VMAP-1.0-adtaguri-cdata&lt;/strong&gt; (error): the &lt;code&gt;AdTagURI&lt;/code&gt; contains an unescaped ampersand and is not inside a CDATA block, so the document stops being well-formed the moment the macro expands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part: when an &lt;code&gt;&amp;lt;AdSource&amp;gt;&lt;/code&gt; embeds VAST inline, vastlint validates that VAST with the full VAST rule chain and reports issues with &lt;code&gt;/VMAP/AdBreak[i]/AdSource/VASTAdData&lt;/code&gt; paths plus document-absolute line and column. A wrapper problem two levels deep points straight at the break it lives in.&lt;/p&gt;

&lt;p&gt;Common VMAP failures vastlint catches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Malformed &lt;code&gt;timeOffset&lt;/code&gt; that silently drops a break.&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;&amp;lt;AdSource&amp;gt;&lt;/code&gt; with more than one payload, or none (the spec requires exactly one of &lt;code&gt;&amp;lt;VASTAdData&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;AdTagURI&amp;gt;&lt;/code&gt;, or &lt;code&gt;&amp;lt;CustomAdData&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AdTagURI&lt;/code&gt; / &lt;code&gt;CustomAdData&lt;/code&gt; not wrapped in CDATA.&lt;/li&gt;
&lt;li&gt;Structurally invalid VAST inside a structurally valid VMAP.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repeatAfter&lt;/code&gt; that has no effect because &lt;code&gt;timeOffset&lt;/code&gt; is &lt;code&gt;start&lt;/code&gt; or &lt;code&gt;end&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  DAAST: deprecated, but the tags did not go away
&lt;/h2&gt;

&lt;p&gt;DAAST (Digital Audio Ad Serving Template) is the audio counterpart the IAB released for public comment in 2014. It mirrors VAST 3.0 but swaps video assumptions for audio ones: &lt;code&gt;&amp;lt;Category&amp;gt;&lt;/code&gt; is required, creatives carry audio MediaFiles, &lt;code&gt;&amp;lt;VideoClicks&amp;gt;&lt;/code&gt; becomes &lt;code&gt;&amp;lt;AdInteractions&amp;gt;&lt;/code&gt;, and the tracking event set is audio-specific.&lt;/p&gt;

&lt;p&gt;Here is the catch: DAAST 1.0 is formally deprecated. In November 2018 the IAB merged audio support into VAST 4.1 via an optional &lt;code&gt;adType&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;Ad&amp;gt;&lt;/code&gt;, and the recommendation since then is to serve audio with VAST 4.1 or later.&lt;/p&gt;

&lt;p&gt;So why validate DAAST in 2026? Because the money never stopped and neither did the legacy tags. IAB and PwC put US digital audio ad spend at &lt;strong&gt;$8.4 billion in 2025&lt;/strong&gt;, up 10.2% year over year, with podcast revenue alone growing 17.6% to roughly &lt;strong&gt;$2.9 billion&lt;/strong&gt;. A market that large still has long-lived DAAST inventory and ad-server templates in circulation. When a DAAST tag shows up, you want to know whether it is clean, a DAAST document with VAST leftovers, or a VAST tag someone mislabeled.&lt;/p&gt;

&lt;p&gt;Here is a VAST tag pretending to be DAAST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;DAAST&lt;/span&gt; &lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Ad&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"audio-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;InLine&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;AdTitle&amp;gt;&lt;/span&gt;Morning drive spot&lt;span class="nt"&gt;&amp;lt;/AdTitle&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Impression&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[https://t.example.com/imp]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/Impression&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Creatives&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;Creative&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;Linear&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;Duration&amp;gt;&lt;/span&gt;00:00:30&lt;span class="nt"&gt;&amp;lt;/Duration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;MediaFiles&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;MediaFile&lt;/span&gt; &lt;span class="na"&gt;delivery=&lt;/span&gt;&lt;span class="s"&gt;"progressive"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="cp"&gt;&amp;lt;![CDATA[https://cdn.example.com/spot.mp4]]&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/MediaFile&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/MediaFiles&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;VideoClicks&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;ClickThrough&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;![CDATA[https://example.com]]&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/ClickThrough&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/VideoClicks&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/Linear&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/Creative&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Creatives&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/InLine&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Ad&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/DAAST&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;vastlint's verdict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DAAST-1.0-inline-category&lt;/strong&gt; (error): &lt;code&gt;&amp;lt;Category&amp;gt;&lt;/code&gt; is required in DAAST and is missing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DAAST-1.0-mediafile-audio-type&lt;/strong&gt; (warning): the MediaFile type is &lt;code&gt;video/mp4&lt;/code&gt; on an audio creative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DAAST-1.0-videoclicks-element&lt;/strong&gt; (warning): &lt;code&gt;&amp;lt;VideoClicks&amp;gt;&lt;/code&gt; is a VAST element; DAAST uses &lt;code&gt;&amp;lt;AdInteractions&amp;gt;&lt;/code&gt;. This is the tell that the tag was lifted from a video flow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other DAAST-specific checks include the &lt;code&gt;&amp;lt;AudioInteractions&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;AdInteractions&amp;gt;&lt;/code&gt; rename, audio pricing models (DAAST adds &lt;code&gt;cpo&lt;/code&gt; to the usual &lt;code&gt;cpm&lt;/code&gt; / &lt;code&gt;cpc&lt;/code&gt; / &lt;code&gt;cpe&lt;/code&gt; / &lt;code&gt;cpv&lt;/code&gt; set, and requires &lt;code&gt;model&lt;/code&gt; plus &lt;code&gt;currency&lt;/code&gt;), required &lt;code&gt;&amp;lt;DAASTAdTagURI&amp;gt;&lt;/code&gt; on wrappers, and &lt;code&gt;[ERRORCODE]&lt;/code&gt; macro presence so failed audio impressions are reportable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this belongs in core, not a separate tool
&lt;/h2&gt;

&lt;p&gt;Ad ops teams do not deal with one format at a time. A single campaign can ship a VMAP schedule whose breaks wrap VAST 4.x creatives, while the audio line item delivers a DAAST tag. Asking people to remember which validator handles which format, and to paste tags into three different web tools, is how broken tags reach production.&lt;/p&gt;

&lt;p&gt;Putting all three formats behind one entry point removes that decision. You hand vastlint a document, it tells you what the document is, and it validates it against the right spec with consistent rule IDs and severities. One engine, one report format, three formats covered, and it runs everywhere you already run vastlint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLI&lt;/strong&gt;: validate VMAP and DAAST files in a pre-flight step or a git hook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Library&lt;/strong&gt;: call &lt;code&gt;validate()&lt;/code&gt; and branch on &lt;code&gt;document_type&lt;/code&gt;; the result shape is identical across formats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI&lt;/strong&gt;: gate merges so a malformed &lt;code&gt;timeOffset&lt;/code&gt; or a DAAST tag with VAST leftovers never reaches a release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP server&lt;/strong&gt;: an AI agent doing campaign QA can validate a playlist or an audio tag through the same tool surface it uses for VAST.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Paste a VMAP playlist or a DAAST audio tag into the validator at &lt;a href="https://vastlint.org/validate/" rel="noopener noreferrer"&gt;vastlint.org/validate&lt;/a&gt;. It detects the document type and returns every issue with a rule ID, severity, and the exact fix, the same way it does for VAST. vastlint is free and open source: &lt;a href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;github.com/aleksUIX/vastlint&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.iab.com/wp-content/uploads/2015/06/VMAPv1_0.pdf" rel="noopener noreferrer"&gt;VMAP 1.0 specification (IAB, July 19, 2012)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iab.com/guidelines/daast/" rel="noopener noreferrer"&gt;Digital Audio Ad Serving Template (DAAST), IAB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.iab.com/news/vast-4-1/" rel="noopener noreferrer"&gt;VAST 4.1 release: DAAST merged into VAST (IAB)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://radioink.com/2026/04/16/iab-digital-audio-grew-10-in-2025-as-podcasts-near-3b/" rel="noopener noreferrer"&gt;IAB: digital audio ad spend hits $8.4B in 2025 (Radio Ink)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>adtech</category>
      <category>opensource</category>
      <category>rust</category>
      <category>ctv</category>
    </item>
    <item>
      <title>OMID Explained: How IAB's Open Measurement SDK Works with VAST AdVerifications</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:27:08 +0000</pubDate>
      <link>https://dev.to/aleksuix/omid-explained-how-iabs-open-measurement-sdk-works-with-vast-adverifications-10k6</link>
      <guid>https://dev.to/aleksuix/omid-explained-how-iabs-open-measurement-sdk-works-with-vast-adverifications-10k6</guid>
      <description>&lt;p&gt;You've probably seen &lt;code&gt;apiFramework="omid"&lt;/code&gt; inside a VAST tag and wondered what it actually does. This is the practical explanation — what OMID is, how it fits into VAST 4.1+, what each field means, and what breaks when you get it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is OMID?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;OMID&lt;/strong&gt; (Open Measurement Interface Definition) is the API layer inside the IAB Tech Lab &lt;strong&gt;Open Measurement SDK&lt;/strong&gt; (OM SDK). It defines a standardised way for third-party measurement vendors — DoubleVerify, IAS, Moat, etc. — to collect viewability, audio, and fraud signals from any video or audio player, whether that's a web browser, a mobile app, or a CTV device.&lt;/p&gt;

&lt;p&gt;The key distinction: OMID is the &lt;em&gt;protocol&lt;/em&gt;. OM SDK is the &lt;em&gt;implementation&lt;/em&gt; (native libraries for iOS, Android, and web). In practice, most people use the terms interchangeably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it replaced VPAID for measurement
&lt;/h2&gt;

&lt;p&gt;Before OMID, the common approach to third-party measurement was to piggyback a verification script inside a VPAID unit. VPAID gave that script full access to the player's JavaScript context and DOM — which meant it could read page data, fire arbitrary pixels, redirect users, and slow down load times. The IAB's own audit found VPAID ranked among the primary vectors for ad fraud.&lt;/p&gt;

&lt;p&gt;OMID draws a hard boundary. Measurement scripts load into an &lt;strong&gt;isolated context&lt;/strong&gt; and communicate through a standardised API. The player reports media events and geometry data to the OM SDK; the SDK forwards them to vendor scripts via the OMID API. No DOM access, no shared JS context, no ability to do anything outside the defined event model.&lt;/p&gt;

&lt;p&gt;It also runs on CTV and SSAI environments where VPAID simply can't execute. That's the main reason the industry migrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it fits into VAST
&lt;/h2&gt;

&lt;p&gt;VAST describes the ad: media files, tracking URLs, companion ads. OMID describes &lt;strong&gt;who measures the ad and how&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Starting with &lt;strong&gt;VAST 4.1&lt;/strong&gt;, the &lt;code&gt;&amp;lt;AdVerifications&amp;gt;&lt;/code&gt; element carries one or more &lt;code&gt;&amp;lt;Verification&amp;gt;&lt;/code&gt; entries, each pointing to a vendor's OMID-compatible script. Players that support OM SDK parse this automatically and begin reporting signals once the ad enters the viewport. Players that don't support it skip the element gracefully — OMID degrades without breaking playback.&lt;/p&gt;

&lt;h2&gt;
  
  
  The XML structure
&lt;/h2&gt;

&lt;p&gt;Here's a complete example with two vendors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;AdVerifications&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;Verification&lt;/span&gt; &lt;span class="na"&gt;vendor=&lt;/span&gt;&lt;span class="s"&gt;"doubleverify.com-omid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;JavaScriptResource&lt;/span&gt; &lt;span class="na"&gt;apiFramework=&lt;/span&gt;&lt;span class="s"&gt;"omid"&lt;/span&gt; &lt;span class="na"&gt;browserOptional=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;![CDATA[https://cdn.doubleverify.com/dvtp_src.js]]&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/JavaScriptResource&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;VerificationParameters&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;![CDATA[ctx=12345678&amp;amp;cmp=DV123456]]&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/VerificationParameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TrackingEvents&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Tracking&lt;/span&gt; &lt;span class="na"&gt;event=&lt;/span&gt;&lt;span class="s"&gt;"verificationNotExecuted"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;![CDATA[https://cdn.doubleverify.com/not_executed.js?ctx=12345678&amp;amp;reason=[REASON]]]&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Tracking&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/TrackingEvents&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Verification&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;Verification&lt;/span&gt; &lt;span class="na"&gt;vendor=&lt;/span&gt;&lt;span class="s"&gt;"ias.com-omid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;JavaScriptResource&lt;/span&gt; &lt;span class="na"&gt;apiFramework=&lt;/span&gt;&lt;span class="s"&gt;"omid"&lt;/span&gt; &lt;span class="na"&gt;browserOptional=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;![CDATA[https://pixel.adsafeprotected.com/jload]]&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/JavaScriptResource&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;VerificationParameters&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;![CDATA[anId=12345&amp;amp;advId=67890]]&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/VerificationParameters&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;TrackingEvents&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;Tracking&lt;/span&gt; &lt;span class="na"&gt;event=&lt;/span&gt;&lt;span class="s"&gt;"verificationNotExecuted"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;![CDATA[https://pixel.adsafeprotected.com/jload?not_executed=1&amp;amp;reason=[REASON]]]&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/Tracking&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/TrackingEvents&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/Verification&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/AdVerifications&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breaking down each field
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;vendor&lt;/code&gt; (required)&lt;/strong&gt;&lt;br&gt;
Identifies the measurement vendor. Must follow the &lt;code&gt;domain-omid&lt;/code&gt; format — e.g. &lt;code&gt;doubleverify.com-omid&lt;/code&gt;, &lt;code&gt;ias.com-omid&lt;/code&gt;. This is how players deduplicate vendors across wrapper chains and how the OM SDK routes signals to the right script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;JavaScriptResource apiFramework="omid"&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The URL to the vendor's OMID-compatible measurement script. &lt;code&gt;apiFramework&lt;/code&gt; must be lowercase &lt;code&gt;omid&lt;/code&gt;. Some players treat this attribute as case-sensitive, so &lt;code&gt;OMID&lt;/code&gt; or &lt;code&gt;Omid&lt;/code&gt; may be silently ignored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;browserOptional&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
When &lt;code&gt;true&lt;/code&gt;, native iOS and Android players can skip loading the JS file and use the vendor's native SDK integration instead. When &lt;code&gt;false&lt;/code&gt; (the default), the script is required even in app contexts — and if the player can't execute it, it should fire &lt;code&gt;verificationNotExecuted&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;VerificationParameters&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
A CDATA block with vendor-specific initialisation params — typically placement ID, publisher ID, campaign ID. The format is vendor-defined; VAST doesn't prescribe it. If this element is present but empty, it's usually a configuration mistake.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;&amp;lt;TrackingEvents&amp;gt;&lt;/code&gt; / &lt;code&gt;verificationNotExecuted&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;[REASON]&lt;/code&gt; macro is replaced by a numeric code when measurement fails:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;API not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;JavaScript resource not executed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Verification not supported for this media type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Player/SDK does not support OM SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This event is how vendors know measurement failed rather than just inferring it from missing signals. Missing it means you have no visibility into non-executing scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  VAST version support
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Support&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VAST 2.0, 3.0&lt;/td&gt;
&lt;td&gt;None (native)&lt;/td&gt;
&lt;td&gt;Some players accept AdVerifications inside an &lt;code&gt;&amp;lt;Extensions&amp;gt;&lt;/code&gt; block — non-standard and not reliable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAST 4.0&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;AdVerifications exists but placement differs from 4.1+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAST 4.1&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Canonical placement; the version most OM SDK integrations target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAST 4.2, 4.3&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Inherited from 4.1 without structural changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're serving tags to CTV or app inventory, target VAST 4.1+. Anything below that and you're relying on non-standard extension handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  OMID in wrapper chains
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;AdVerifications&amp;gt;&lt;/code&gt; can appear in both &lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Wrapper&amp;gt;&lt;/code&gt; documents. When a wrapper chain is involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The player accumulates &lt;code&gt;&amp;lt;Verification&amp;gt;&lt;/code&gt; entries from every hop in the chain — outermost wrapper down to InLine.&lt;/li&gt;
&lt;li&gt;Duplicate vendor values (same &lt;code&gt;vendor&lt;/code&gt; attribute at multiple hops) should be deduplicated by the player. Only one instance per vendor should run. "Should" — behaviour varies.&lt;/li&gt;
&lt;li&gt;If you're debugging a live chain, a hop-by-hop inspector will show you exactly which vendors are introduced at which level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  OMID vs VPAID for measurement — side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;VPAID&lt;/th&gt;
&lt;th&gt;OMID&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Security model&lt;/td&gt;
&lt;td&gt;Script runs in shared player JS context&lt;/td&gt;
&lt;td&gt;Script runs in isolated context via OM SDK API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DOM access&lt;/td&gt;
&lt;td&gt;Full access to publisher DOM&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CTV / native players&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Supported via native OM SDK libraries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSAI environments&lt;/td&gt;
&lt;td&gt;Not possible&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple vendors&lt;/td&gt;
&lt;td&gt;Each vendor wraps the whole ad&lt;/td&gt;
&lt;td&gt;Multiple &lt;code&gt;&amp;lt;Verification&amp;gt;&lt;/code&gt; elements in one tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed measurement signal&lt;/td&gt;
&lt;td&gt;No standard mechanism&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;verificationNotExecuted&lt;/code&gt; tracking event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fraud risk&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low — constrained to OMID event model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Deprecated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Current standard (v1.5)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;p&gt;These are the issues that actually show up in production tags:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;apiFramework="OMID"&lt;/code&gt; (uppercase)&lt;/strong&gt;&lt;br&gt;
Case-sensitive players fail to recognise the vendor. Always lowercase: &lt;code&gt;omid&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP script URL&lt;/strong&gt;&lt;br&gt;
Mixed-content block in modern browsers. The script never loads. Use HTTPS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing &lt;code&gt;vendor&lt;/code&gt; attribute&lt;/strong&gt;&lt;br&gt;
Players can't deduplicate vendors or route scripts correctly. Always include it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;vendor&lt;/code&gt; not in &lt;code&gt;domain-omid&lt;/code&gt; format&lt;/strong&gt;&lt;br&gt;
Values like &lt;code&gt;"DoubleVerify"&lt;/code&gt; or &lt;code&gt;"DV"&lt;/code&gt; are non-standard. Strict OM SDK implementations may silently ignore them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Missing &lt;code&gt;verificationNotExecuted&lt;/code&gt; tracking URL&lt;/strong&gt;&lt;br&gt;
The vendor has no signal when measurement fails. Discrepancies go undetected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Duplicate vendor at multiple wrapper hops&lt;/strong&gt;&lt;br&gt;
Some players run both instances and double-count. Use your inspector to catch this before it hits production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AdVerifications in VAST 2.0 / 3.0 without an Extensions wrapper&lt;/strong&gt;&lt;br&gt;
Structurally invalid — most players ignore the block entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation
&lt;/h2&gt;

&lt;p&gt;If you want to catch these issues before a tag goes live, paste it into &lt;a href="https://vastlint.org/validate/" rel="noopener noreferrer"&gt;VASTlint&lt;/a&gt;. It checks vendor format, &lt;code&gt;apiFramework&lt;/code&gt; casing, HTTPS enforcement, empty &lt;code&gt;VerificationParameters&lt;/code&gt;, and duplicate vendor detection. It won't execute the script or validate the runtime OM SDK session — that requires a live player — but it catches the structural mistakes that account for most OMID failures.&lt;/p&gt;




&lt;p&gt;The IAB Tech Lab OM SDK docs are the authoritative reference if you need the full protocol details: &lt;a href="https://iabtechlab.com/standards/open-measurement-sdk/" rel="noopener noreferrer"&gt;iabtechlab.com/standards/open-measurement-sdk&lt;/a&gt;. The &lt;a href="https://iabtechlab.com/wp-content/uploads/2024/08/OMID_API_v1.5.pdf" rel="noopener noreferrer"&gt;OMID API v1.5 spec&lt;/a&gt; is worth reading if you're integrating on the player or vendor side.&lt;/p&gt;

</description>
      <category>adtech</category>
      <category>vast</category>
      <category>advertising</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CTV Fraud Has an IPv6 Business Problem</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sat, 30 May 2026 16:01:32 +0000</pubDate>
      <link>https://dev.to/aleksuix/ctv-fraud-has-an-ipv6-business-problem-3a1c</link>
      <guid>https://dev.to/aleksuix/ctv-fraud-has-an-ipv6-business-problem-3a1c</guid>
      <description>&lt;p&gt;Most discussions about CTV fraud start with threat actors, fake apps, or suspicious traffic spikes.&lt;/p&gt;

&lt;p&gt;That is useful, but it misses a more expensive problem: bad fraud decisions.&lt;/p&gt;

&lt;p&gt;If your fraud stack still treats one IP address as a durable identity, IPv6 is already making those decisions worse. That creates two kinds of cost at the same time. Fraud slips through when rotating addresses look new, and legitimate traffic gets penalized when broad network blocks catch more than they should.&lt;/p&gt;

&lt;p&gt;That is not just a security issue. It is a business problem for the whole ad ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  A small watchlist, a useful lesson
&lt;/h2&gt;

&lt;p&gt;Pixalate's May 2026 AdFraud IOC-DB workbook for IPv6 addresses is a good example of the problem.&lt;/p&gt;

&lt;p&gt;The workbook is small. It contains 25 populated high-risk indicators, not a market census. But the mix is still useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;21 entries are tagged &lt;code&gt;displayImpressionFraud&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;2 are tagged &lt;code&gt;IABcrawler&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;1 is tagged &lt;code&gt;appSpoofing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;1 is tagged &lt;code&gt;deviceIdStuffing&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The provider distribution is what makes the dataset interesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;11 entries are associated with Spectrum&lt;/li&gt;
&lt;li&gt;3 with Verizon Fios&lt;/li&gt;
&lt;li&gt;3 with T-Mobile USA&lt;/li&gt;
&lt;li&gt;2 with Comcast Cable&lt;/li&gt;
&lt;li&gt;1 each with AT&amp;amp;T Internet, Comcast Business, Play, AT&amp;amp;T Wireless, Hetzner Online, and Starlink&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does not mean those providers are fraud networks. It means suspicious ad activity can show up across residential broadband, mobile access, satellite access, and data-center infrastructure.&lt;/p&gt;

&lt;p&gt;The repeated prefixes matter even more. Four of the listed addresses sit inside the same Spectrum /64. Nine sit inside the same Spectrum /32. Two Verizon Fios addresses share a /64. Two Comcast Cable addresses share a /64.&lt;/p&gt;

&lt;p&gt;That is the operational lesson.&lt;/p&gt;

&lt;p&gt;The single address can change. The surrounding network context can still repeat.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why IPv6 changes the economics
&lt;/h2&gt;

&lt;p&gt;IPv6 was built to make long-term address correlation harder.&lt;/p&gt;

&lt;p&gt;RFC 8981 describes temporary IPv6 addresses that rotate randomized interface identifiers over time. That is a privacy improvement. It reduces the value of using one full address to track the same host across many sessions.&lt;/p&gt;

&lt;p&gt;That is good network design. It is bad news for simplistic fraud models.&lt;/p&gt;

&lt;p&gt;If a system still assumes one address equals one stable endpoint, it will make two predictable mistakes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It will miss abuse when suspicious actors rotate through new /128s.&lt;/li&gt;
&lt;li&gt;It will overblock when one suspicious /128 gets expanded to a much broader prefix without enough evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both errors are expensive.&lt;/p&gt;

&lt;p&gt;One leaks money to bad traffic. The other blocks revenue from good traffic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The false positive problem is bigger than most teams admit
&lt;/h2&gt;

&lt;p&gt;In ad tech, false negatives get the attention because they look like fraud losses.&lt;/p&gt;

&lt;p&gt;False positives are quieter. They look like lower match rates, lower fill, lower bid density, underdelivery, or weaker reach. That makes them easier to misdiagnose.&lt;/p&gt;

&lt;p&gt;If a buyer, platform, or verification layer decides that a broad IPv6 prefix is bad because one address in that space was flagged, the blast radius can be large.&lt;/p&gt;

&lt;p&gt;For publishers, that can mean rejecting legitimate demand or discounting inventory quality for users who are not actually fraudulent.&lt;/p&gt;

&lt;p&gt;For SSPs and exchanges, it can mean pushing overly broad risk labels downstream, which changes auction behavior without proving the underlying case.&lt;/p&gt;

&lt;p&gt;For DSPs, it can mean excluding reachable households from a campaign, weakening delivery and frequency goals while making optimization look worse than it should.&lt;/p&gt;

&lt;p&gt;For agencies and brands, it can mean paying for expensive fraud controls that suppress real audience access.&lt;/p&gt;

&lt;p&gt;This is where IPv6 becomes a business issue instead of a pure detection issue.&lt;/p&gt;

&lt;p&gt;When identity assumptions get weaker, the cost of blunt enforcement gets higher.&lt;/p&gt;




&lt;h2&gt;
  
  
  CTV makes those errors harder to unwind
&lt;/h2&gt;

&lt;p&gt;CTV already has fragmented observability.&lt;/p&gt;

&lt;p&gt;The IP visible to a content request is not always the same IP seen by the ad server, the SSAI stitcher, the player, or the downstream reporting system. By the time the logs disagree, the impression is gone.&lt;/p&gt;

&lt;p&gt;That matters because network signals are often treated as if they are closer to ground truth than they really are.&lt;/p&gt;

&lt;p&gt;A suspicious IPv6 indicator can tell you something useful about origin, recurrence, or likely abuse. It does not tell you, on its own, whether:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the inventory description was false&lt;/li&gt;
&lt;li&gt;the app was spoofed&lt;/li&gt;
&lt;li&gt;the supply path was misrepresented&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://vastlint.org/docs/validate-vast-xml/" rel="noopener noreferrer"&gt;VAST was runnable&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the ad rendered successfully on the device that mattered&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means a weak IP decision can ripple across multiple business systems.&lt;/p&gt;

&lt;p&gt;It can affect eligibility, scoring, pacing, billing, discrepancy reviews, partner escalations, and renewal conversations.&lt;/p&gt;

&lt;p&gt;In practice, many of those downstream failures show up as execution problems that have nothing to do with IP identity by themselves: too many redirects in a &lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-depth/" rel="noopener noreferrer"&gt;wrapper chain&lt;/a&gt;, insecure &lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-https/" rel="noopener noreferrer"&gt;HTTP media URLs on HTTPS inventory&lt;/a&gt;, or a tag that passes a quick glance but fails in a real &lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;live-tag test flow&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the ecosystem feels the damage
&lt;/h2&gt;

&lt;p&gt;The biggest implication of IPv6 in fraud detection is not technical complexity by itself. It is decision quality across the market.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishers
&lt;/h3&gt;

&lt;p&gt;Publishers care about fill, yield, and trust.&lt;/p&gt;

&lt;p&gt;If network-based controls are too aggressive, good traffic can get downgraded or blocked. If the controls are too weak, invalid traffic still makes it into sold inventory. Either way, the publisher absorbs the economic damage first.&lt;/p&gt;

&lt;h3&gt;
  
  
  SSPs and exchanges
&lt;/h3&gt;

&lt;p&gt;SSPs and exchanges sit in the middle of the trust chain.&lt;/p&gt;

&lt;p&gt;If they pass along weak identity assumptions as if they were strong fraud signals, they distort auction quality and partner scoring. If they do not cluster recurring signals above the single address level, they also miss repeat patterns that should trigger closer review.&lt;/p&gt;

&lt;h3&gt;
  
  
  DSPs and buyers
&lt;/h3&gt;

&lt;p&gt;DSPs need accurate suppression, not maximum suppression.&lt;/p&gt;

&lt;p&gt;Overblocking broad IPv6 space can quietly reduce addressable reach and campaign efficiency. Underblocking lets suspicious activity continue long enough to waste budget and pollute performance models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification and fraud vendors
&lt;/h3&gt;

&lt;p&gt;Vendors that still lean too heavily on single-address reputation will face the hardest tradeoff. Their models can look decisive while being economically blunt.&lt;/p&gt;

&lt;p&gt;The market increasingly needs cluster logic, recurrence logic, and stronger correlation across network, app, device, and execution signals.&lt;/p&gt;




&lt;h2&gt;
  
  
  What better operations look like
&lt;/h2&gt;

&lt;p&gt;The answer is not to throw away IP intelligence.&lt;/p&gt;

&lt;p&gt;The answer is to use it more carefully.&lt;/p&gt;

&lt;p&gt;An IPv6 IOC feed is most useful as an escalation surface, not a standalone verdict engine.&lt;/p&gt;

&lt;p&gt;That means a few practical shifts:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Treat the /128 as a lead
&lt;/h3&gt;

&lt;p&gt;Keep the exact address. It still matters.&lt;/p&gt;

&lt;p&gt;But do not stop there. Enrich it with bundle ID, app ID, supply path, user agent data, session timing, creative identifiers, SSAI markers, and execution outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cluster above the single address
&lt;/h3&gt;

&lt;p&gt;Review /64, /48, /32, ASN, ISP, and time-window recurrence together.&lt;/p&gt;

&lt;p&gt;That is where repeated behavior becomes visible without pretending the full address is a stable identity token.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Separate ranking from enforcement
&lt;/h3&gt;

&lt;p&gt;A network signal can justify lower trust, tighter review, or increased measurement before it justifies a hard block.&lt;/p&gt;

&lt;p&gt;This is especially important in consumer broadband and mobile access space, where the collateral damage of overbroad enforcement can be substantial.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Connect detection to business outcomes
&lt;/h3&gt;

&lt;p&gt;For any suspect impression, teams should be able to connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request context&lt;/li&gt;
&lt;li&gt;network context&lt;/li&gt;
&lt;li&gt;winning creative&lt;/li&gt;
&lt;li&gt;final VAST or stitched instruction&lt;/li&gt;
&lt;li&gt;execution result&lt;/li&gt;
&lt;li&gt;billing consequence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those records do not join cleanly, the organization is not really evaluating behavior. It is comparing disconnected logs and making partial decisions.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you want to go deeper into the VAST side of the problem
&lt;/h2&gt;

&lt;p&gt;The network side is only part of the story. If you want to connect business outcomes back to execution quality, these are the most useful vastlint.org pages to start with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/validate/" rel="noopener noreferrer"&gt;VAST Tag Validator&lt;/a&gt; for raw XML validation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;VAST Tag Tester&lt;/a&gt; for live tag QA, creative preview, and click tracking&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/inspect/" rel="noopener noreferrer"&gt;VAST Inspector&lt;/a&gt; for hop-by-hop wrapper debugging&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/validate-vast-xml/" rel="noopener noreferrer"&gt;How to validate VAST XML&lt;/a&gt; for the practical decision tree between validator, tester, and inspector&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/vast-versions/" rel="noopener noreferrer"&gt;VAST versions guide&lt;/a&gt; for version drift, VPAID removal, and CTV addendum context&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/guides/iab-vast-validator/" rel="noopener noreferrer"&gt;IAB VAST validator guide&lt;/a&gt; for where pure spec compliance stops and platform behavior starts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-depth/" rel="noopener noreferrer"&gt;VAST-2.0-wrapper-depth&lt;/a&gt; for one of the most common delivery blockers in wrapped tags&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-https/" rel="noopener noreferrer"&gt;VAST-2.0-mediafile-https&lt;/a&gt; for the portability and CTV playback risk of insecure media URLs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/methodology/" rel="noopener noreferrer"&gt;Rule derivation methodology&lt;/a&gt; if you want to see how the validation rules are grounded in specs and standards&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The bigger market implication
&lt;/h2&gt;

&lt;p&gt;The ad ecosystem has spent years building better fraud controls around identifiers that were never as stable as people wanted them to be.&lt;/p&gt;

&lt;p&gt;IPv6 makes that harder to ignore.&lt;/p&gt;

&lt;p&gt;It forces a more honest model of what a network signal is.&lt;/p&gt;

&lt;p&gt;It is evidence.&lt;/p&gt;

&lt;p&gt;It is context.&lt;/p&gt;

&lt;p&gt;It is sometimes a strong clue.&lt;/p&gt;

&lt;p&gt;It is not identity by default.&lt;/p&gt;

&lt;p&gt;That matters because the market does not only pay for fraud that gets through. It also pays for misclassification, suppressed reach, broken partner trust, and slow dispute cycles caused by bad assumptions.&lt;/p&gt;

&lt;p&gt;So the real business question is no longer just, "Can this IP be flagged?"&lt;/p&gt;

&lt;p&gt;It is, "What happens to revenue, delivery, trust, and reconciliation when we act on that signal?"&lt;/p&gt;

&lt;p&gt;That is the right question for CTV.&lt;/p&gt;

&lt;p&gt;And increasingly, it is the right question for the broader ad ecosystem too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pixalate, AdFraud IOC-DB - IPv6 Addresses, May 2026 workbook reviewed from the IOC database export&lt;/li&gt;
&lt;li&gt;RFC 8981, Temporary Address Extensions for Stateless Address Autoconfiguration in IPv6, &lt;a href="https://www.rfc-editor.org/rfc/rfc8981" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc8981&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Source note
&lt;/h2&gt;

&lt;p&gt;The IPv6 IOC workbook reflects Pixalate's published watchlist data and disclaimer language for internal operational use. It is useful as an indicator set, not as a standalone market estimate or a definitive claim about any ISP, subscriber, or platform.&lt;/p&gt;

</description>
      <category>adtech</category>
      <category>ctv</category>
      <category>fraud</category>
    </item>
    <item>
      <title>How vastlint's Validation Rules Were Extracted from the VAST PDF Specs and XSD Schemas</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Fri, 01 May 2026 15:39:18 +0000</pubDate>
      <link>https://dev.to/aleksuix/how-vastlints-validation-rules-were-extracted-from-the-vast-pdf-specs-and-xsd-schemas-5b1</link>
      <guid>https://dev.to/aleksuix/how-vastlints-validation-rules-were-extracted-from-the-vast-pdf-specs-and-xsd-schemas-5b1</guid>
      <description>&lt;p&gt;The IAB Tech Lab publishes VAST as two artifacts: a PDF specification document and a set of XSD schema files. The PDF is the normative authority. It defines what a conformant VAST response must, should, and may contain. The XSD describes the document structure in machine-readable form but has gaps; it does not cover business logic, ordering constraints, or anything involving URLs. Building a linter against VAST meant reading both and treating them as complementary sources of truth, not interchangeable ones.&lt;/p&gt;

&lt;p&gt;The PDF extraction was manual and slow. Each version of the spec (2.0, 3.0, 4.0, 4.1, 4.2, 4.3) was read cover to cover and every normative statement was tagged by element, attribute, and constraint type. A rule like "InLine must contain AdTitle" comes directly from section language in the 2.0 spec that predates all subsequent versions and has never been relaxed. Deprecations were tracked version by version: conditionalAd deprecated in 4.1, Survey deprecated in 4.1, the fullscreen/exitFullscreen tracking events removed in 4.0. Each one becomes a versioned rule rather than a blanket error, so a VAST 3.0 document is not penalised for things that only matter in 4.x.&lt;/p&gt;

&lt;p&gt;The XSD pass was more systematic. The schemas enumerate valid attribute values, allowed child elements, and content models for every element across spec versions. These became structural rules: unknown attributes, unknown child elements, invalid enum values for things like delivery, adType, renderingMode, and tracking event names. A third category of rules does not come from either the PDF or the XSD. It comes from what goes wrong in production. HTTPS enforcement on MediaFile and tracking URLs, duplicate Impression URLs causing billing disputes, missing quartile tracking events, the VPAID-in-CTV footgun: these are rules sourced from patterns that show up repeatedly in real VAST traffic. The source tag on each rule (VastSpec, VastXsd, IndustryBestPractice, Inferred, etc.) reflects exactly where it came from.&lt;/p&gt;

&lt;p&gt;The full rule set is listed below. Each rule ID links to its dedicated page on vastlint.org with the exact spec section, severity, fix guidance, and examples.&lt;/p&gt;




&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule ID&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-root-element/" rel="noopener noreferrer"&gt;VAST-2.0-root-element&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Root element must be &lt;code&gt;&amp;lt;VAST&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-root-version/" rel="noopener noreferrer"&gt;VAST-2.0-root-version&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;VAST&amp;gt;&lt;/code&gt; must have a version attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-root-version-value/" rel="noopener noreferrer"&gt;VAST-2.0-root-version-value&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;VAST version attribute must be a recognised version string&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-root-has-ad-or-error/" rel="noopener noreferrer"&gt;VAST-2.0-root-has-ad-or-error&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;VAST&amp;gt;&lt;/code&gt; must contain at least one &lt;code&gt;&amp;lt;Ad&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;Error&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-wrapper-root-error/" rel="noopener noreferrer"&gt;VAST-4.0-wrapper-root-error&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;VAST&amp;gt;&lt;/code&gt; root contains both &lt;code&gt;&amp;lt;Ad&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Error&amp;gt;&lt;/code&gt; elements (invalid per VAST 4.0)&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-ad-has-inline-or-wrapper/" rel="noopener noreferrer"&gt;VAST-2.0-ad-has-inline-or-wrapper&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Each &lt;code&gt;&amp;lt;Ad&amp;gt;&lt;/code&gt; must contain exactly one &lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;Wrapper&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-inline-adsystem/" rel="noopener noreferrer"&gt;VAST-2.0-inline-adsystem&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;AdSystem&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-inline-adtitle/" rel="noopener noreferrer"&gt;VAST-2.0-inline-adtitle&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;AdTitle&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-inline-impression/" rel="noopener noreferrer"&gt;VAST-2.0-inline-impression&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; must contain at least one &lt;code&gt;&amp;lt;Impression&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-inline-creatives/" rel="noopener noreferrer"&gt;VAST-2.0-inline-creatives&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;Creatives&amp;gt;&lt;/code&gt; with at least one &lt;code&gt;&amp;lt;Creative&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-adservingid-present/" rel="noopener noreferrer"&gt;VAST-4.1-adservingid-present&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;AdServingId&amp;gt;&lt;/code&gt; (VAST 4.1+)&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-universaladid-present/" rel="noopener noreferrer"&gt;VAST-4.0-universaladid-present&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Creative&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;UniversalAdId&amp;gt;&lt;/code&gt; (VAST 4.0+)&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-universaladid-idregistry/" rel="noopener noreferrer"&gt;VAST-4.0-universaladid-idregistry&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;UniversalAdId&amp;gt;&lt;/code&gt; must have an idRegistry attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-universaladid-idvalue/" rel="noopener noreferrer"&gt;VAST-4.0-universaladid-idvalue&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;UniversalAdId&amp;gt;&lt;/code&gt; missing required idValue attribute (VAST 4.0)&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-universaladid-idvalue-removed/" rel="noopener noreferrer"&gt;VAST-4.1-universaladid-idvalue-removed&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;UniversalAdId&amp;gt;&lt;/code&gt; idValue attribute was removed in VAST 4.1&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-universaladid-content/" rel="noopener noreferrer"&gt;VAST-4.1-universaladid-content&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;UniversalAdId&amp;gt;&lt;/code&gt; must have text content in VAST 4.1+&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-linear-duration/" rel="noopener noreferrer"&gt;VAST-2.0-linear-duration&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Linear&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-linear-mediafiles/" rel="noopener noreferrer"&gt;VAST-2.0-linear-mediafiles&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Linear&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;MediaFiles&amp;gt;&lt;/code&gt; with at least one &lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-delivery/" rel="noopener noreferrer"&gt;VAST-2.0-mediafile-delivery&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; must have a delivery attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-delivery-enum/" rel="noopener noreferrer"&gt;VAST-2.0-mediafile-delivery-enum&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; delivery must be "progressive" or "streaming"&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-type/" rel="noopener noreferrer"&gt;VAST-2.0-mediafile-type&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; must have a type attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-dimensions/" rel="noopener noreferrer"&gt;VAST-2.0-mediafile-dimensions&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; must have width and height attributes&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-adsystem/" rel="noopener noreferrer"&gt;VAST-2.0-wrapper-adsystem&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Wrapper&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;AdSystem&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-impression/" rel="noopener noreferrer"&gt;VAST-2.0-wrapper-impression&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Wrapper&amp;gt;&lt;/code&gt; must contain at least one &lt;code&gt;&amp;lt;Impression&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-vastadtaguri/" rel="noopener noreferrer"&gt;VAST-2.0-wrapper-vastadtaguri&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Wrapper&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;VASTAdTagURI&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-companion-resource/" rel="noopener noreferrer"&gt;VAST-2.0-companion-resource&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Companion&amp;gt;&lt;/code&gt; must contain at least one StaticResource, IFrameResource, or HTMLResource&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-nonlinear-resource/" rel="noopener noreferrer"&gt;VAST-2.0-nonlinear-resource&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;InLine &lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; must contain at least one StaticResource, IFrameResource, or HTMLResource&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-depth/" rel="noopener noreferrer"&gt;VAST-2.0-wrapper-depth&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Wrapper chain depth exceeds the configured maximum&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-ad-sequence/" rel="noopener noreferrer"&gt;VAST-2.0-ad-sequence&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Mixed use of sequence attribute across &lt;code&gt;&amp;lt;Ad&amp;gt;&lt;/code&gt; elements in a pod&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-duration-format/" rel="noopener noreferrer"&gt;VAST-2.0-duration-format&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt; value does not match HH:MM:SS[.mmm] format&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-text-only-element/" rel="noopener noreferrer"&gt;VAST-2.0-text-only-element&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Text-only element contains a child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-unknown-attribute/" rel="noopener noreferrer"&gt;VAST-2.0-unknown-attribute&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Element has an attribute not defined in the VAST spec&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-inline-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-inline-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-wrapper-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Wrapper&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-creatives-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-creatives-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Creatives&amp;gt;&lt;/code&gt; may only contain &lt;code&gt;&amp;lt;Creative&amp;gt;&lt;/code&gt; elements&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-creative-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-creative-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Creative&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-linear-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-linear-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Linear&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-trackingevents-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-trackingevents-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;TrackingEvents&amp;gt;&lt;/code&gt; may only contain &lt;code&gt;&amp;lt;Tracking&amp;gt;&lt;/code&gt; elements&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafiles-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-mediafiles-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFiles&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-extensions-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-extensions-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Extensions&amp;gt;&lt;/code&gt; may only contain &lt;code&gt;&amp;lt;Extension&amp;gt;&lt;/code&gt; elements&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-videoclicks-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-videoclicks-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;VideoClicks&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-nonlinearads-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-nonlinearads-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-nonlinear-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-nonlinear-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-companionads-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-companionads-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;CompanionAds&amp;gt;&lt;/code&gt; may only contain &lt;code&gt;&amp;lt;Companion&amp;gt;&lt;/code&gt; elements&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-companion-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-companion-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Companion&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-creativeextensions-unknown-child/" rel="noopener noreferrer"&gt;VAST-2.0-creativeextensions-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;CreativeExtensions&amp;gt;&lt;/code&gt; may only contain &lt;code&gt;&amp;lt;CreativeExtension&amp;gt;&lt;/code&gt; elements&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-extension-misplaced-element/" rel="noopener noreferrer"&gt;VAST-2.0-extension-misplaced-element&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Extension&amp;gt;&lt;/code&gt; contains an element that has a dedicated location in the VAST spec&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-creative-extension-misplaced-element/" rel="noopener noreferrer"&gt;VAST-2.0-creative-extension-misplaced-element&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;CreativeExtension&amp;gt;&lt;/code&gt; contains an element that has a dedicated location in the VAST spec&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-mediafile-https/" rel="noopener noreferrer"&gt;VAST-2.0-mediafile-https&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; URL uses HTTP instead of HTTPS, blocked by mixed-content policy on secure inventory&lt;/td&gt;
&lt;td&gt;Industry Best Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-tracking-https/" rel="noopener noreferrer"&gt;VAST-2.0-tracking-https&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Tracking or click URL uses HTTP instead of HTTPS, blocked by mixed-content policy, measurement signal lost&lt;/td&gt;
&lt;td&gt;Industry Best Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-url-empty/" rel="noopener noreferrer"&gt;VAST-2.0-url-empty&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;URL field is empty&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-url-invalid/" rel="noopener noreferrer"&gt;VAST-2.0-url-invalid&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;URL field does not appear to be a valid URI&lt;/td&gt;
&lt;td&gt;RFC 3986&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-parse-error/" rel="noopener noreferrer"&gt;VAST-2.0-parse-error&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;XML parse error, document may be malformed&lt;/td&gt;
&lt;td&gt;XML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-version-mismatch/" rel="noopener noreferrer"&gt;VAST-2.0-version-mismatch&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Declared version does not match structural signals&lt;/td&gt;
&lt;td&gt;Inferred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-duplicate-impression/" rel="noopener noreferrer"&gt;VAST-2.0-duplicate-impression&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Duplicate &lt;code&gt;&amp;lt;Impression&amp;gt;&lt;/code&gt; URL within the same &lt;code&gt;&amp;lt;Ad&amp;gt;&lt;/code&gt;, causes double-counted billing and disputes&lt;/td&gt;
&lt;td&gt;Industry Best Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-nonlinear-dimensions/" rel="noopener noreferrer"&gt;VAST-2.0-nonlinear-dimensions&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; missing width or height&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-companion-dimensions/" rel="noopener noreferrer"&gt;VAST-2.0-companion-dimensions&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Companion&amp;gt;&lt;/code&gt; missing width or height&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-flash-mediafile/" rel="noopener noreferrer"&gt;VAST-2.0-flash-mediafile&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flash-based MediaFile type is no longer supported&lt;/td&gt;
&lt;td&gt;Inferred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-linear-tracking-quartiles/" rel="noopener noreferrer"&gt;VAST-2.0-linear-tracking-quartiles&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Linear&amp;gt;&lt;/code&gt; has no standard quartile tracking events, impression serves but measurement system receives no signal&lt;/td&gt;
&lt;td&gt;Industry Best Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icons-unknown-child/" rel="noopener noreferrer"&gt;VAST-3.0-icons-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icons&amp;gt;&lt;/code&gt; may only contain &lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; elements&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-unknown-child/" rel="noopener noreferrer"&gt;VAST-3.0-icon-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-iconclicks-unknown-child/" rel="noopener noreferrer"&gt;VAST-3.0-iconclicks-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;IconClicks&amp;gt;&lt;/code&gt; contains an unrecognised child element&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-progress-offset/" rel="noopener noreferrer"&gt;VAST-3.0-progress-offset&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Tracking event="progress"&amp;gt;&lt;/code&gt; requires an offset attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-progress-offset-format/" rel="noopener noreferrer"&gt;VAST-3.0-progress-offset-format&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Tracking progress offset does not match required format&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-attrs/" rel="noopener noreferrer"&gt;VAST-3.0-icon-attrs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Icon missing recommended attributes (program/width/height/position)&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-program/" rel="noopener noreferrer"&gt;VAST-3.0-icon-program&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; missing required program attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-width/" rel="noopener noreferrer"&gt;VAST-3.0-icon-width&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; missing required width attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-height/" rel="noopener noreferrer"&gt;VAST-3.0-icon-height&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; missing required height attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-xposition/" rel="noopener noreferrer"&gt;VAST-3.0-icon-xposition&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; missing required xPosition attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-yposition/" rel="noopener noreferrer"&gt;VAST-3.0-icon-yposition&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; missing required yPosition attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-icon-resource/" rel="noopener noreferrer"&gt;VAST-3.0-icon-resource&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Icon&amp;gt;&lt;/code&gt; must have at least one resource element&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-skipoffset-format/" rel="noopener noreferrer"&gt;VAST-3.0-skipoffset-format&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Linear skipoffset does not match HH:MM:SS[.mmm] or n% format&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-skip-event-no-skipoffset/" rel="noopener noreferrer"&gt;VAST-3.0-skip-event-no-skipoffset&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;skip tracking event present but Linear has no skipoffset attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-minmaxbitrate-pair/" rel="noopener noreferrer"&gt;VAST-3.0-minmaxbitrate-pair&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; must have both minBitrate and maxBitrate or neither&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-bitrate-conflict/" rel="noopener noreferrer"&gt;VAST-3.0-bitrate-conflict&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; has both bitrate and minBitrate/maxBitrate&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-pricing-model/" rel="noopener noreferrer"&gt;VAST-3.0-pricing-model&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Pricing&amp;gt;&lt;/code&gt; missing required model attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-pricing-currency/" rel="noopener noreferrer"&gt;VAST-3.0-pricing-currency&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Pricing&amp;gt;&lt;/code&gt; missing required currency attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-pricing-model-case/" rel="noopener noreferrer"&gt;VAST-3.0-pricing-model-case&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Pricing&amp;gt;&lt;/code&gt; model value should be lowercase in VAST 3.0 (cpm/cpc/cpe/cpv)&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-pricing-currency-format/" rel="noopener noreferrer"&gt;VAST-3.0-pricing-currency-format&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Pricing&amp;gt;&lt;/code&gt; currency attribute must be a 3-letter ISO-4217 code&lt;/td&gt;
&lt;td&gt;ISO 4217&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-3.0-companion-required-attr/" rel="noopener noreferrer"&gt;VAST-3.0-companion-required-attr&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;CompanionAds&amp;gt;&lt;/code&gt; required attribute must be all, any, or none&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-conditionalad/" rel="noopener noreferrer"&gt;VAST-4.0-conditionalad&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;conditionalAd attribute is deprecated as of VAST 4.1&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-wrapper-clickthrough/" rel="noopener noreferrer"&gt;VAST-4.0-wrapper-clickthrough&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ClickThrough&amp;gt;&lt;/code&gt; inside Wrapper &lt;code&gt;&amp;lt;VideoClicks&amp;gt;&lt;/code&gt; was removed in VAST 4.0 (re-allowed in 4.2)&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-tracking-event-removed/" rel="noopener noreferrer"&gt;VAST-4.0-tracking-event-removed&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;fullscreen/exitFullscreen tracking events were removed in VAST 4.0&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-mediafile-apiframework/" rel="noopener noreferrer"&gt;VAST-4.0-mediafile-apiframework&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFile apiFramework&amp;gt;&lt;/code&gt; is deprecated in VAST 4.0+, use &lt;code&gt;&amp;lt;InteractiveCreativeFile&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-category-authority/" rel="noopener noreferrer"&gt;VAST-4.0-category-authority&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Category&amp;gt;&lt;/code&gt; missing required authority attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-companion-clicktracking-id/" rel="noopener noreferrer"&gt;VAST-4.0-companion-clicktracking-id&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;CompanionClickTracking&amp;gt;&lt;/code&gt; missing required id attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.0-interactive-creative-no-api/" rel="noopener noreferrer"&gt;VAST-4.0-interactive-creative-no-api&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InteractiveCreativeFile&amp;gt;&lt;/code&gt; should have an apiFramework attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-adservingid-present/" rel="noopener noreferrer"&gt;VAST-4.1-adservingid-present&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InLine&amp;gt;&lt;/code&gt; must contain &lt;code&gt;&amp;lt;AdServingId&amp;gt;&lt;/code&gt; (VAST 4.1+)&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-ad-serving-id-empty/" rel="noopener noreferrer"&gt;VAST-4.1-ad-serving-id-empty&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;AdServingId&amp;gt;&lt;/code&gt; is present but empty&lt;/td&gt;
&lt;td&gt;Inferred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-survey-deprecated/" rel="noopener noreferrer"&gt;VAST-4.1-survey-deprecated&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Survey&amp;gt;&lt;/code&gt; is deprecated as of VAST 4.1&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-vpaid-apiframework/" rel="noopener noreferrer"&gt;VAST-4.1-vpaid-apiframework&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;apiFramework="VPAID" is deprecated as of VAST 4.1&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-mezzanine-delivery/" rel="noopener noreferrer"&gt;VAST-4.1-mezzanine-delivery&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Mezzanine&amp;gt;&lt;/code&gt; missing required delivery attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-mezzanine-type/" rel="noopener noreferrer"&gt;VAST-4.1-mezzanine-type&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Mezzanine&amp;gt;&lt;/code&gt; missing required type attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-mezzanine-width/" rel="noopener noreferrer"&gt;VAST-4.1-mezzanine-width&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Mezzanine&amp;gt;&lt;/code&gt; missing required width attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-mezzanine-height/" rel="noopener noreferrer"&gt;VAST-4.1-mezzanine-height&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Mezzanine&amp;gt;&lt;/code&gt; missing required height attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-mezzanine-recommended/" rel="noopener noreferrer"&gt;VAST-4.1-mezzanine-recommended&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;MediaFiles&amp;gt;&lt;/code&gt; has no &lt;code&gt;&amp;lt;Mezzanine&amp;gt;&lt;/code&gt;, ad-stitching servers may reject in CTV/SSAI contexts&lt;/td&gt;
&lt;td&gt;Industry Best Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-verification-no-resource/" rel="noopener noreferrer"&gt;VAST-4.1-verification-no-resource&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Verification&amp;gt;&lt;/code&gt; should have JavaScriptResource or ExecutableResource&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-verification-vendor/" rel="noopener noreferrer"&gt;VAST-4.1-verification-vendor&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;Verification&amp;gt;&lt;/code&gt; is missing required vendor attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-blockedadcategories-no-authority/" rel="noopener noreferrer"&gt;VAST-4.1-blockedadcategories-no-authority&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;BlockedAdCategories&amp;gt;&lt;/code&gt; should have authority attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-interactive-creative-type/" rel="noopener noreferrer"&gt;VAST-4.1-interactive-creative-type&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InteractiveCreativeFile&amp;gt;&lt;/code&gt; should have a type attribute identifying the MIME type&lt;/td&gt;
&lt;td&gt;IANA Media Types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-js-resource-apiframework/" rel="noopener noreferrer"&gt;VAST-4.1-js-resource-apiframework&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;JavaScriptResource&amp;gt;&lt;/code&gt; is missing required apiFramework attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-exec-resource-apiframework/" rel="noopener noreferrer"&gt;VAST-4.1-exec-resource-apiframework&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ExecutableResource&amp;gt;&lt;/code&gt; is missing required apiFramework attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-exec-resource-type/" rel="noopener noreferrer"&gt;VAST-4.1-exec-resource-type&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ExecutableResource&amp;gt;&lt;/code&gt; is missing required type attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-tracking-event-value/" rel="noopener noreferrer"&gt;VAST-4.1-tracking-event-value&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Tracking event attribute not in the valid set for this VAST version&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-adtype-value/" rel="noopener noreferrer"&gt;VAST-4.1-adtype-value&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Ad adType must be video, audio, or hybrid&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-companion-renderingmode-value/" rel="noopener noreferrer"&gt;VAST-4.1-companion-renderingmode-value&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Companion renderingMode must be default, end-card, or concurrent&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.1-vpaid-in-interactive-context/" rel="noopener noreferrer"&gt;VAST-4.1-vpaid-in-interactive-context&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;VPAID MediaFile alongside InteractiveCreativeFile, VPAID unsupported in CTV, zero fill&lt;/td&gt;
&lt;td&gt;Industry Best Practice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.2-closedcaptionfiles-unknown-child/" rel="noopener noreferrer"&gt;VAST-4.2-closedcaptionfiles-unknown-child&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;ClosedCaptionFiles&amp;gt;&lt;/code&gt; may only contain &lt;code&gt;&amp;lt;ClosedCaptionFile&amp;gt;&lt;/code&gt; elements&lt;/td&gt;
&lt;td&gt;VAST XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.2-icon-fallback-image-width-height/" rel="noopener noreferrer"&gt;VAST-4.2-icon-fallback-image-width-height&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;IconClickFallbackImage&amp;gt;&lt;/code&gt; should have width and height attributes&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/rules/VAST-4.3-js-resource-browser-optional/" rel="noopener noreferrer"&gt;VAST-4.3-js-resource-browser-optional&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;JavaScriptResource&amp;gt;&lt;/code&gt; should have a browserOptional attribute&lt;/td&gt;
&lt;td&gt;VAST Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.0-simid-type-required/" rel="noopener noreferrer"&gt;SIMID-1.0-simid-type-required&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InteractiveCreativeFile apiFramework="SIMID"&amp;gt;&lt;/code&gt; must have type="text/html"&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.0-simid-url-empty/" rel="noopener noreferrer"&gt;SIMID-1.0-simid-url-empty&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InteractiveCreativeFile apiFramework="SIMID"&amp;gt;&lt;/code&gt; must contain a non-empty URL&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.0-simid-url-https/" rel="noopener noreferrer"&gt;SIMID-1.0-simid-url-https&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InteractiveCreativeFile apiFramework="SIMID"&amp;gt;&lt;/code&gt; URL must use HTTPS&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.0-simid-variable-duration-value/" rel="noopener noreferrer"&gt;SIMID-1.0-simid-variable-duration-value&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;InteractiveCreativeFile&amp;gt;&lt;/code&gt; variableDuration attribute must be "true" when present&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.0-simid-mediafile-required/" rel="noopener noreferrer"&gt;SIMID-1.0-simid-mediafile-required&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Linear SIMID ad must include a video/audio &lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; alongside the interactive creative&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.1-nonlinear-simid-no-iframe/" rel="noopener noreferrer"&gt;SIMID-1.1-nonlinear-simid-no-iframe&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinear apiFramework="SIMID"&amp;gt;&lt;/code&gt; must contain an &lt;code&gt;&amp;lt;IFrameResource&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.1-iframe-simid-type-required/" rel="noopener noreferrer"&gt;SIMID-1.1-iframe-simid-type-required&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;IFrameResource&amp;gt;&lt;/code&gt; in SIMID &lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; should have type="text/html"&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.1-iframe-simid-url-empty/" rel="noopener noreferrer"&gt;SIMID-1.1-iframe-simid-url-empty&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;IFrameResource&amp;gt;&lt;/code&gt; in SIMID &lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; must contain a non-empty URL&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://vastlint.org/docs/simid-rules/SIMID-1.1-iframe-simid-url-https/" rel="noopener noreferrer"&gt;SIMID-1.1-iframe-simid-url-https&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;IFrameResource&amp;gt;&lt;/code&gt; in SIMID &lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; URL must use HTTPS&lt;/td&gt;
&lt;td&gt;SIMID Spec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>adtech</category>
      <category>opensource</category>
      <category>rust</category>
    </item>
    <item>
      <title>I Built a VS Code Extension That Validates VAST XML as You Type</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Mon, 20 Apr 2026 04:13:48 +0000</pubDate>
      <link>https://dev.to/aleksuix/i-built-a-vs-code-extension-that-validates-vast-xml-as-you-type-2ln</link>
      <guid>https://dev.to/aleksuix/i-built-a-vs-code-extension-that-validates-vast-xml-as-you-type-2ln</guid>
      <description>&lt;p&gt;If you work in ad tech you've probably seen VAST XML. It's the IAB standard that carries every video and CTV ad impression: the tag that tells a player what to show, where to fire tracking pixels, and how to handle wrappers. $30B+ in annual US ad spend runs through it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw258f958f7g4cw5ps19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkw258f958f7g4cw5ps19.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's also surprisingly easy to ship a broken one. The spec is six versions deep (2.0 through 4.3), touches several other standards (XML, RFC 3986, IANA media types, ISO 4217), and the tooling for validating it has historically been: read the PDF, or find out from your partner's discrepancy report.&lt;/p&gt;

&lt;p&gt;So I built vastlint, an open-source VAST linter with 108 rules written in Rust. Last week I shipped the VS Code extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Install it from the VS Code Marketplace. Open any .xml file that contains a VAST tag.&lt;/p&gt;

&lt;p&gt;You get squiggles on the exact element or attribute that violates the spec, hover tooltips with the rule ID and the fix, and Problems panel integration with file path and line number. VAST version is auto-detected. No config, no CLI, no external service. The validator runs entirely in-process via WebAssembly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually catches
&lt;/h2&gt;

&lt;p&gt;Here's a broken tag in the editor. This one is missing  on the  element, a required field since VAST 2.0 that's easy to drop when templating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Linear&amp;gt;&lt;/span&gt;
  &lt;span class="c"&gt;&amp;lt;!-- Duration is required — VAST-2.0-linear-duration --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;MediaFiles&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;MediaFile&lt;/span&gt; &lt;span class="na"&gt;delivery=&lt;/span&gt;&lt;span class="s"&gt;"progressive"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"360"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="cp"&gt;&amp;lt;![CDATA[https://example.com/video.mp4]]&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/MediaFile&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/MediaFiles&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Linear&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The squiggle lands on . Hovering shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VAST-2.0-linear-duration · Error
&amp;lt;Linear&amp;gt; is missing required &amp;lt;Duration&amp;gt;
IAB VAST 2.0 §3.7.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other rules are less obvious than a missing required element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VAST-2.0-mediafile-https: HTTP  URL on HTTPS inventory (mixed-content, ad won't play)&lt;/li&gt;
&lt;li&gt;VAST-2.0-duration-format:  is 00:30 instead of 00:00:30 (strict players reject this, others misfire tracking events at the wrong times)&lt;/li&gt;
&lt;li&gt;VAST-4.1-mezzanine-recommended: no  on a 4.1 tag (SSAI platforms reject these for CTV)&lt;/li&gt;
&lt;li&gt;VAST-4.1-vpaid-apiframework: VPAID apiFramework in a 4.1 tag (deprecated, Roku and Fire TV skip it)&lt;/li&gt;
&lt;li&gt;VAST-2.0-duplicate-impression: same impression URL appears twice (both sides double-count, neither report is technically wrong, they just never match)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core library is vastlint-core on crates.io, a pure Rust library with no external dependencies. The VS Code extension loads it as a WebAssembly module so validation runs locally with no network calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string in -&amp;gt; parse XML -&amp;gt; detect VAST version -&amp;gt; run rule set -&amp;gt; issues out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical validation time on a real-world CTV tag is under 100 microseconds. The WASM overhead adds a bit in the editor context but it's imperceptible while typing.&lt;/p&gt;

&lt;p&gt;The same core powers a CLI (cargo install vastlint), a REST API on RapidAPI, an MCP server for Claude/Cursor/Windsurf, and the online validator at vastlint.org.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-rule configuration
&lt;/h2&gt;

&lt;p&gt;Not every rule applies to every context. Mezzanine is noise for browser-only inventory but critical for Roku. You can tune per-rule in settings.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vastlint.rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"VAST-4.1-mezzanine-recommended"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"VAST-2.0-mediafile-https"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"VAST-4.1-vpaid-apiframework"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Valid levels: "error", "warning", "info", "off".&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;I ran into this problem at work. The available tooling was either proprietary, behind a paywall, or only checked structure against the XSD. The spec is public. The failure modes are documented. There was no good reason an open-source implementation didn't exist.&lt;/p&gt;

&lt;p&gt;The other thing: these errors get caught way too late. A malformed tag makes it through QA, gets sent to the partner, fails on their platform, and shows up three weeks later in a discrepancy report. The campaign has already run, the impressions are gone. Catching it in the editor before the file leaves your machine is the right fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=aleksUIX.vastlint" rel="noopener noreferrer"&gt;Install from VS Code Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://vastlint.org/validate" rel="noopener noreferrer"&gt;Online validator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vscode</category>
      <category>adtech</category>
      <category>opensource</category>
      <category>rust</category>
    </item>
    <item>
      <title>The 10 VAST Errors That Silently Kill Your CTV Ad Revenue</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Wed, 08 Apr 2026 01:35:58 +0000</pubDate>
      <link>https://dev.to/aleksuix/the-10-vast-errors-that-silently-kill-your-ctv-ad-revenue-25i8</link>
      <guid>https://dev.to/aleksuix/the-10-vast-errors-that-silently-kill-your-ctv-ad-revenue-25i8</guid>
      <description>&lt;p&gt;&lt;a href="https://medium.com/@aleksander-sekowski/the-10-vast-errors-that-silently-kill-your-ctv-ad-revenue" rel="noopener noreferrer"&gt;Originally published on Medium.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Samsung TV Plus just crossed 100 million monthly active users. Netflix ended 2024 with 277.6 million subscribers worldwide, 70 million on its ad-supported tier. Amazon Prime Video had 230 million users. Roku reported 89.8 million active accounts in Q4 2024. The ad spend flowing through all of it: $33 billion in the US alone in 2024 runs on VAST XML.&lt;/p&gt;

&lt;p&gt;Every impression, every pixel, every billing event depends on a tag being parsed correctly by players, ad servers, and measurement vendors that were built by different teams to different tolerances. Penthera found that 40% of VOD ads fail. Google Ad Manager flags any gap larger than 25% between impressions served and impressions counted as a sign of systemic VAST errors. Most of these failures trace back to a handful of tag-level mistakes that are trivially detectable before the ad ever reaches a player.&lt;/p&gt;

&lt;p&gt;I built an open-source tool that catches all errors mentioned below: vastlint. 108 rules across VAST 2.0 through 4.3. Free, no account, runs in CI in about 100 microseconds per tag.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/aleksUIX" rel="noopener noreferrer"&gt;
        aleksUIX
      &lt;/a&gt; / &lt;a href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;
        vastlint
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;vastlint&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://crates.io/crates/vastlint-cli" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2beb5b5855b57656861b67861574aecb4cee782d97520ddfa281e7698b6b629d/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f762f766173746c696e742d636c692e7376673f6c6162656c3d6372617465732e696f" alt="crates.io"&gt;&lt;/a&gt;
&lt;a href="https://crates.io/crates/vastlint-core" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a72af7d00fdcded39a6a5c8dff3c947cabcea697d93e6ca79d588d146ccdae79/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f762f766173746c696e742d636f72652e7376673f6c6162656c3d766173746c696e742d636f7265" alt="crates.io"&gt;&lt;/a&gt;
&lt;a href="https://docs.rs/vastlint-core" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2e66f1a456a606735a90cc9cc47c2b60e78d103706a464beed69a6b0912e8b93/68747470733a2f2f646f63732e72732f766173746c696e742d636f72652f62616467652e737667" alt="docs.rs"&gt;&lt;/a&gt;
&lt;a href="https://www.npmjs.com/package/vastlint" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f57064e4b17dcbd17fc10fce20ffadc141f9da635abec86d173144a8c552d152/68747470733a2f2f696d672e736869656c64732e696f2f6e706d2f762f766173746c696e742e7376673f6c6162656c3d6e706d" alt="npm"&gt;&lt;/a&gt;
&lt;a href="https://github.com/aleksUIX/vastlint/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/25a5325411ddfdd46094f1e6a698dac67891eaf213c5891a8a6c76f3466091fe/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f6c2f766173746c696e742d636c692e737667" alt="license"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A VAST XML validator. Checks ad tags against the IAB Tech Lab VAST specification so you don't have to read it. Over $30 billion in annual CTV and video ad spend flows through VAST XML, and malformed tags are one of the most common causes of lost impressions, broken tracking, and revenue discrepancies between platforms. There is no widely adopted open-source tool that validates VAST XML against the full IAB specification across all published versions.&lt;/p&gt;
&lt;p&gt;Validates VAST documents against:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://iabtechlab.com/standards/vast/" rel="nofollow noopener noreferrer"&gt;IAB Tech Lab VAST&lt;/a&gt; 2.0, 3.0, 4.0, 4.1, 4.2, and 4.3 -- structural rules derived from the published XSD schemas and spec prose&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/TR/xml/" rel="nofollow noopener noreferrer"&gt;W3C XML 1.0&lt;/a&gt; well-formedness (malformed documents are rejected before any spec rule runs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rfc-editor.org/rfc/rfc3986" rel="nofollow noopener noreferrer"&gt;RFC 3986&lt;/a&gt; URI syntax (all URL fields)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iana.org/assignments/media-types/" rel="nofollow noopener noreferrer"&gt;IANA Media Types&lt;/a&gt; (MediaFile and resource MIME types)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.iso.org/iso-4217-currency-codes.html" rel="nofollow noopener noreferrer"&gt;ISO 4217&lt;/a&gt; currency codes (Pricing elements)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.ad-id.org/" rel="nofollow noopener noreferrer"&gt;Ad-ID&lt;/a&gt; registry format (UniversalAdId)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;108 rules across required fields, schema validation, structural correctness…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/aleksUIX/vastlint" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Here are the ten I see most often.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Missing  attributes&lt;br&gt;
delivery, type, width, height — all four are required. Leave one out and the player guesses. CTV devices don't guess well. A tag that plays fine in Chrome fails on Roku because the device won't infer the delivery method from context. The XML is valid. The spec is violated. The blank slot is yours.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;HTTP media URLs on HTTPS inventory&lt;br&gt;
A  pointing at http:// gets blocked by mixed-content policies on any HTTPS page or app. The ad doesn't play, the impression doesn't fire, fill rate drops. The tag is valid XML. It's technically spec-compliant. It just won't work on most real inventory. This one is invisible to any validator that only checks structure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bad  format&lt;br&gt;
The spec requires HH:MM:SS or HH:MM:SS.mmm. Tags show up with 00:30, 30s, plain 30, or empty. Strict players reject the creative outright. Loose players guess wrong and fire progress events — firstQuartile, midpoint, complete — at the wrong times. Your reporting looks fine but the measurement is off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No  element&lt;br&gt;
No  URL means the ad plays but nobody gets paid. The DSP doesn't know it rendered, the SSP can't bill. Both sides pull discrepancy reports and argue. On wrapper chains it's worse. A missing impression at any level means that link in the transaction goes unrecorded. The outer wrapper fires, the inner wrapper doesn't, the numbers never reconcile.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrapper without &lt;br&gt;
A  with no redirect URL is a dead end. The slot goes unfilled, no error fires, the demand partner reports zero delivery. Usually happens when a template renders a wrapper variant but the URL variable is null or empty. At scale, that's thousands of lost impressions per minute with nothing in the logs pointing at the cause.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt; missing or malformed&lt;br&gt;
Required on every  since VAST 4.0. Needs an idRegistry attribute and a non-empty value. Without it, frequency capping and competitive separation break. The ad server can't tell two creatives apart, so it can't enforce exclusion rules between competing brands or cap a user who's already seen the ad five times. The ads still serve. The contracts you're supposed to be honoring don't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VPAID apiFramework on VAST 4.1+ tags&lt;br&gt;
VPAID was deprecated in VAST 4.1. Google, Amazon, and most major SSPs block VPAID execution entirely. Tags declaring apiFramework="VPAID" pass XML validation. They may even pass some linting tools that only check structure. They get filtered before reaching a screen. Fill rate craters and the root cause is a single attribute value that no one thought to check.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tracking event typos&lt;br&gt;
The spec defines a fixed set: creativeView, start, firstQuartile, midpoint, thirdQuartile, complete, and so on. Misspell one — Midpoint, mid_point, MidPoint — and no player fires it. The pixel silently never calls. VAST 4.0 also removed fullscreen and exitFullscreen. Tags copied from 3.0 templates still carry those events. They worked. They don't anymore.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Malformed XML&lt;br&gt;
Unescaped &amp;amp;, unclosed elements, bad UTF-8. Common when tags are assembled by string concatenation instead of an XML library. Many ad servers have lenient parsers that silently fix this. CTV devices don't. You debug a blank slot for an afternoon and the problem is &amp;amp; instead of &amp;amp; in a click URL buried three wrappers deep.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Version declaration doesn't match content&lt;br&gt;
The tag says  but contains  and , which are 4.x elements. The player applies 2.0 parsing rules and skips what it doesn't recognize. Happens constantly when templates are copied between projects without updating the version attribute. Everything looks fine in dev. In production the player is running a different code path than you think.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of these is detectable before the tag reaches a player. None of them require a live ad server, a real device, or a debugging session.&lt;/p&gt;

</description>
      <category>adtech</category>
      <category>rust</category>
      <category>node</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
