DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

A Native Bid Skips required: 1. The Response Still Validates Alone.

A native campaign wins the auction. The exchange accepts the bid. The in-feed unit renders with a hero image and no headline.

The DSP did not forget to write copy. The bid returned asset id 2 (the image) and never returned asset id 1, even though the publisher's native template marked id 1 as mandatory. OpenRTB still looks fine if you only stare at the response JSON.

required lives inside the string

OpenRTB carries the Native Markup Request on imp.native.request. That field is a string, not a nested object. Inside the string, Native Ads 1.2 lists assets, and each asset can set required to 1 when the placement cannot render without that slot.

A typical in-feed ask might look like this after you decode the string (the wire format is one escaped JSON blob on request):

{
  "ver": "1.2",
  "assets": [
    {"id": 1, "required": 1, "title": {"len": 90}},
    {"id": 2, "img": {"type": 3, "wmin": 1200, "hmin": 627}}
  ]
}
Enter fullscreen mode Exit fullscreen mode

Asset 1 is the headline contract. Asset 2 is the main image. Both ids are valid. Only id 1 carries required: 1.

The bid side answers with mtype: 4 and a Native Markup Response in adm, again as a JSON string. A response that only fills the image is still well typed:

{
  "ver": "1.2",
  "link": {"url": "https://brand.example/landing"},
  "assets": [
    {"id": 2, "img": {"url": "https://cdn.example/hero.png"}}
  ]
}
Enter fullscreen mode Exit fullscreen mode

Run that response through a structural validator and you often get a clean pass. The headline was never optional on the request side; it was required on an asset the bidder chose not to return.

That gap is relational. The requirement is declared in document A (the bid request's native string). The fulfillment is declared in document B (adm). Schema validation on B alone never opens A.

What the exchange actually checks

Some stacks reject incomplete native markup at render time. Many do not until the slot looks broken in production. Prebid Server and several SSP validators do walk the native string on the request, but bidders still ship responses copied from banner workflows: image URL plus click URL, no title object.

The failure mode is quiet. You pay for an in-feed native placement, measure clicks on the image, and never know the required text asset was absent because nothing in your CI paired the two payloads.

The same class of bug shows up when ids match but types do not: the request asked for a title on id 1 and the response sends an img on id 1. That is a different rule (openrtb.native.asset.type_mismatch), but the fix is the same habit: keep the request fixture beside the response fixture.

How you catch it

Pair validation is the check that belongs in integration tests. You already store a sample imp.native.request string (or a full bid request) next to expected bidder output.

$ rtblint validate --type response --request req.json resp.json
FAILED (OpenRTB 2.6-202606 bid response): 1 error(s), 0 warning(s).
- [error] seatbid[0].bid[0].adm: The native request marked asset id 1 as required, but the response does not return it. (openrtb.native.asset.required_missing) ยท spec 4.2.3
Enter fullscreen mode Exit fullscreen mode

The request-only pass still matters for encoding mistakes on the string itself (double JSON encoding, legacy {"native": {...}} wrappers, missing assets). Those are covered in the native request string write-up and in the OpenRTB native field notes. Pairing is what catches required after both sides parse.

An implementation that lets a model emit OpenRTB, ARTF mutations, or AdCP-to-OpenRTB mappings still has to verify the native string and the native adm on the wire. rtblint (cargo install rtblint, npm install rtblint-core, MCP rtblint-mcp) is the package that owns that bid object and those nested native payloads. It is independent of IAB Tech Lab and of AAO; the spec does not require it, but the contract does not move when an agent hallucinates a compliant-looking bid.

$ cargo install rtblint
$ rtblint validate --type response --request req.json resp.json
Enter fullscreen mode Exit fullscreen mode

The openrtb.native.asset.required_missing finding is stable for CI: fail the build when a required id disappears.

Where to go next

If you are rebuilding fixtures, start from a full native bid request example so imp.native.request stays a string on the wire, then add a response that returns every required: 1 id before you tune optional assets.

When you extend the same pipeline to agentic hops, the native contract still ends as these two JSON documents. Validate the request string first, validate the response against that request second, and treat a missing required headline the same way you would an unknown impid: a bid that should not have cleared.

Top comments (0)