Blog · Agentic
OpenRTB has two JSON encodings, and ARTF's own sample corpus uses both
Ask an OpenRTB engineer what imp.secure holds and you get 0 or 1, because that is what the specification says. Ask someone who compiled the IAB's OpenRTB protobuf schema and you get true or false, because that is what the proto declares. Both are right about their own transport. The problem is that the payload carries no marker saying which one it is, and one of the two is unparseable to the other side.
The divergence is 28 fields wide
The OpenRTB specification types a family of flag fields as integers with the value set {0, 1}. The protobuf schema in com.iabtechlab.openrtb.v2, which is what every gRPC bidstream integration compiles against, declares them bool. Diffing the two mechanically, object by object and field by field, produces 28 disagreements:
| Object | Fields the proto declares bool and the spec types integer |
|---|---|
Imp | secure, instl, clickbrowser, rwdd |
Banner | topframe, vcm |
Video | skip, boxingallowed |
Audio | stitched |
Pmp | private_auction |
Site | mobile, privacypolicy |
App | paid, privacypolicy |
Content | livestream, sourcerelationship, embeddable |
Device | dnt, lmt, js, geofetch |
UserAgent | mobile |
Regs | coppa, gdpr |
Source | fd |
SupplyChain | complete |
SupplyChainNode | hp |
SeatBid | group |
These are not obscure corners. regs.coppa and regs.gdpr are regulatory signals, imp.secure decides whether a creative can load, pmp.private_auction decides whether an auction is open or closed, and schain's complete and hp are the fields the whole supply chain check rests on.
Neither encoding is a bug, which is what makes it expensive
The protobuf JSON mapping has no integer-to-bool coercion. A bool field accepts the literals true and false and nothing else, so "coppa": 0 does not become false. It fails the unmarshal, and it fails it for the whole message rather than the one field. One stray flag drops the entire request.
Going the other way, a typed JSON consumer reading "secure": true against the specification's own types rejects the value or, worse, binds it to a default and carries on. The reported error in both directions is a plain type mismatch, which sends the reader to check a serializer that is working correctly. The mistake is never in the code that produced the payload. It is in the assumption about who is going to read it.
There is a quieter version of the same problem in the field names. Protobuf JSON emits lowerCamelCase unless the serializer asks for proto names, so private_auction serialises as privateAuction and us_privacy as usPrivacy. A protobuf reader accepts both spellings, so a protobuf-to-protobuf hop works and nothing complains. Hand that payload to a JSON consumer and the field is not wrong, it is absent: a private auction reads as an open one and a privacy string goes unread, with no error anywhere.
ARTF is where the two encodings meet
The Agentic RTB Framework carries an OpenRTB payload inside a protobuf envelope and hands it to an agent, which proposes mutations back. We wrote in July about the gap that nothing in the framework checks whether the mutated auction is still valid OpenRTB. The dialect split is the layer underneath that: before anyone can check a mutation, both sides have to agree on what the payload even says.
The reference implementation ships five sample envelopes. Validated as protobuf JSON, which is what the framework transports, two of them cannot be parsed at all:
samples/multi-impression.json 4 errors bid_request.device.js 1 protojson wants true bid_request.regs.coppa 0 protojson wants false bid_request.regs.gdpr 0 protojson wants false bid_request.source.fd 1 protojson wants true samples/native-ad.json 2 errors bid_request.device.js 1 bid_request.regs.coppa 0
The other three samples are protobuf JSON and are correct as such. samples/video-deals.json writes "private_auction": true, "livestream": false, and "coppa": false, which is exactly right for the transport and exactly wrong if you read the same file as an OpenRTB bid request. Run it that way and four fields fail in the opposite direction.
So the corpus does not have a bug in it. It has both encodings in it, in different files, with nothing marking which is which. That is what an undeclared dialect looks like once a specification has more than one implementation. A patch fixing the sample encodings is open upstream as PR #13.
Two smaller divergences sit alongside it, both between the ARTF v1.0 document and the .proto in the same repository. The document's worked examples write "intent": "activateSegments", "op": "add", and a "value": {"IDsPayload": [...]} wrapper; the proto defines ACTIVATE_SEGMENTS, OPERATION_ADD, and top-level oneof members. And the document's illustrative message block numbers tmax as field 6 and ext as field 3, where the proto uses 3 for tmax and 6 for originator. Anyone generating code from the document rather than the file produces something the reference server cannot read.
What to do about it
- Decide the dialect per hop, and write it down. The payload will not tell you. A JSON exchange boundary is spec JSON; a gRPC extension point is protobuf JSON. The conversion belongs at the boundary, once, not scattered through whichever field handler noticed first.
- Set
UseProtoNameswhen you serialise. ARTF's own Go server does. Camel-cased field names survive a protobuf hop silently and vanish at the first JSON one. - Validate the sample corpus in CI, whatever the specification. Both defects here are the kind a strict unmarshal catches on every commit and a code review catches never, because the payload looks right in both dialects to a reader who knows only one.
- Do not let a type error stand in for a dialect error. "Expects integer but received boolean" sends an engineer to audit code that is correct. Naming the other encoding gets the fix right on the first try.
Both dialects, in the validator
RTBlint 0.8.0 validates against a declared dialect rather than assuming one. openrtb.dialect.bool_for_integer fires when protobuf flags arrive in spec JSON, openrtb.dialect.integer_for_bool when spec flags arrive in protobuf JSON, and openrtb.dialect.camel_case_name on the silent one. Each names the other encoding and the value to send instead.
ARTF payloads need no flag: an envelope is protobuf JSON by construction, so --type artf-request validates the envelope and everything it carries in the right dialect, and --type artf-response checks a mutation set against the auction it targets.
rtblint validate --dialect proto-json bid-request.json rtblint validate --type artf-request rtb-request.json rtblint validate --type artf-response --apply --request rtb-request.json rtb-response.json
The same checks run in the browser tester, over MCP as tools an agent can call on its own output before it answers, and over gRPC, which is where an orchestrator wants them, since ARTF already has the connection open.
Further reading
- ARTF explained: the Agentic RTB Framework and where it sits in the auction
- ARTF lets agents rewrite the bid request in flight. Nothing in it checks the result.
- The diagnostic code reference, including every ARTF and dialect finding
- IABTechLab/agentic-real-time-framework: specification, protobuf definitions, and sample payloads