<?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>Unity loss codes 1213 through 1222 are the offensive-content bucket.</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sat, 17 Oct 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/aleksuix/unity-loss-codes-1213-through-1222-are-the-offensive-content-bucket-2n3f</link>
      <guid>https://dev.to/aleksuix/unity-loss-codes-1213-through-1222-are-the-offensive-content-bucket-2n3f</guid>
      <description>&lt;p&gt;Offensive content on Unity Exchange is a range, not a single code. The loss notification still carries one integer, and that integer is what shows up in a bidder's loss report.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1213/" rel="noopener noreferrer"&gt;1213&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1214/" rel="noopener noreferrer"&gt;1214&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1215/" rel="noopener noreferrer"&gt;1215&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1216/" rel="noopener noreferrer"&gt;1216&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1217/" rel="noopener noreferrer"&gt;1217&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1218/" rel="noopener noreferrer"&gt;1218&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1219/" rel="noopener noreferrer"&gt;1219&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1220/" rel="noopener noreferrer"&gt;1220&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1221/" rel="noopener noreferrer"&gt;1221&lt;/a&gt;, and &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1222/" rel="noopener noreferrer"&gt;1222&lt;/a&gt; are the ten codes in the middle of that bucket. Unity's table uses the same review reason for each of them. 1224 through 1226 continue it.&lt;/p&gt;

&lt;p&gt;A price change will not clear these. The creative Unity reviewed is the thing the status is attached to. A new asset gets a new review. The same asset keeps the same code.&lt;/p&gt;

</description>
      <category>openrtb</category>
      <category>adtech</category>
      <category>api</category>
      <category>debugging</category>
    </item>
    <item>
      <title>9 CTV Creative Readiness Mistakes That Pass Validation and Still Kill Fill</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Sat, 17 Oct 2026 00:20:33 +0000</pubDate>
      <link>https://dev.to/aleksuix/9-ctv-creative-readiness-mistakes-that-pass-validation-and-still-kill-fill-2h2e</link>
      <guid>https://dev.to/aleksuix/9-ctv-creative-readiness-mistakes-that-pass-validation-and-still-kill-fill-2h2e</guid>
      <description>&lt;p&gt;On 13 October 2026, Pixalate shipped &lt;a href="https://adtechsumo.com/news/pixalate-launches-ad-fraud-api-v3-0-returns-ad-fraud-reason-codes-for-ddc9da" rel="noopener noreferrer"&gt;Ad Fraud API 3.0&lt;/a&gt; with named invalid-traffic reason codes, including SSAI Misrepresentation for requests that claim server-side insertion but fail metadata checks. That is progress on the bid object. It is not proof that the VAST the stitcher pulls next carries playable media. Court TV and other publishers are rolling out &lt;a href="https://www.tvbeurope.com/adtech/court-tv-deploys-yospace-ssai-to-maximise-ad-monetisation" rel="noopener noreferrer"&gt;Yospace-style SSAI&lt;/a&gt; while buyers still QA tags with XSD and a green dashboard. The gap between those two checks is where CTV revenue quietly disappears.&lt;/p&gt;

&lt;p&gt;Creative readiness means the tag matches the environment that was bought: SSAI transcode paths, HTTPS-only players, pod clocks, and identity fields on the deal. A document can validate and still never serve. These nine mistakes show up in production more often than hard XML syntax errors.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treating SSAI Misrepresentation clearance as VAST clearance.&lt;/strong&gt; A pre-bid block or allow on reason code SSAI Misrepresentation runs on request metadata, not on the stitched response. The same operator can pass the filter and still forward wrappers whose inline never collapses to a servable &lt;code&gt;MediaFile&lt;/code&gt;. We wrote up that split in &lt;a href="https://vastlint.org/blog/ssai-misrepresentation-reason-code-not-vast-proof/" rel="noopener noreferrer"&gt;SSAI misrepresentation reason codes versus VAST proof&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shipping only a progressive &lt;code&gt;MediaFile&lt;/code&gt; when the stitcher expects a mezzanine.&lt;/strong&gt; VAST 4.1+ recommends a mezzanine file for downstream transcoding. SSAI operators often ingest mezzanine and emit renditions. A tag with a single MP4 and no mezzanine can validate while the transcode queue has nothing to work with. See &lt;a href="https://vastlint.org/docs/mezzanine/" rel="noopener noreferrer"&gt;mezzanine rules and attributes&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mezzanine present but missing delivery, type, width, or height.&lt;/strong&gt; Stitchers skip files that look like URLs without the attributes their packager reads. Demo tags copied from IAB samples often include the shape; production swaps the URL and drops dimensions during CMS export.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP media on HTTPS-only CTV surfaces.&lt;/strong&gt; Schema checks rarely enforce TLS on every &lt;code&gt;MediaFile&lt;/code&gt; and tracking URL. Living-room players and many SSAI edges refuse mixed content. The tag fetches in a desktop validator and dies on device.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VPAID or legacy interactive flags on a live CTV stitch.&lt;/strong&gt; Interactive creatives belong in a controlled player context. Server-side insertion paths on CTV commonly block VPAID outright. An &lt;code&gt;InteractiveCreativeFile&lt;/code&gt; with &lt;code&gt;apiFramework="VPAID"&lt;/code&gt; can pass XSD and fail at stitch time. &lt;a href="https://vastlint.org/docs/vpaid-rules/VPAID-ctv-blocked/" rel="noopener noreferrer"&gt;VPAID on CTV blocked&lt;/a&gt; documents the environment rule. If you replace VPAID with SIMID, handshake QA belongs in the independent &lt;a href="https://iab-tech-lab-vast-tester.vastlint.org/" rel="noopener noreferrer"&gt;IAB-style VAST tester fork&lt;/a&gt; (not an IAB Tech Lab product).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Duration&lt;/code&gt; longer than the ad break slot.&lt;/strong&gt; VMAP and pod logic use declared duration to schedule slots. A 60-second &lt;code&gt;Duration&lt;/code&gt; inside a 30-second break does not always fail validation; it fails scheduling and yields an empty pod or a skipped ad.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;Duration&lt;/code&gt; that disagrees with the encoded asset.&lt;/strong&gt; Players that trust the XML clock can cut off early or hold black frames. Billing and quartile trackers desync from what the viewer saw. Pod guides cover how sequence and duration interact in &lt;a href="https://vastlint.org/guides/vast-ad-pods/" rel="noopener noreferrer"&gt;VAST ad pods&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Missing or empty &lt;code&gt;UniversalAdId&lt;/code&gt; on a deal that requires ACIF identity.&lt;/strong&gt; Registration frameworks and buyer macros assume a registry and value pair. A blank id or wrong registry validates structurally and fails clearance, so the impression never bills.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;QAing the client-side wrapper while SSAI ingests a different hop.&lt;/strong&gt; Trafficking pastes a VAST URL into a DSP; the stitcher resolves a wrapper chain on the server. Impression nodes on outer wrappers, missing &lt;code&gt;InLine&lt;/code&gt; handoff, or depth limits bite on the path the stitcher walks, not the path someone opened in a browser. Rules such as &lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-impression/" rel="noopener noreferrer"&gt;wrapper impression placement&lt;/a&gt; matter on the chain SSAI actually follows.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How you catch it
&lt;/h2&gt;

&lt;p&gt;Start with the live tag URL in the &lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;VAST tag tester&lt;/a&gt;: fetch, preview the creative, and confirm tracking and click URLs resolve. When the tag is a wrapper, open the &lt;a href="https://vastlint.org/inspect/" rel="noopener noreferrer"&gt;VAST inspector&lt;/a&gt; and walk hops until you hit the &lt;code&gt;InLine&lt;/code&gt; the stitcher must collapse to.&lt;/p&gt;

&lt;p&gt;After that, run the same XML through vastlint (&lt;code&gt;cargo install vastlint&lt;/code&gt; or &lt;code&gt;npm install vastlint&lt;/code&gt;) in CI or on the saved response. An LLM or agent can emit VAST that looks plausible and still miss mezzanine, HTTPS, duration, or identity rules. vastlint checks the creative payload the buy claimed. It is independent of IAB Tech Lab and of AgenticAdvertising.org; it is not an official harness and the spec does not require it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/mezzanine/" rel="noopener noreferrer"&gt;Mezzanine file requirements&lt;/a&gt; for SSAI transcode paths.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/guides/vast-ad-pods/" rel="noopener noreferrer"&gt;VAST ad pods&lt;/a&gt; when duration and sequence must fit the break.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/blog/ssai-misrepresentation-reason-code-not-vast-proof/" rel="noopener noreferrer"&gt;SSAI misrepresentation versus VAST proof&lt;/a&gt; when fraud filters and tag QA disagree.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/rules/VAST-2.0-wrapper-impression/" rel="noopener noreferrer"&gt;Wrapper impression rules&lt;/a&gt; on chains the server resolves.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/vast-tracking-events/start/" rel="noopener noreferrer"&gt;Start tracking event semantics&lt;/a&gt; once media actually plays.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vast</category>
      <category>advertising</category>
      <category>video</category>
      <category>ctv</category>
    </item>
    <item>
      <title>Migrate VPAID interactivity to SIMID when your delivery partners can support it.</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Fri, 16 Oct 2026 16:36:35 +0000</pubDate>
      <link>https://dev.to/aleksuix/migrate-vpaid-interactivity-to-simid-when-your-delivery-partners-can-support-it-2e8l</link>
      <guid>https://dev.to/aleksuix/migrate-vpaid-interactivity-to-simid-when-your-delivery-partners-can-support-it-2e8l</guid>
      <description>&lt;p&gt;Originally published on &lt;a href="https://vastlint.org/blog/migrate-vpaid-to-simid/" rel="noopener noreferrer"&gt;VASTLint&lt;/a&gt;. This article is part of the Migrate video standards with evidence series.&lt;/p&gt;

&lt;p&gt;An interactive video campaign becomes expensive to operate when every destination needs a different creative package. The sales team sells one experience, the studio builds several implementations, and operations discovers the exceptions near launch. A migration should reduce that repeated work while preserving the experience the advertiser bought.&lt;/p&gt;

&lt;p&gt;SIMID is a useful destination for interactive video, but a migration decision needs more than a newer acronym. Start with the placements you can actually serve, the interactions those placements support, and the evidence required to call the campaign ready. A format that passes a document check can still encounter an unsupported player.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the interactive experience from video playback.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://iabtechlab.com/standards/simid/" rel="noopener noreferrer"&gt;IAB Tech Lab describes SIMID&lt;/a&gt; as the interactivity component of the move away from VPAID. The interactive layer communicates with the player through defined messages while the media remains separate. The player controls playback and handles requests from the creative. Measurement belongs in the Open Measurement path.&lt;/p&gt;

&lt;p&gt;That separation gives a migration team a concrete engineering objective. The video should play through the destination's normal media pipeline. The interaction should add the approved behavior without taking ownership of the whole playback implementation. The test plan can then distinguish a media failure from an interaction failure.&lt;/p&gt;

&lt;p&gt;Treat any claimed commercial benefit as a hypothesis to measure. If one reusable package replaces several destination-specific builds, record the builds avoided and the remaining exceptions. If the same engineering work merely moves into a new adapter, the operating cost may not have improved yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one interaction you can sell and test.
&lt;/h2&gt;

&lt;p&gt;Choose a narrow experience, such as a product information panel with an approved destination link. Write down the required user actions, the allowed player responses and the behavior when the interaction cannot run. Avoid making the first release depend on every feature of an existing VPAID creative.&lt;/p&gt;

&lt;p&gt;Ask each publisher or platform to confirm support for that exact experience on the intended app, device and delivery method. A general statement that a platform supports VAST or interactive video does not answer whether the proposed SIMID package will execute there. Save the tested player and SDK versions with the acceptance record.&lt;/p&gt;

&lt;p&gt;Specify the business fallback too. If the interactive feature was an optional enhancement, ordinary video may be acceptable. If the advertiser bought a particular interaction, losing that feature can change the delivered product. Operations should not silently count the fallback as equivalent fulfillment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep verification on its own migration track.
&lt;/h2&gt;

&lt;p&gt;Inventory everything the old creative does. Some VPAID packages combine an interaction, a measurement vendor integration and media handling. Replacing only the interaction does not demonstrate that the verification requirement has been met.&lt;/p&gt;

&lt;p&gt;Create a separate acceptance record for measurement. Identify the destination integration, the verification resource and the provider that must receive usable signals. Require a runtime test in addition to checking the VAST declaration. A successful interaction is evidence about the interaction, not about the provider's measurement report.&lt;/p&gt;

&lt;p&gt;Keep this distinction in the handoff to commercial teams. An experience can be visually correct while failing a buyer's verification requirement. Conversely, a properly measured video may lack the interactive feature that the campaign proposal promised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a small pilot with an explicit failure path.
&lt;/h2&gt;

&lt;p&gt;First, save the current creative, VAST response and expected behavior. Build the SIMID version against the &lt;a href="https://github.com/InteractiveAdvertisingBureau/SIMID/blob/master/SIMID%201.2.md" rel="noopener noreferrer"&gt;published interface specification&lt;/a&gt;, then validate its document with &lt;a href="https://vastlint.org/" rel="noopener noreferrer"&gt;VASTLint&lt;/a&gt;. Retain the findings and corrected document so the next reviewer can see what changed.&lt;/p&gt;

&lt;p&gt;Next, run the package in each approved destination. Test normal playback, the intended interaction, a blocked interaction resource and a slow response. Record what the user sees, whether the video continues as agreed and which events reach the relevant reporting systems. Include an ordinary non-interactive video as a control.&lt;/p&gt;

&lt;p&gt;Release only to the destinations that passed those checks. Keep a routing rule and a tested rollback package. A practical pilot produces a list of approved environments with evidence, rather than a global switch labeled migrated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the work saved before projecting revenue.
&lt;/h2&gt;

&lt;p&gt;Compare the pilot with a similar recent campaign. Count creative packages produced, review rounds, destination-specific fixes and support time. Record the campaign scope so a small two-placement pilot is not compared directly with a large multi-market launch.&lt;/p&gt;

&lt;p&gt;Suppose, as an illustration, a team normally spends six hours adapting one experience and spends four hours on the new route. The observed result would be two hours less work for that comparison. It would not establish a universal reduction, additional impressions or increased sales. Include any extra player integration work before claiming net savings.&lt;/p&gt;

&lt;p&gt;For an advertiser, the commercially relevant question may be whether the requested interactive product can reach the booked placements. For a publisher, it may be whether the feature can be accepted without custom creative execution. Those are specific outcomes that a pilot can document.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expand from an approved delivery contract.
&lt;/h2&gt;

&lt;p&gt;Write the accepted media requirements, interaction behavior, verification requirements and fallback policy into the creative brief for the next campaign. Give the studio and trafficker the same test record. Require an explicit review when a new device or delivery path is added.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://vastlint.org/" rel="noopener noreferrer"&gt;VASTLint&lt;/a&gt; to repeat the document checks at each handoff. Keep player execution and provider reporting as separate acceptance steps. The migration earns its place when a second campaign can reuse the approved route with less uncertainty and a clear explanation of its remaining limits.&lt;/p&gt;

</description>
      <category>adtech</category>
      <category>webdev</category>
      <category>testing</category>
      <category>performance</category>
    </item>
    <item>
      <title>Unity uses one review reason for loss codes 1200 through 1208.</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Fri, 16 Oct 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/aleksuix/unity-uses-one-review-reason-for-loss-codes-1200-through-1208-3m5m</link>
      <guid>https://dev.to/aleksuix/unity-uses-one-review-reason-for-loss-codes-1200-through-1208-3m5m</guid>
      <description>&lt;p&gt;Unity's creative-review codes look like a long enum. For user security they are not. The public loss table assigns the same sentence to every integer from 1200 through 1208: the exchange review rejected the creative for a user-security issue. 1227 is the same bucket later in the list.&lt;/p&gt;

&lt;p&gt;Read the number you actually received. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1200/" rel="noopener noreferrer"&gt;1200&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1201/" rel="noopener noreferrer"&gt;1201&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1202/" rel="noopener noreferrer"&gt;1202&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1203/" rel="noopener noreferrer"&gt;1203&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1204/" rel="noopener noreferrer"&gt;1204&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1205/" rel="noopener noreferrer"&gt;1205&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1206/" rel="noopener noreferrer"&gt;1206&lt;/a&gt;, &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1207/" rel="noopener noreferrer"&gt;1207&lt;/a&gt;, and &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1208/" rel="noopener noreferrer"&gt;1208&lt;/a&gt; are that run. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1227/" rel="noopener noreferrer"&gt;1227&lt;/a&gt; is the later user-security code.&lt;/p&gt;

&lt;p&gt;Resubmitting the same asset returns the same integer. Unity does not publish which security check inside the bucket failed. The practical step is a new creative, then a check that the new &lt;code&gt;crid&lt;/code&gt; is the one in the bid.&lt;/p&gt;

</description>
      <category>openrtb</category>
      <category>adtech</category>
      <category>api</category>
      <category>debugging</category>
    </item>
    <item>
      <title>The CTV Ad Portfolio Cheat Sheet Every Video Ad Ops Engineer Needs</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Fri, 16 Oct 2026 00:17:52 +0000</pubDate>
      <link>https://dev.to/aleksuix/the-ctv-ad-portfolio-cheat-sheet-every-video-ad-ops-engineer-needs-5410</link>
      <guid>https://dev.to/aleksuix/the-ctv-ad-portfolio-cheat-sheet-every-video-ad-ops-engineer-needs-5410</guid>
      <description>&lt;p&gt;On October 9, 2026, &lt;a href="https://www.thetradedesk.com/resources/pause-ads-are-here-signaling-the-next-evolution-of-ctv" rel="noopener noreferrer"&gt;The Trade Desk framed pause ads as the next leg of a broader CTV portfolio&lt;/a&gt;: formats beyond the midroll break, each with its own moment on the glass. Netflix had already said pause would move onto partner DSPs in October. The hard part is not finding budget. It is matching three layers that were finalized separately: AdCOM integers on the bid request, the VAST envelope in &lt;code&gt;adm&lt;/code&gt;, and what the player or stitcher actually schedules. IAB Tech Lab closed the &lt;a href="https://iabtechlab.com/standards/ctv-ad-portfolio/" rel="noopener noreferrer"&gt;CTV Ad Portfolio signaling guidance on July 22, 2026&lt;/a&gt;. That document is a table of enumerations, not a creative QA checklist. Programmatic pause in particular still splits by route, which we mapped in &lt;a href="https://vastlint.org/blog/netflix-programmatic-pause-ads-nonlinear-vast/" rel="noopener noreferrer"&gt;Netflix pause ads go programmatic, and each buying route accepts a different NonLinear creative&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This sheet is for video ad ops and trafficking engineers who receive a DSP export, an SSAI template, or an agent-generated VAST blob and need to know, in one screen, which OpenRTB signals and which VAST subtree belong together. Bookmark it next to your plcmt reference. One wrong integer pair can validate and still never render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format-to-signal and VAST shape
&lt;/h2&gt;

&lt;p&gt;Five portfolio formats ride &lt;code&gt;imp.video&lt;/code&gt; and return &lt;strong&gt;NonLinear&lt;/strong&gt; VAST in &lt;code&gt;adm&lt;/code&gt;. Menu and tile units ride &lt;code&gt;imp.native&lt;/code&gt; and may embed VAST inside &lt;code&gt;video.vasttag&lt;/code&gt;; they do not use the NonLinear container. Values below come from IAB's &lt;a href="https://github.com/InteractiveAdvertisingBureau/Ad-Format-Guidelines-for-Digital-Video-CTV/blob/main/Signaling-Implementation-Guidelines.md" rel="noopener noreferrer"&gt;Signaling Implementation Guidelines&lt;/a&gt; format table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;&lt;code&gt;video.plcmt&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Typical &lt;code&gt;pos&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;Typical &lt;code&gt;playbackmethod&lt;/code&gt;
&lt;/th&gt;
&lt;th&gt;VAST delivery&lt;/th&gt;
&lt;th&gt;Tracking the player expects&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pause&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7 fullscreen, 8 partial&lt;/td&gt;
&lt;td&gt;8 sound on, 9 sound off (user pause)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt; with static, cinemagraph, or video asset&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;creativeView&lt;/code&gt;, &lt;code&gt;overlayViewDuration&lt;/code&gt;; quartiles only when &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt; is present on video&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Screensaver&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;7 or 8&lt;/td&gt;
&lt;td&gt;10 sound on, 11 sound off (idle)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt;; often static&lt;/td&gt;
&lt;td&gt;Same NonLinear set; duration optional for stills&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overlay&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9, 10, 14, 15, or 5 by geometry&lt;/td&gt;
&lt;td&gt;Often 1 or 2 (autostart sound on/off)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt; with image or video&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;creativeView&lt;/code&gt;, &lt;code&gt;overlayViewDuration&lt;/code&gt;; include &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt; when time-based beacons matter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Squeezeback&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;11, 12, 13, 16, or 17 by layout&lt;/td&gt;
&lt;td&gt;Often 1 or 2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt;; content frame shrinks&lt;/td&gt;
&lt;td&gt;Same as overlay; &lt;code&gt;pos&lt;/code&gt; must match squeezeback layout, not pause fullscreen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-scene&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;(format-specific)&lt;/td&gt;
&lt;td&gt;Often 1 or 2&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt; insert in scene&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bid.attr&lt;/code&gt; should declare motion; NonLinear tracking as above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Menu / tile&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;plcmttype&lt;/code&gt; 1 or 3&lt;/td&gt;
&lt;td&gt;N/A on video object&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;img.url&lt;/code&gt; and/or &lt;code&gt;video.vasttag&lt;/code&gt; (may wrap linear VAST)&lt;/td&gt;
&lt;td&gt;Native &lt;code&gt;eventtrackers&lt;/code&gt; plus embedded VAST pixels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Downstream, DSPs echo &lt;code&gt;plcmt&lt;/code&gt;, &lt;code&gt;pos&lt;/code&gt;, &lt;code&gt;playbackmethod&lt;/code&gt;, and &lt;code&gt;attr&lt;/code&gt; into VAST AdCOM extensions so stitchers that never saw the bid can still read what was sold. The child element name must match the extension &lt;code&gt;type&lt;/code&gt;. A &lt;code&gt;type="pos"&lt;/code&gt; wrapper with a &lt;code&gt;&amp;lt;plcmt&amp;gt;&lt;/code&gt; child is well formed XML and wrong semantics.&lt;/p&gt;

&lt;p&gt;Interactive portfolio units that use SIMID declare &lt;code&gt;&amp;lt;InteractiveCreativeFile apiFramework="SIMID"&amp;gt;&lt;/code&gt; under &lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; in the VAST 4.4 pattern, with a renderable &lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; fallback. That handshake is separate from the signal table, but the same rule applies: the buy must match the container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three table rows that pass schema and lose the impression
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pause plcmt with a linear &lt;code&gt;&amp;lt;Linear&amp;gt;&lt;/code&gt; creative.&lt;/strong&gt; OpenRTB can carry &lt;code&gt;plcmt=5&lt;/code&gt; while &lt;code&gt;adm&lt;/code&gt; is a standard instream MP4 under &lt;code&gt;&amp;lt;Linear&amp;gt;&lt;/code&gt;. XSD checks pass. CTV portfolio players look for &lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt;. The spot does not enter the pause overlay path; the impression disappears without a trafficking error code. This is the most common copy-paste failure when a team reuses a :15 preroll tag for a pause deal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;plcmt=5&lt;/code&gt; paired with squeezeback &lt;code&gt;pos&lt;/code&gt; and instream &lt;code&gt;playbackmethod&lt;/code&gt;.&lt;/strong&gt; Each integer is legal AdCOM. Together they describe pause, squeezeback, and ordinary autoplay at once. Stitchers that enforce portfolio rules reject the mismatch; lenient parsers attach the wrong geometry. vastlint flags cross-format pairs such as pause plcmt with &lt;code&gt;playbackmethod&lt;/code&gt; 1 instead of 8 or 9.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video &lt;code&gt;&amp;lt;MediaFile&amp;gt;&lt;/code&gt; under &lt;code&gt;&amp;lt;NonLinear&amp;gt;&lt;/code&gt; with no &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt;.&lt;/strong&gt; IAB allows omitting duration for static pause and screensaver assets when display time is unknown at response time. Add a looping MP4 for a cinemagraph pause, and quartile URLs plus &lt;code&gt;overlayViewDuration&lt;/code&gt; have no timeline. The tag validates; measurement flatlines. That row is easy to miss because the same NonLinear subtree works for a JPEG.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you catch it
&lt;/h2&gt;

&lt;p&gt;Schema validation alone cannot enforce the table. You need the resolved tag the player sees, including wrapper hops.&lt;/p&gt;

&lt;p&gt;Paste the live tag URL into the &lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;VAST tag tester&lt;/a&gt; to fetch, preview the NonLinear asset, and confirm tracking URLs fire on the events your MMP expects. When the ad server returns wrappers, open the &lt;a href="https://vastlint.org/inspect/" rel="noopener noreferrer"&gt;VAST inspector&lt;/a&gt; and walk the chain hop by hop until you hit the InLine that carries &lt;code&gt;&amp;lt;NonLinearAds&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For SIMID or IAB sample creatives on portfolio placements, use the &lt;a href="https://iab-tech-lab-vast-tester.vastlint.org/" rel="noopener noreferrer"&gt;IAB-style VAST tester&lt;/a&gt; (SIMID studio and sample tags). It is an independent fork, not an IAB Tech Lab product, but it exercises the interactive NonLinear path the portfolio spec describes.&lt;/p&gt;

&lt;p&gt;After preview, run &lt;strong&gt;vastlint&lt;/strong&gt; on the final XML (&lt;code&gt;cargo install vastlint&lt;/code&gt; or &lt;code&gt;npm install vastlint&lt;/code&gt;). An agent or MCP task can emit portfolio VAST that parses clean while &lt;code&gt;plcmt&lt;/code&gt;, &lt;code&gt;pos&lt;/code&gt;, and &lt;code&gt;playbackmethod&lt;/code&gt; still disagree. vastlint is the deterministic check on the creative the buy claimed. It is independent of IAB Tech Lab and of AAO; the spec does not require it, but it encodes the same format-to-signal rows as rules you can run in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/vast-4-4/" rel="noopener noreferrer"&gt;VAST 4.4 and CTV portfolio changes&lt;/a&gt; for NonLinear &lt;code&gt;&amp;lt;MediaFiles&amp;gt;&lt;/code&gt;, duration, and AdCOM extension typing.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/docs/ctv-ad-portfolio/" rel="noopener noreferrer"&gt;CTV Ad Portfolio pack&lt;/a&gt; for format-by-format notes and rule ids.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/guides/vast-pause-ads/" rel="noopener noreferrer"&gt;Pause ads guide&lt;/a&gt; with a full static pause NonLinear example and signal checklist.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb/plcmt/" rel="noopener noreferrer"&gt;OpenRTB &lt;code&gt;video.plcmt&lt;/code&gt; reference&lt;/a&gt; for values 1 through 9 on the bid side.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vastlint.org/guides/vast-wrapper-chains/" rel="noopener noreferrer"&gt;Wrapper chains guide&lt;/a&gt; when the portfolio creative sits three hops deep and the InLine never reaches QA.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ctv</category>
      <category>vast</category>
      <category>advertising</category>
      <category>openrtb</category>
    </item>
    <item>
      <title>Unity loss 1011 means the bid never named a billing URL.</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Thu, 15 Oct 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/aleksuix/unity-loss-1011-means-the-bid-never-named-a-billing-url-10b7</link>
      <guid>https://dev.to/aleksuix/unity-loss-1011-means-the-bid-never-named-a-billing-url-10b7</guid>
      <description>&lt;p&gt;Unity 1011 is the loss code bidders look up as a Unity RTB error after a bid that otherwise parsed. The bid had no billing notice on &lt;code&gt;burl&lt;/code&gt; and no impression trackers on &lt;code&gt;ext.imptrackers&lt;/code&gt;. Unity wants one of those.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1011/" rel="noopener noreferrer"&gt;1011&lt;/a&gt; is the page for that check. The codes around it are different failures that show up in the same hour of a log.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1010/" rel="noopener noreferrer"&gt;1010&lt;/a&gt; is an IAB category the publisher blocked. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1012/" rel="noopener noreferrer"&gt;1012&lt;/a&gt; is a bid that arrived after Unity's auction window. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1200/" rel="noopener noreferrer"&gt;1200&lt;/a&gt; starts the user-security review bucket: Unity uses 1200 through 1208 for that one reason and does not publish a separate cause per integer. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1209/" rel="noopener noreferrer"&gt;1209&lt;/a&gt; is a blank creative or a 1x1 pixel. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1210/" rel="noopener noreferrer"&gt;1210&lt;/a&gt; is a performance disapproval. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1212/" rel="noopener noreferrer"&gt;1212&lt;/a&gt; is a creative with no media file. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1213/" rel="noopener noreferrer"&gt;1213&lt;/a&gt; starts the offensive-content bucket. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1234/" rel="noopener noreferrer"&gt;1234&lt;/a&gt; is a quality rejection from Unity's automated review. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1235/" rel="noopener noreferrer"&gt;1235&lt;/a&gt; is a quality rejection from a human reviewer.&lt;/p&gt;

&lt;p&gt;If the integer is 1011, the fix is the tracker. The other nine are the codes it gets confused with.&lt;/p&gt;

</description>
      <category>openrtb</category>
      <category>adtech</category>
      <category>api</category>
      <category>debugging</category>
    </item>
    <item>
      <title>GPP Explained: How Privacy Strings on the Bid and Tag Gate Creatives</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Thu, 15 Oct 2026 00:16:13 +0000</pubDate>
      <link>https://dev.to/aleksuix/gpp-explained-how-privacy-strings-on-the-bid-and-tag-gate-creatives-4ncd</link>
      <guid>https://dev.to/aleksuix/gpp-explained-how-privacy-strings-on-the-bid-and-tag-gate-creatives-4ncd</guid>
      <description>&lt;p&gt;IAB Europe set &lt;strong&gt;October 23, 2026&lt;/strong&gt; as the deadline for consent management platforms to ship the new TCF v2.4 disclosures on the web, with mobile and CTV following in February 2027. The &lt;a href="https://iabeurope.eu/wp-content/uploads/TCF_V-CMP_comms_TCFpoliciesV5.0.bAndSpecificationsV2.4ConfirmationOf-theTimeline_160726_IABEurope.pdf" rel="noopener noreferrer"&gt;timeline letter&lt;/a&gt; confirms the spec and Global Vendor List update landed July 23, 2026. Disclosed-vendor bits and clearer feature text change what a TC string is allowed to mean. That matters downstream because exchanges and DSPs do not re-run your CMP UI. They read whatever string arrived on the bid request or the pixel URL and decide whether the creative's vendors may fire.&lt;/p&gt;

&lt;p&gt;Programmatic fill is not only codec and duration. It is also whether the privacy signal grants the measurement and ad tech companies embedded in the tag. A VAST InLine can validate, a bid can win, and the buyer still records a loss or a blank when consent does not cover vendor 755 on the GVL.&lt;/p&gt;

&lt;h2&gt;
  
  
  What rides on the OpenRTB bid request
&lt;/h2&gt;

&lt;p&gt;OpenRTB 2.6-202211 gave GPP a first-class home. You put the encoded consent container in &lt;code&gt;regs.gpp&lt;/code&gt; and the applicable section identifiers in &lt;code&gt;regs.gpp_sid&lt;/code&gt;. GDPR traffic still pairs &lt;code&gt;regs.gdpr&lt;/code&gt; with the IAB Europe TC string in &lt;code&gt;user.consent&lt;/code&gt;. The &lt;a href="https://rtblint.org/docs/openrtb/privacy/" rel="noopener noreferrer"&gt;OpenRTB privacy field guide&lt;/a&gt; walks the same paths for 2.5 extension placements versus 2.6 top-level fields.&lt;/p&gt;

&lt;p&gt;A common failure mode is half a signal. An SSP forwards &lt;code&gt;regs.gpp_sid: [7]&lt;/code&gt; after an edge rewrite but drops &lt;code&gt;regs.gpp&lt;/code&gt;. The section id tells the bidder which grammar to expect; without the string there is nothing to decode. RTBlint flags that pattern as incoherent on the wire. Another failure is the inverse: a long base64 blob in &lt;code&gt;user.consent&lt;/code&gt; while &lt;code&gt;regs.gdpr&lt;/code&gt; is omitted, so the exchange treats the auction as non-GDPR and applies the wrong policy.&lt;/p&gt;

&lt;p&gt;US state and national signals increasingly ride inside GPP rather than the legacy four-character &lt;code&gt;regs.us_privacy&lt;/code&gt; string IAB Tech Lab deprecated on January 31, 2024. During transition many stacks still read both. If GPP section 7 says opt-out of sale and &lt;code&gt;us_privacy&lt;/code&gt; still reads &lt;code&gt;1YNN&lt;/code&gt;, you have contradictory instructions and no deterministic winner unless the buyer documents precedence.&lt;/p&gt;

&lt;p&gt;Google's creative status codes in the 200 range include cases where the TC string does not consent to a vendor present in the creative. That is not a VAST schema error. The XML is fine. The privacy contract on the bid is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What rides on the tag and the pixel
&lt;/h2&gt;

&lt;p&gt;Not every hop runs OpenRTB. Impression pixels, verification scripts, and server-side conversion posts carry the same strings through query parameters and macros. TCF expects the TC string to propagate &lt;strong&gt;as-is&lt;/strong&gt;. A tag manager that truncates, re-encodes, or substitutes a placeholder breaks vendor-level checks even when the page looked compliant at banner time.&lt;/p&gt;

&lt;p&gt;The Global Privacy Platform wraps multiple jurisdiction-specific sections in one transport string. Section type lives in the header; decoders must read that header before they interpret payload bits. Treating a GPP blob like a raw TC string, or passing &lt;code&gt;gdpr_consent=1&lt;/code&gt; because the macro defaulted to a boolean, produces values that are syntactically plausible and semantically empty. Pixellint's &lt;a href="https://pixellint.org/docs/gpp/" rel="noopener noreferrer"&gt;GPP documentation&lt;/a&gt; and &lt;a href="https://pixellint.org/docs/tcf-v2/" rel="noopener noreferrer"&gt;TCF v2 guide&lt;/a&gt; separate those grammars so you can lint the string itself, not just whether the HTTP request returned 200.&lt;/p&gt;

&lt;p&gt;Server-side stacks amplify the risk. Edge auctions and Trusted Server-style paths rebuild OpenRTB after the browser blocked sync pixels. If the reconstructed &lt;code&gt;regs.gpp&lt;/code&gt; and &lt;code&gt;user.consent&lt;/code&gt; disagree with what the CMP emitted on the page, the compliance failure moves from DevTools to logs buyers never see. The enforcement point changed; the requirement that the string match user choice did not.&lt;/p&gt;

&lt;p&gt;Video adds a third surface: VAST &lt;code&gt;Impression&lt;/code&gt; and &lt;code&gt;Tracking&lt;/code&gt; URLs often expand &lt;code&gt;${GDPR_CONSENT_xxx}&lt;/code&gt; macros at serve time. The consent string on that URL must still match the bid the DSP won with.&lt;/p&gt;

&lt;h2&gt;
  
  
  How decoders turn strings into serve / no-serve
&lt;/h2&gt;

&lt;p&gt;For GDPR, &lt;code&gt;regs.gdpr=1&lt;/code&gt; tells the bidder to apply TCF rules. The TC string encodes purposes, vendors, and (since v2.3) which vendors were actually disclosed in the CMP UI. TCF v2.4 tightens GVL feature text and removes ambiguous legitimate-interest shortcuts for certain vendor classes. A bidder comparing your creative's AdVerification vendor list against the consent bitmap is implementing that policy, not XML validation.&lt;/p&gt;

&lt;p&gt;For US and multi-state traffic, GPP section IDs in &lt;code&gt;regs.gpp_sid&lt;/code&gt; tell the decoder which slice of the container applies. New sections continue to land; the &lt;a href="https://rtblint.org/blog/gpp-new-sections-2026/" rel="noopener noreferrer"&gt;GPP sections update for 2026&lt;/a&gt; is the map for what showed up in recent IAB Tech Lab revisions versus what your exchange still ignores.&lt;/p&gt;

&lt;p&gt;COPPA and child-directed inventory add a parallel gate: &lt;code&gt;regs.coppa=1&lt;/code&gt; tells buyers to suppress behavioral use regardless of what a generic consent string says. Sending rich &lt;code&gt;user.eids&lt;/code&gt; under COPPA is a policy violation even when the JSON schema accepts the fields.&lt;/p&gt;

&lt;p&gt;One concrete mistake: copying a consent macro from a display template into a video tracking URL without the vendor id the video partner registered. The string validates as base64; the vendor bits are zero for that id; measurement never starts and nobody gets a VAST error code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you catch it before fill drops
&lt;/h2&gt;

&lt;p&gt;An LLM wiring AdCP, ARTF, or a Prebid adapter can emit &lt;code&gt;regs.gpp&lt;/code&gt; and &lt;code&gt;user.consent&lt;/code&gt; that parse and still misrepresent what the CMP showed. The RPC or the adapter callback can return OK while the exchange applies a stricter decode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;rtblint&lt;/strong&gt; owns the OpenRTB object: &lt;code&gt;cargo install rtblint&lt;/code&gt; or &lt;code&gt;npm install rtblint-core&lt;/code&gt;, then validate the bid request JSON against the dated snapshot your partner runs. &lt;strong&gt;pixellint&lt;/strong&gt; owns the string on the beacon or tag URL: &lt;code&gt;cargo install pixellint&lt;/code&gt; or &lt;code&gt;npm install pixellint&lt;/code&gt; for TC, GPP, and legacy US privacy formats. These packages are independent checks; they are not IAB Tech Lab or AAO official harnesses, and the specs do not require them, but they catch the drift an agent implementation will miss.&lt;/p&gt;

&lt;p&gt;When consent rides a live VAST tag, use the &lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;VAST tag tester&lt;/a&gt; to fetch the served XML and inspect expanded impression URLs, and the &lt;a href="https://vastlint.org/inspect/" rel="noopener noreferrer"&gt;VAST inspector&lt;/a&gt; when wrappers strip or rewrite macros hop by hop.&lt;/p&gt;

&lt;p&gt;Start from &lt;a href="https://pixellint.org/docs/consent-on-the-wire/" rel="noopener noreferrer"&gt;consent on the wire&lt;/a&gt; when you need to compare CMP output, ad tag macros, and SSP forwarding on the same request id. The &lt;a href="https://pixellint.org/docs/consent/" rel="noopener noreferrer"&gt;consent overview&lt;/a&gt; ties IAB formats to the fields your pixels actually fire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb/privacy/" rel="noopener noreferrer"&gt;Privacy signals in OpenRTB&lt;/a&gt; for field-by-field placement across 2.5 and 2.6 snapshots.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pixellint.org/docs/gpp/" rel="noopener noreferrer"&gt;GPP on pixellint&lt;/a&gt; for header types and section decoding before you trust a macro expansion.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://pixellint.org/docs/tcf-v2/" rel="noopener noreferrer"&gt;TCF v2 on pixellint&lt;/a&gt; for disclosed vendors and purpose bits that changed under v2.4.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/blog/gpp-new-sections-2026/" rel="noopener noreferrer"&gt;GPP new sections in 2026&lt;/a&gt; when your &lt;code&gt;gpp_sid&lt;/code&gt; list outgrows what your bidder's policy table knows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Fix the string on the same hop as the creative decision, not after the loss report shows "consent vendor mismatch" with a perfectly valid VAST file attached.&lt;/p&gt;

</description>
      <category>openrtb</category>
      <category>advertising</category>
      <category>privacy</category>
      <category>gdpr</category>
    </item>
    <item>
      <title>Unity's first ten loss codes are validator failures and publisher blocks.</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Wed, 14 Oct 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/aleksuix/unitys-first-ten-loss-codes-are-validator-failures-and-publisher-blocks-2hf6</link>
      <guid>https://dev.to/aleksuix/unitys-first-ten-loss-codes-are-validator-failures-and-publisher-blocks-2hf6</guid>
      <description>&lt;p&gt;A Unity loss code in the 1000s never appears in the OpenRTB table. Unity Exchange returns it in &lt;code&gt;${AUCTION_LOSS}&lt;/code&gt; when the bid failed Unity's own checks before price mattered.&lt;/p&gt;

&lt;p&gt;The first four are on the bidder. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1000/" rel="noopener noreferrer"&gt;1000&lt;/a&gt; means Unity's OpenRTB validator rejected the payload. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1001/" rel="noopener noreferrer"&gt;1001&lt;/a&gt; means the body was not JSON. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1002/" rel="noopener noreferrer"&gt;1002&lt;/a&gt; is the catch-all marketplace block, the code Unity uses when it will not name a narrower rule. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1003/" rel="noopener noreferrer"&gt;1003&lt;/a&gt; is specific: the advertised app's age rating is above the publisher's maximum.&lt;/p&gt;

&lt;p&gt;The next six are publisher blocks, still on Unity's integers. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1004/" rel="noopener noreferrer"&gt;1004&lt;/a&gt; and &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1005/" rel="noopener noreferrer"&gt;1005&lt;/a&gt; are &lt;code&gt;crtype&lt;/code&gt; on a consolidated request: missing, or not one of the ad types Unity sent. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1006/" rel="noopener noreferrer"&gt;1006&lt;/a&gt; is the region. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1007/" rel="noopener noreferrer"&gt;1007&lt;/a&gt; is the platform, iOS or Android. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1008/" rel="noopener noreferrer"&gt;1008&lt;/a&gt; is a sensitive attribute such as real-money gambling. &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/1009/" rel="noopener noreferrer"&gt;1009&lt;/a&gt; is a Unity subcategory the publisher turned off.&lt;/p&gt;

&lt;p&gt;The full list, including 1010 through the creative-review codes, is the &lt;a href="https://rtblint.org/docs/openrtb/loss/exchange/unity/" rel="noopener noreferrer"&gt;Unity loss index&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>openrtb</category>
      <category>adtech</category>
      <category>api</category>
      <category>debugging</category>
    </item>
    <item>
      <title>AdCP Video Buy vs VAST InLine: The MCP Task Can Finish Before the Player Has a Spot</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Wed, 14 Oct 2026 01:41:28 +0000</pubDate>
      <link>https://dev.to/aleksuix/adcp-video-buy-vs-vast-inline-the-mcp-task-can-finish-before-the-player-has-a-spot-35ed</link>
      <guid>https://dev.to/aleksuix/adcp-video-buy-vs-vast-inline-the-mcp-task-can-finish-before-the-player-has-a-spot-35ed</guid>
      <description>&lt;p&gt;On October 7, 2026 AdCP shipped &lt;a href="https://github.com/adcontextprotocol/adcp/releases/tag/v3.2.3" rel="noopener noreferrer"&gt;v3.2.3&lt;/a&gt; with a security fix that finally enforces request signing on spend-committing calls such as &lt;code&gt;create_media_buy&lt;/code&gt;. That closes a governance hole on the negotiation layer. It does not fetch the tag behind a VAST asset URL or compare &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt; to the &lt;code&gt;duration_ms&lt;/code&gt; in the manifest. The buy can be signed, schema-valid, and complete while the InLine the stitcher needs is still wrong.&lt;/p&gt;

&lt;p&gt;Agentic CTV workflows are moving from demos to production spend. Trade coverage of MCP-enabled buying on platforms like Vibe.co describes agents that read reporting and order actions from a dashboard. The wire split underneath is older: Ad Context Protocol (AdCP) negotiates products, format capabilities, and creative manifests over MCP or A2A. Video delivery still ends in a VAST document (or a wrapper chain that must terminate in one). Treating a green tool response as creative readiness is how you bill for thirty seconds and serve sixty-five.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the AdCP side proves
&lt;/h2&gt;

&lt;p&gt;AdCP sits above the auction and the tag. A buyer agent discovers inventory with &lt;code&gt;get_products&lt;/code&gt;, checks &lt;code&gt;get_adcp_capabilities&lt;/code&gt;, and commits with &lt;code&gt;create_media_buy&lt;/code&gt;. Success means the seller accepted the terms, returned a manifest, and echoed the negotiated &lt;code&gt;adcp_version&lt;/code&gt;. Conformance storyboards grade whether your server speaks the protocol: auth headers, idempotency, schema on each response, and now, on 3.2.x, signatures where the trust model says they are required.&lt;/p&gt;

&lt;p&gt;For video, the manifest typically carries a VAST asset object rather than inline XML:&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;"asset_type"&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"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"delivery_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://adserver.example/vast?pl=12345"&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_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30000&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;The schema marks &lt;code&gt;duration_ms&lt;/code&gt; as optional ("if known"). &lt;code&gt;delivery_type: url&lt;/code&gt; means the bytes were not part of the MCP payload. Format capabilities from AdCP 3.1 add a parallel contract: a capability like &lt;code&gt;video_hosted_16x9&lt;/code&gt; can declare &lt;code&gt;duration_ms_range&lt;/code&gt; or &lt;code&gt;duration_ms_exact&lt;/code&gt; so buyers select slots with explicit bounds.&lt;/p&gt;

&lt;p&gt;None of that is what a CTV player or SSAI packager executes. It is what two agents agreed should be true about a URL. The &lt;a href="https://vastlint.org/docs/adcp-conformance/" rel="noopener noreferrer"&gt;AdCP conformance guide&lt;/a&gt; tells you whether your agent returns valid JSON on those tools. Passing it is not a substitute for opening the tag.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the VAST side actually runs
&lt;/h2&gt;

&lt;p&gt;The player, the SSAI stitcher, and the verification SDK read XML. They follow wrapper hops until they find an InLine (or fail). Inside &lt;code&gt;&amp;lt;Linear&amp;gt;&lt;/code&gt; they need a &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;MediaFiles&amp;gt;&lt;/code&gt; the environment can decode, and, on many CTV paths, attributes and companion structures the buy never mentioned in JSON.&lt;/p&gt;

&lt;p&gt;Example mismatch that every layer can "pass" independently:&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="nt"&gt;&amp;lt;Duration&amp;gt;&lt;/span&gt;00:01:05&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="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1920"&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"1080"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      https://cdn.example/spot.mp4
    &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;Thirty thousand milliseconds in the manifest satisfies a sixty-second format capability bound. Sixty-five seconds in &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt; is what the SDK schedules. Quartile tracking fires on the wrong clock. Some stacks reject the break outright.&lt;/p&gt;

&lt;p&gt;Wrappers make the gap wider. The manifest stores one URL. Hop one can be a &lt;code&gt;&amp;lt;Wrapper&amp;gt;&lt;/code&gt; with no &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt;. Duration lives on the terminal InLine. QA that stops at the first response approves a redirect graph, not a servable creative. Identity fields diverge too: AdCP may reference a creative id in JSON while the tag omits &lt;a href="https://vastlint.org/docs/vast-macros/universaladid/" rel="noopener noreferrer"&gt;UniversalAdId&lt;/a&gt; or ships a registry value the SSAI path cannot resolve. Deprecated &lt;a href="https://vastlint.org/docs/vpaid-rules/" rel="noopener noreferrer"&gt;VPAID&lt;/a&gt; flags can validate against an older XSD while the CTV player ignores or blocks the interactive layer entirely.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://vastlint.org/blog/adcp-3-1-format-capabilities-vast-duration/" rel="noopener noreferrer"&gt;format capabilities write-up&lt;/a&gt; walks the three-number problem (capability bound, manifest field, XML duration). The &lt;a href="https://vastlint.org/blog/adcp-vast-asset-declared-vs-verified/" rel="noopener noreferrer"&gt;declared versus verified asset post&lt;/a&gt; states the same blind spot at the asset record: every field on the VAST asset is a claim about a document nobody parsed during the buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same flight, two verdicts
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What "success" means&lt;/th&gt;
&lt;th&gt;Typical check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AdCP / MCP&lt;/td&gt;
&lt;td&gt;Tool returned 200, manifest validates, buy id issued&lt;/td&gt;
&lt;td&gt;Storyboard, &lt;code&gt;get_adcp_capabilities&lt;/code&gt;, signature on &lt;code&gt;create_media_buy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format capability&lt;/td&gt;
&lt;td&gt;Selected &lt;code&gt;capability_id&lt;/code&gt; matches declared bounds&lt;/td&gt;
&lt;td&gt;JSON Schema on capability objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VAST InLine&lt;/td&gt;
&lt;td&gt;Player can parse, schedule, and render&lt;/td&gt;
&lt;td&gt;XSD or linter rules on fetched XML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSAI / CTV readiness&lt;/td&gt;
&lt;td&gt;Mezzanine, HTTPS, codec, pod rules&lt;/td&gt;
&lt;td&gt;Environment-specific rules beyond XSD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An LLM implementing the buyer agent can hallucinate a plausible manifest, call MCP, and still get an OK from a stub seller. An LLM generating "VAST" can emit well-formed XML with a broken &lt;code&gt;MediaFile&lt;/code&gt; URL. Neither model is a deterministic check on the wire contract.&lt;/p&gt;

&lt;p&gt;When the buy also has to become OpenRTB (display or mixed agentic flows), the same stack may emit bid requests through MCP tools. &lt;strong&gt;rtblint&lt;/strong&gt; (&lt;code&gt;cargo install rtblint&lt;/code&gt;, &lt;code&gt;npm install rtblint-core&lt;/code&gt;, MCP &lt;code&gt;rtblint-mcp&lt;/code&gt;) owns that object, AdCP discovery helpers, and ARTF envelopes. &lt;strong&gt;vastlint&lt;/strong&gt; (&lt;code&gt;cargo install vastlint&lt;/code&gt;, &lt;code&gt;npm install vastlint&lt;/code&gt;) owns the creative envelope the video buy referenced. They are independent checks, not IAB Tech Lab or AAO official harnesses, and the specs do not require them. You still need both when an agent closes both legs.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you catch it before the flight
&lt;/h2&gt;

&lt;p&gt;Split the job the way players split it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step one: live URL in the &lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;VAST tag tester&lt;/a&gt;.&lt;/strong&gt; Paste the URI from the manifest, not XML copied from an earlier debug session. The tester fetches what the endpoint serves now, previews when it can, and shows tracking URLs. Compare parsed &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt; to the &lt;code&gt;duration_ms&lt;/code&gt; you think you bought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step two: the same URL in the &lt;a href="https://vastlint.org/inspect/" rel="noopener noreferrer"&gt;VAST inspector&lt;/a&gt;.&lt;/strong&gt; Resolve wrapper hops through to the InLine. If duration or mezzanine attributes only appear on the last hop, hop-one QA is a false green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step three: wire validation into the agent loop.&lt;/strong&gt; After fetch, run vastlint CLI or MCP on the terminal document. Reject missing &lt;code&gt;&amp;lt;Duration&amp;gt;&lt;/code&gt;, HTTP media on &lt;code&gt;imp.secure&lt;/code&gt; inventory, or capability mismatches before the buy writes to your system of record. The &lt;a href="https://vastlint.org/docs/adcp/" rel="noopener noreferrer"&gt;AdCP and VAST handoff docs&lt;/a&gt; map where protocol negotiation ends and creative verification begins. For agents that also validate OpenRTB in the same session, the &lt;a href="https://rtblint.org/docs/openrtb-validator-mcp/" rel="noopener noreferrer"&gt;OpenRTB validator MCP&lt;/a&gt; doc shows how to keep bid objects on the same deterministic footing.&lt;/p&gt;

&lt;p&gt;Treat "manifest accepted" as an intermediate state, not launch clearance. Signed &lt;code&gt;create_media_buy&lt;/code&gt; on AdCP 3.2.3 proves the seller authenticated the commit. It still does not prove the tag behind &lt;code&gt;delivery_type: url&lt;/code&gt; is the spot you sold.&lt;/p&gt;

</description>
      <category>vast</category>
      <category>adtech</category>
      <category>advertising</category>
      <category>api</category>
    </item>
    <item>
      <title>OpenRTB loss reason 206 changed from an app bundle exclusion to a store ID exclusion.</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Tue, 13 Oct 2026 20:52:45 +0000</pubDate>
      <link>https://dev.to/aleksuix/openrtb-loss-reason-206-changed-from-an-app-bundle-exclusion-to-a-store-id-exclusion-2kb4</link>
      <guid>https://dev.to/aleksuix/openrtb-loss-reason-206-changed-from-an-app-bundle-exclusion-to-a-store-id-exclusion-2kb4</guid>
      <description>&lt;p&gt;A loss dashboard that labels 206 as an app bundle exclusion is reading the OpenRTB 2.5 table. A bidder that labels the same integer as a store ID exclusion is reading the current list. Both integrations are following a published spec. They are following different editions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 2.5 printed
&lt;/h2&gt;

&lt;p&gt;OpenRTB 2.5 section 5.25 introduced the loss reason list. In that table, 206 is Creative Filtered - App Bundle Exclusions. The list ends at 213, Creative Filtered - Not Allowed in PMP Deal. There is no 214 and no 215. Exchange-specific values start at 1000.&lt;/p&gt;

&lt;p&gt;If your exchange integration was written against that PDF and never updated, a blocked bundle still arrives as 206.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the current list says
&lt;/h2&gt;

&lt;p&gt;In September 2021 the OpenRTB 3.0 list renamed 206 from Bundle to App Store ID Exclusions, and added 215 for App Bundle Exclusions. The change log describes the rename as keeping 206's intent consistent across 2.x and 3.x. The printed 2.5 table still says bundle. Treat the September 2021 list as the current definition, and treat a partner who cites 2.5 as still on the older one.&lt;/p&gt;

&lt;p&gt;OpenRTB 2.6 does not print its own loss list. The &lt;code&gt;${AUCTION_LOSS}&lt;/code&gt; macro in 2.6 points at the OpenRTB 3.0 table. On that table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;206 is Creative Filtered - App Store ID Exclusions. The exchange compared the promoted app's store id, &lt;code&gt;bid.bundle&lt;/code&gt;, with &lt;code&gt;bapp&lt;/code&gt; or a publisher block. Play ids are package names. Apple ids are numeric.&lt;/li&gt;
&lt;li&gt;215 is Creative Filtered - App Bundle Exclusions. This is the condition 2.5 reported as 206.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current list also marks exchange-specific values at 500 and above, where 2.5 marked them at 1000 and above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to do with a 206 in production
&lt;/h2&gt;

&lt;p&gt;Ask the exchange which list it implements before you build the alert. An exchange on the current list is telling you the store id was excluded, and a bundle exclusion from that same exchange arrives as 215. An exchange still on the 2.5 table is telling you the bundle was excluded, and it will not send 215 at all.&lt;/p&gt;

&lt;p&gt;Drop the blocked id from the campaign on that inventory, and check the id format before you call it a policy block. A Play package name and an Apple numeric id are both legal &lt;code&gt;bid.bundle&lt;/code&gt; values, and a block on the wrong form looks like a 206 when the campaign was never going to match.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://rtblint.org/docs/openrtb/loss/206/" rel="noopener noreferrer"&gt;loss reason 206 page&lt;/a&gt; and the &lt;a href="https://rtblint.org/docs/openrtb/loss/215/" rel="noopener noreferrer"&gt;loss reason 215 page&lt;/a&gt; spell out the field each code checks. The &lt;a href="https://rtblint.org/guides/openrtb-loss-reason-codes/" rel="noopener noreferrer"&gt;loss reason code table&lt;/a&gt; is the current list, with the 2.5 difference called out above the range notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other codes worth splitting out
&lt;/h2&gt;

&lt;p&gt;Once 206 and 215 are labeled against the right list, the rest of a falling win rate sorts by range. These four are the ones that usually explain the drop, and each one names the field to inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb/loss/100/" rel="noopener noreferrer"&gt;100&lt;/a&gt; compares &lt;code&gt;bid.price&lt;/code&gt; with &lt;code&gt;imp.bidfloor&lt;/code&gt; in &lt;code&gt;imp.bidfloorcur&lt;/code&gt;. The request &lt;code&gt;cur&lt;/code&gt; array does not set the floor currency. A yen floor sent without &lt;code&gt;JPY&lt;/code&gt; is read as USD.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb/loss/102/" rel="noopener noreferrer"&gt;102&lt;/a&gt; is a loss to a higher bid. &lt;code&gt;${AUCTION_MIN_TO_WIN}&lt;/code&gt; on the loss notice, when the exchange fills it, is the price that would have won.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb/loss/203/" rel="noopener noreferrer"&gt;203&lt;/a&gt; is a size mismatch against &lt;code&gt;banner.w&lt;/code&gt; and &lt;code&gt;banner.h&lt;/code&gt;, or any pair in &lt;code&gt;banner.format&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb/loss/212/" rel="noopener noreferrer"&gt;212&lt;/a&gt; is an animation longer than the placement allows. The request does not carry that limit. It lives in the creative policy, and a looping banner keeps drawing 212 until the creative changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://rtblint.org/docs/openrtb/loss/202/" rel="noopener noreferrer"&gt;202&lt;/a&gt; and &lt;a href="https://rtblint.org/docs/openrtb/loss/207/" rel="noopener noreferrer"&gt;207&lt;/a&gt; sit in the same creative range: a &lt;code&gt;crid&lt;/code&gt; the exchange already disapproved, and an http URL inside a creative served where &lt;code&gt;imp.secure&lt;/code&gt; is 1.&lt;/p&gt;

</description>
      <category>openrtb</category>
      <category>adtech</category>
      <category>api</category>
      <category>debugging</category>
    </item>
    <item>
      <title>10 Curated PMP Mistakes That Drop Paid Filters and Kill Fill</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Tue, 13 Oct 2026 00:18:40 +0000</pubDate>
      <link>https://dev.to/aleksuix/10-curated-pmp-mistakes-that-drop-paid-filters-and-kill-fill-366e</link>
      <guid>https://dev.to/aleksuix/10-curated-pmp-mistakes-that-drop-paid-filters-and-kill-fill-366e</guid>
      <description>&lt;p&gt;On &lt;strong&gt;October 6, 2026&lt;/strong&gt;, the IAB Tech Lab opened a &lt;a href="https://adtechsumo.com/news/tech-lab-opens-public-comment-to-streamline-programmatic-f1b04b" rel="noopener noreferrer"&gt;public comment period&lt;/a&gt; aimed at simplifying programmatic plumbing. Curated private marketplace lines are a big part of that conversation: buyers pay for packaging, not just a deal id. On the wire, packaging still collapses to &lt;code&gt;imp.pmp&lt;/code&gt; and a &lt;code&gt;deals&lt;/code&gt; array. When operations treat that shape like a generic PMP row, the upstream filter disappears and fill still clears against the deal name on the IO. That is unbillable confusion at scale, not a UI typo.&lt;/p&gt;

&lt;p&gt;Programmatic curation is sold as inventory plus data, assembled before the auction. OpenRTB never got a "curator" field in the bid request. IAB put terms, scope, and fee disclosure in the Deal Sync sidecar. The auction object remains an ordinary &lt;a href="https://rtblint.org/guides/openrtb-deals-pmp/" rel="noopener noreferrer"&gt;Deal entry&lt;/a&gt;. These ten mistakes are where curated buys silently downgrade to plain PMP traffic.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treating Deal Sync copy as live targeting.&lt;/strong&gt; The sidecar feed is reconciliation metadata. The spec is explicit that criteria pushed through Deal Sync are not guaranteed to match the next bid request. If QA only diffs the sync export, you can ship traffic that never carried the segment or site list the buyer signed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Leaving &lt;code&gt;private_auction&lt;/code&gt; unset on invite-only packages.&lt;/strong&gt; In OpenRTB 2.x, &lt;code&gt;imp.pmp.private_auction&lt;/code&gt; defaults to &lt;strong&gt;0&lt;/strong&gt;, which means open auction competition is allowed unless you set &lt;strong&gt;1&lt;/strong&gt;. Curators often sell "PMP-only" packages while the SSP still emits the default. Bidders who did not buy the package can win, and the curated filter never applied.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Winning on a deal id that was not on the impression you billed.&lt;/strong&gt; Reporting joins on &lt;code&gt;dealid&lt;/code&gt; in the bid response, but finance should trace back to &lt;code&gt;imp.pmp.deals[].id&lt;/code&gt; on the request that actually ran. Multi-imp pods and duplicated deal ids across imps make it easy to attribute fill to a curated line that was not on the imp that cleared.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Expecting curator fee disclosure inside the OpenRTB Deal object.&lt;/strong&gt; Fee and origin fields live in Deal Sync's Curation object, not in &lt;code&gt;imp.pmp.deals&lt;/code&gt;. A bid request validator will never prove fee transparency; conflating the two objects sends buyers looking for fields that are not in the schema.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assuming &lt;code&gt;user.data&lt;/code&gt; segments prove the curator filter ran.&lt;/strong&gt; When a curator applies segments only as an upstream supply filter, those segments may never appear in the bid request at all. Seeing unrelated &lt;code&gt;Data&lt;/code&gt; objects on the request is attribution by convention, not proof that the MFA exclusion list from the one-pager executed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deploying floor changes from the sync file without diffing live &lt;code&gt;bidfloor&lt;/code&gt;.&lt;/strong&gt; Deal floors in traffic are on the Deal object in the request. A mismatch between the sidecar export and live &lt;a href="https://rtblint.org/docs/rules/openrtb-2-6-202309-deal-floor_fields/" rel="noopener noreferrer"&gt;&lt;code&gt;imp.pmp.deals[].bidfloor&lt;/code&gt;&lt;/a&gt; means you can underbid curated inventory or overbid open paths while dashboards still show the old package terms.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Swapping white-label curator ids for publisher-direct ids without updating seat maps.&lt;/strong&gt; The same inventory sometimes clears under different deal ids depending on which SSP node packaged it. Seat allow lists (&lt;code&gt;wseat&lt;/code&gt;) and advertiser block lists (&lt;code&gt;wadomain&lt;/code&gt;) on the Deal object are per id. A seat cleared for one id may be illegal on the renamed curated id.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validating only the first impression in a pod.&lt;/strong&gt; CTV and long-form video requests often carry multiple imps. Curated deals frequently attach to a mid-roll imp while preroll stays open market. Checking imp index zero alone misses the deal attachment the buyer actually purchased.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Patching deal floors or activations on the wrong JSON path.&lt;/strong&gt; Agent and ARTF workflows mutate live objects under time pressure. A mutation aimed at &lt;code&gt;/imp/0/pmp&lt;/code&gt; when the curated deal sits on &lt;code&gt;/imp/1/pmp/deals/0&lt;/code&gt; returns RPC success while the exchange still runs the uncurated object. Path precision matters more than envelope parse status.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Trusting schain completeness on curated resale without matching deal terms.&lt;/strong&gt; Curators resell across SSPs with different &lt;a href="https://rtblint.org/guides/openrtb-schain-1-1-migration/" rel="noopener noreferrer"&gt;SupplyChain 1.1&lt;/a&gt; habits. A valid-looking &lt;code&gt;source.schain&lt;/code&gt; does not replace &lt;code&gt;at&lt;/code&gt;, floor, and seat constraints on the Deal object. You can pass chain validation and still serve inventory outside the curated scope.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One field is worth memorizing: &lt;strong&gt;&lt;code&gt;imp.pmp.private_auction&lt;/code&gt; omitted means open auction&lt;/strong&gt;. That default has burned more curated packages than any single bad segment id, because the buyer still sees a deal id on the line item and assumes exclusivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you catch it
&lt;/h2&gt;

&lt;p&gt;An LLM implementing AdCP, ARTF, or AAMP can emit a bid request that parses and still drop the filter the buy described. The tool call can return OK while the exchange runs a different shape. &lt;strong&gt;rtblint&lt;/strong&gt; (&lt;code&gt;cargo install rtblint&lt;/code&gt;, &lt;code&gt;npm install rtblint-core&lt;/code&gt;, MCP &lt;code&gt;rtblint-mcp&lt;/code&gt;) is the deterministic check on the OpenRTB object: PMP containers, deal ids, floors, and the dated snapshot your partner actually enforces. It is independent of IAB Tech Lab and of AAO; the specs do not require it, but someone has to diff live requests against the deal terms you sold.&lt;/p&gt;

&lt;p&gt;Ingest Deal Sync if you use it, then validate every production template against the same snapshot you pin in CI. The &lt;a href="https://rtblint.org/docs/openrtb/" rel="noopener noreferrer"&gt;OpenRTB docs index&lt;/a&gt; and &lt;a href="https://rtblint.org/docs/what-rtblint-checks/" rel="noopener noreferrer"&gt;what rtblint checks&lt;/a&gt; list the deal and PMP rules that show up on curated lines first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/answers/what-is-curation-in-programmatic/" rel="noopener noreferrer"&gt;What is curation in programmatic?&lt;/a&gt; for the Deal Sync versus bidstream split (companion to the October 2026 simplification comment).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/blog/iab-tech-lab-2026-roadmap-openrtb/" rel="noopener noreferrer"&gt;IAB Tech Lab 2026 roadmap and OpenRTB&lt;/a&gt; for where the Curation object lives relative to &lt;code&gt;imp.pmp.deals&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb-versions/" rel="noopener noreferrer"&gt;OpenRTB version trains&lt;/a&gt; when curated traffic crosses SSPs on different 2.6 snapshots.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/guides/agentic-buying-openrtb/" rel="noopener noreferrer"&gt;Agentic buying and OpenRTB&lt;/a&gt; when negotiation closes above the auction but execution still materializes as deal ids.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/guides/" rel="noopener noreferrer"&gt;Guides index&lt;/a&gt; for PMP vocabulary and schain migration notes on multi-SSP curated packages.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>openrtb</category>
      <category>advertising</category>
      <category>programmatic</category>
      <category>deals</category>
    </item>
    <item>
      <title>The OpenRTB Native Assets Cheat Sheet Every In-App Bid Ops Engineer Needs</title>
      <dc:creator>Aleksander Sekowski</dc:creator>
      <pubDate>Mon, 12 Oct 2026 00:16:47 +0000</pubDate>
      <link>https://dev.to/aleksuix/the-openrtb-native-assets-cheat-sheet-every-in-app-bid-ops-engineer-needs-o9n</link>
      <guid>https://dev.to/aleksuix/the-openrtb-native-assets-cheat-sheet-every-in-app-bid-ops-engineer-needs-o9n</guid>
      <description>&lt;p&gt;In early October 2026 the IAB Tech Lab opened &lt;a href="https://iabtechlab.com/wp-content/uploads/2026/09/Public-Comment-Programmatic-Best-Practices-v1-1.pdf" rel="noopener noreferrer"&gt;public comment on Programmatic Best Practices v1&lt;/a&gt;, including guidance to stop rotating placement IDs when one slot accepts native, display, and video. Multiformat impressions are the norm on in-app feed inventory. The native contract still lives in a string inside &lt;code&gt;imp.native.request&lt;/code&gt;, and the creative answer still lives in another string in &lt;code&gt;bid.adm&lt;/code&gt;. Trade coverage of the comment period landed around &lt;a href="https://adtechsumo.com/news/tech-lab-opens-public-comment-to-streamline-programmatic-f1b04b" rel="noopener noreferrer"&gt;October 6, 2026&lt;/a&gt;. When you debug fill, you need the asset map on one page, not another lecture about JSON escaping.&lt;/p&gt;

&lt;p&gt;This sheet is for engineers who validate bid requests in a dashboard, write bidder adapters, or triage SDK render failures. OpenRTB 2.x transports the &lt;a href="https://iabtechlab.com/openrtb-native-version-1-2-finalized/" rel="noopener noreferrer"&gt;Native Ads specification&lt;/a&gt; as serialized JSON. Version 1.2 is the production baseline most exchanges cite today. The table below pairs what the publisher asks for in the inner request object with what must come back in the Native Markup Response inside &lt;code&gt;adm&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request asset to response field map
&lt;/h2&gt;

&lt;p&gt;Each row is one &lt;code&gt;assets[]&lt;/code&gt; entry in the inner native request JSON (the payload inside &lt;code&gt;imp.native.request&lt;/code&gt; after you parse the string once). The response mirrors the same &lt;code&gt;id&lt;/code&gt; values under &lt;code&gt;assets[]&lt;/code&gt; in &lt;code&gt;adm&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request asset&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;required&lt;/code&gt; on asset&lt;/th&gt;
&lt;th&gt;Key request fields&lt;/th&gt;
&lt;th&gt;Response object&lt;/th&gt;
&lt;th&gt;What must come back&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; = must serve&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;title.len&lt;/code&gt; (max characters)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;assets[].title.text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Non-empty &lt;code&gt;text&lt;/code&gt; within &lt;code&gt;len&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image (icon)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;img.type&lt;/code&gt; = &lt;strong&gt;1&lt;/strong&gt;, optional &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;h&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;assets[].img&lt;/code&gt; with same &lt;code&gt;id&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;url&lt;/code&gt;, width and height matching constraints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image (main)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; or &lt;code&gt;0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;img.type&lt;/code&gt; = &lt;strong&gt;3&lt;/strong&gt;, &lt;code&gt;wmin&lt;/code&gt;/&lt;code&gt;hmin&lt;/code&gt; or ratio&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;assets[].img&lt;/code&gt; with same &lt;code&gt;id&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;url&lt;/code&gt;; main image aspect per Native 1.2 types list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data (description)&lt;/td&gt;
&lt;td&gt;often &lt;code&gt;1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;data.type&lt;/code&gt; = &lt;strong&gt;2&lt;/strong&gt;, &lt;code&gt;len&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;assets[].data.value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copy within &lt;code&gt;len&lt;/code&gt;; type &lt;strong&gt;2&lt;/strong&gt; is description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data (sponsored by)&lt;/td&gt;
&lt;td&gt;common&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;data.type&lt;/code&gt; = &lt;strong&gt;1&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;assets[].data.value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Brand or sponsor line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Video (native asset)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1&lt;/code&gt; when offered&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;video.mimes&lt;/code&gt;, &lt;code&gt;minduration&lt;/code&gt;, &lt;code&gt;maxduration&lt;/code&gt;, &lt;code&gt;protocols&lt;/code&gt; (all required in request)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;assets[].video.vasttag&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;VAST XML string the SDK can hand to a player&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;not an asset row&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;link&lt;/code&gt; optional in request&lt;/td&gt;
&lt;td&gt;top-level &lt;code&gt;link.url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Landing URL; required in response even when request omits it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event trackers (1.2)&lt;/td&gt;
&lt;td&gt;per &lt;code&gt;eventtrackers[]&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;event&lt;/code&gt;, &lt;code&gt;methods&lt;/code&gt; bitmask&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;eventtrackers[]&lt;/code&gt; with &lt;code&gt;event&lt;/code&gt;, &lt;code&gt;method&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Match requested viewability or impression events&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Also keep &lt;code&gt;imp.native.ver&lt;/code&gt; aligned with the inner &lt;code&gt;"ver"&lt;/code&gt; field (typically &lt;code&gt;"1.2"&lt;/code&gt;). A bid response whose outer OpenRTB validates can still ship &lt;code&gt;adm&lt;/code&gt; at &lt;code&gt;"1.1"&lt;/code&gt; while the request asked for event tracker objects that only exist in 1.2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rows that validate and still break in production
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Title asset &lt;code&gt;required: 1&lt;/code&gt; with a missing &lt;code&gt;id&lt;/code&gt; in &lt;code&gt;adm&lt;/code&gt;.&lt;/strong&gt; OpenRTB only requires that &lt;code&gt;imp.native.request&lt;/code&gt; be a string and that &lt;code&gt;adm&lt;/code&gt; be a string when &lt;code&gt;mtype&lt;/code&gt; declares native. Nothing in the core schema asserts that asset id &lt;code&gt;2&lt;/code&gt; in the request appears in the response. The exchange accepts the bid; the SDK renders a blank card. Pairing checks belong on the decoded native objects, documented under &lt;a href="https://rtblint.org/docs/rule-reference/openrtb-native-asset-required_missing/" rel="noopener noreferrer"&gt;&lt;code&gt;openrtb-native-asset-required_missing&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main image type 3 requested, type 1 icon returned.&lt;/strong&gt; Both sides pass type checks as generic &lt;code&gt;img&lt;/code&gt; objects. The mediation SDK expected a 1200×627 main image and received a 80×80 icon. Fill counts as served; viewability and CTR do not recover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legacy &lt;code&gt;{ "native": { ... } }&lt;/code&gt; wrapper inside &lt;code&gt;request&lt;/code&gt;.&lt;/strong&gt; Native 1.1 dropped the root wrapper from 1.0, but older publisher SDKs still emit it. Parsers that expect a flat object accept the string and never descend into &lt;code&gt;assets&lt;/code&gt;. See &lt;a href="https://rtblint.org/docs/rule-reference/openrtb-native-request-legacy_wrapper/" rel="noopener noreferrer"&gt;&lt;code&gt;openrtb-native-request-legacy_wrapper&lt;/code&gt;&lt;/a&gt; and the walkthrough at &lt;a href="https://rtblint.org/blog/native-request-is-a-json-string/" rel="noopener noreferrer"&gt;&lt;code&gt;native-request-is-a-json-string&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How you catch it
&lt;/h2&gt;

&lt;p&gt;An LLM implementing a bidder or an AdCP-to-OpenRTB mapper can emit plausible JSON for both strings and still miss asset ids, &lt;code&gt;required&lt;/code&gt; flags, or Native 1.2 event trackers. The RPC and the exchange ACK do not re-run that pairing for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rtblint.org" rel="noopener noreferrer"&gt;RTBlint&lt;/a&gt; (&lt;code&gt;cargo install rtblint&lt;/code&gt;, &lt;code&gt;npm install rtblint-core&lt;/code&gt;, MCP &lt;code&gt;rtblint-mcp&lt;/code&gt;) validates the OpenRTB object, decodes &lt;code&gt;imp.native.request&lt;/code&gt;, and checks native markup in &lt;code&gt;adm&lt;/code&gt; against the request assets. It is independent of IAB Tech Lab and of AAO; the spec does not mandate it, but it is the deterministic check on the wire shape agents keep getting wrong.&lt;/p&gt;

&lt;p&gt;When the response row is &lt;strong&gt;Video (native asset)&lt;/strong&gt;, &lt;code&gt;assets[].video.vasttag&lt;/code&gt; is a full VAST envelope, not a hint. Paste the decoded XML into the &lt;a href="https://vastlint.org/tester/" rel="noopener noreferrer"&gt;VAST tag tester&lt;/a&gt; to fetch media and tracking from a live URL, and use the &lt;a href="https://vastlint.org/inspect/" rel="noopener noreferrer"&gt;VAST inspector&lt;/a&gt; if that tag is a wrapper chain. Native fill often dies on the VAST half after the OpenRTB half already cleared QA.&lt;/p&gt;

&lt;p&gt;Start from a known-good fixture in the &lt;a href="https://rtblint.org/docs/openrtb-examples/native-bid-request/" rel="noopener noreferrer"&gt;native bid request example&lt;/a&gt;, then diff your production traffic with &lt;code&gt;rtblint validate&lt;/code&gt; on the bid response direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/docs/openrtb/native/" rel="noopener noreferrer"&gt;Native ads in OpenRTB&lt;/a&gt;: &lt;code&gt;imp.native&lt;/code&gt; fields, asset type lists, and 1.1 versus 1.2 deltas.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rtblint.org/blog/native-request-is-a-json-string/" rel="noopener noreferrer"&gt;Blog: request is a JSON string&lt;/a&gt;: why double encoding on the way in and out breaks naive parsers.&lt;/li&gt;
&lt;li&gt;Rule references above for required asset pairing and legacy wrappers when you need the exact finding id in CI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bookmark the table. When multiformat placements share one ID, the native string is still the only place that says which headline and main image the renderer must get back.&lt;/p&gt;

</description>
      <category>openrtb</category>
      <category>javascript</category>
      <category>advertising</category>
      <category>adtech</category>
    </item>
  </channel>
</rss>
