A trafficking export can look perfect in Chrome. The progressive MP4 starts, quartile pixels fire, and nobody asks why <MediaFiles> also has a sibling you never clicked. That sibling is <Mezzanine>, and on server-side insertion it is not decorative. It is the file the stitcher downloads when it has never seen your creative before.
Client-side paths and browser QA live on <MediaFile>. SSAI lives on the master plus whatever identity the stitcher uses to reuse a prior transcode. Treating them as interchangeable is how fill dies with a valid XSD and a playable web rendition.
What CSAI and the browser actually consume
In client-side ad insertion (CSAI), the player (or the IMA SDK wrapped around it) walks <MediaFiles>, filters on type, width, height, and delivery, and picks a rendition it can decode. For most web and in-app video that means a progressive file:
<MediaFiles>
<MediaFile delivery="progressive" type="video/mp4" width="1280" height="720" bitrate="1200">
<![CDATA[https://cdn.example/spot_1280.mp4]]>
</MediaFile>
</MediaFiles>
If that URL is HTTPS, the duration matches the break, and tracking is wired, the ad often “works” in the only environment QA exercised. No rule forces the player to fetch <Mezzanine> on CSAI. Many players ignore it entirely.
That success is real for CSAI. It is not proof the same XML is ready for a stitcher that must normalize bitrate and codec against live content before muxing into the manifest.
What SSAI extracts before anyone sees the spot
The IAB CTV Programmatic Guide walks SSAI as a chain where the stitching service requests VAST, expects a mezzanine and ready-to-serve files, downloads the highest quality available, and transcodes when the creative is new. If the mezzanine cannot be used, the guide describes skipping the ad and sending VAST error 407 while the pod moves on.
On SSAI, <Mezzanine> is the high-quality source, usually with delivery="streaming" and a mezzanine-grade URL. The progressive siblings are the outputs you intend players to play after transcoding, keyed by something like UniversalAdId or an equivalent creative identifier the stitcher caches.
<MediaFiles>
<Mezzanine delivery="streaming" type="video/mp4" width="1920" height="1080">
<![CDATA[https://cdn.example/spot_master.mp4]]>
</Mezzanine>
<MediaFile delivery="progressive" type="video/mp4" width="1280" height="720" bitrate="1200">
<![CDATA[https://cdn.example/spot_1280.mp4]]>
</MediaFile>
</MediaFiles>
A common failure mode: only the bottom <MediaFile> exists because the transcoder output was copied into VAST without the master. Browser QA passes. The stitcher hits step “extract mezzanine,” finds nothing usable, skips the ad, and reports 407. Revenue disappears in the pod, not in the validator.
Another failure mode is worse: mezzanine present but HTTP while the app and content are HTTPS-only. CSAI might still play the progressive file in a permissive desktop browser. SSAI infrastructure often refuses the fetch.
SSAI master versus the client wrapper that never reaches the stitcher
The comparison is not only two XML elements. It is two delivery topologies.
On CSAI, trafficking often ships a wrapper URL to the player. The device chases VASTAdTagURI until it finds an InLine. QA stops when the inner creative plays.
On SSAI, the stitching service is the client. It must unwrap the same chain server-side, honor the same precedence rules, and land on an InLine that includes what transcoding needs. A wrapper that terminates in an InLine with only progressive files looks fine when the last hop is tested in a browser against the wrapper URL, because the browser never simulates “first time this master hit the stitcher.”
Wrapper mistakes show up as silent hops: missing AdSystem on a wrapper breaks expectations for downstream analytics, and an empty or malformed root on a wrapper can validate in one tool while confusing unwrap order. The wrapper root error rule and wrapper AdSystem rule document shapes SSAI unwrap logic still has to tolerate.
If your ops team signs off on “tag plays in the tester” using only the CSAI entry URL, they may never fetch the InLine the stitcher sees after five hops. That is a different document than the one in the trafficking UI.
Where the two paths agree (and where they do not)
Both paths need consistent Duration, HTTPS media on locked-down devices, and identity fields buyers enforce. Both need tracking URLs that make sense for the environment (SSAI often fires from the stitcher, not the TV). Those overlaps are why readiness checklists blur the two files into one “video URL.”
They diverge on Mezzanine and on first-seen transcode. VAST 4.1 added mezzanine for SSAI precisely because progressive renditions alone do not tell the stitcher what to ingest before it builds CTV-safe outputs. A linear SIMID creative with mezzanine but no progressive sibling is another split: interactive files do not replace the video file the stitcher muxes (mezzanine recommended rule).
Tracking events illustrate the same “valid but wrong context” pattern. firstQuartile under a linear creative is expected; under NonLinear portfolio formats it may validate and still never schedule on CTV players. The firstQuartile event doc is a reminder that schema-valid tracking is not the same as player scheduling.
How you catch it
Start with the same surface ops already uses for fires and previews. Paste the live tag URL into the VAST tag tester to fetch, preview the creative, and see tracking and click URLs on the response you actually get. When the entry point is a wrapper, run the VAST inspector hop by hop until you hold the InLine the stitcher must transcode.
Then run vastlint on that InLine, not on the wrapper you sell to CSAI. An implementation (or an agent) can emit XML that passes XSD and still omit <Mezzanine> on a CTV SSAI line. vastlint flags missing mezzanine in CTV/SSAI contexts and separates mezzanine attribute requirements from progressive MediaFile checks. These packages are a deterministic check on the wire object; they are independent of IAB Tech Lab and of AgenticAdvertising.org, and nothing in the spec replaces running them before you declare readiness.
For mezzanine-specific handoffs (dimensions, MIME, delivery), the mezzanine validation guide walks what to fix before you argue with the stitcher. When the tag fails earlier (no ad, fetch errors), the tester error explainer maps common fetch and empty responses before you blame transcoding. To compare against IAB sample shapes side by side, the independent IAB-style VAST tester workbench loads reference tags and protocol logs; it is a fork, not an IAB Tech Lab product.
Where to go next
- Compare your SSAI InLine (after unwrap) to your CSAI entry URL in the inspector; they should not be different surprises at depth.
- Turn on mezzanine as error for CTV-bound tags in CI, not warning, if your stitcher spec requires a master (mezzanine recommended rule).
- Read the mezzanine doc index when you need version-specific attribute rules beside the creative identity story.
The playable MP4 in <MediaFile> is what CSAI proves. The master in <Mezzanine> is what SSAI spends CPU on. Validation that only ever tests the first is not wrong for the browser; it is incomplete for the fill path that transcodes.
Top comments (0)