Validate OpenRTB bid requests and responses against IAB specs. ARTF, Rust core.
MCP Server: io.github.aleksUIX/rtblint (RTBlint)
RTBlint is an OpenRTB linter that validates OpenRTB 2.x bid requests and bid responses against versioned IAB Tech Lab spec snapshots. It uses a Rust core (“ARTF, Rust core”) and supports IAB snapshots from 2.0 through monthly 2.6 releases (currently up to 2.6-202606).
🛠️ Key Features
Validates OpenRTB bid requests and bid responses
Checks against versioned IAB Tech Lab spec snapshots
Supports OpenRTB 2.0 through monthly 2.6 releases (up to 2.6-202606)
Implemented as an ARTF/Rust core with 6 tools
🚀 Use Cases
Programmatic-advertising quality checks for OpenRTB traffic
Linting/validation of RTB artifacts against IAB Tech Lab specs
Developer workflows needing an OpenRTB validator
⚡ Developer Benefits
Versioned spec validation for OpenRTB 2.x
Rust-based core for consistent validation behavior
Categorized as a linter/validator in ad-tech and RTB tooling
⚠️ Limitations
Scope is limited to OpenRTB bid requests and bid responses and IAB Tech Lab spec snapshots from 2.0–2.6 monthly releases
Validate an OpenRTB 2.x bid request JSON payload against a tracked spec version. Returns structured issues with rule ids, severities, and JSON paths.
Parameters3
payload
string
required
The OpenRTB bid request as a raw JSON string.
version
string
optional
OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0
dialect
string
optional
JSON dialect the payload is written in. spec-json (default) types flag fields such as imp.secure, regs.coppa and pmp.private_auction as integers, the way the OpenRTB specification does. proto-json follows the IAB OpenRTB protobuf schema, which declares 28 of those fields bool, so true/false is correct there and an integer is the error. Use proto-json for anything that came off a gRPC bidstream integration.
Raw schema
{
"type": "object",
"properties": {
"payload": {
"type": "string",
"description": "The OpenRTB bid request as a raw JSON string."
},
"version": {
"type": "string",
"description": "OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0"
},
"dialect": {
"type": "string",
"enum": [
"spec-json",
"proto-json"
],
"description": "JSON dialect the payload is written in. spec-json (default) types flag fields such as imp.secure, regs.coppa and pmp.private_auction as integers, the way the OpenRTB specification does. proto-json follows the IAB OpenRTB protobuf schema, which declares 28 of those fields bool, so true/false is correct there and an integer is the error. Use proto-json for anything that came off a gRPC bidstream integration."
}
},
"required": [
"payload"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
validate_bid_response
Validate an OpenRTB 2.x bid response JSON payload against a tracked spec version. Optionally cross-validate it against the originating bid request (impid, mtype, adm markup, dealid, seat, and currency coherence). Returns structured issues with rule ids, severities, and JSON paths.
Parameters4
payload
string
required
The OpenRTB bid response as a raw JSON string.
version
string
optional
OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0
dialect
string
optional
JSON dialect the payload is written in. spec-json (default) types flag fields such as imp.secure, regs.coppa and pmp.private_auction as integers, the way the OpenRTB specification does. proto-json follows the IAB OpenRTB protobuf schema, which declares 28 of those fields bool, so true/false is correct there and an integer is the error. Use proto-json for anything that came off a gRPC bidstream integration.
bid_request
string
optional
Optional: the originating OpenRTB bid request as a raw JSON string. When supplied, every bid is also cross-checked against the Imp it references.
Raw schema
{
"type": "object",
"properties": {
"payload": {
"type": "string",
"description": "The OpenRTB bid response as a raw JSON string."
},
"version": {
"type": "string",
"description": "OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0"
},
"dialect": {
"type": "string",
"enum": [
"spec-json",
"proto-json"
],
"description": "JSON dialect the payload is written in. spec-json (default) types flag fields such as imp.secure, regs.coppa and pmp.private_auction as integers, the way the OpenRTB specification does. proto-json follows the IAB OpenRTB protobuf schema, which declares 28 of those fields bool, so true/false is correct there and an integer is the error. Use proto-json for anything that came off a gRPC bidstream integration."
},
"bid_request": {
"type": "string",
"description": "Optional: the originating OpenRTB bid request as a raw JSON string. When supplied, every bid is also cross-checked against the Imp it references."
}
},
"required": [
"payload"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}
validate_artf_request
Validate an ARTF (IAB Tech Lab Agentic Real Time Framework) RTBRequest envelope: required members, lifecycle and payload coherence, tmax plausibility, originator and applicable_intents enums, plus full OpenRTB validation of the bid request and bid response it carries. The carried payloads are protobuf JSON, so they are validated in that dialect.
Parameters2
payload
string
required
The ARTF RTBRequest envelope as a raw JSON string.
version
string
optional
OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0
Raw schema
{
"type": "object",
"properties": {
"payload": {
"type": "string",
"description": "The ARTF RTBRequest envelope as a raw JSON string."
},
"version": {
"type": "string",
"description": "OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0"
}
},
"required": [
"payload"
]
}
validate_artf_response
Validate an ARTF RTBResponse mutation set against the RTBRequest it answers: envelope id echo, declared intent against applicable_intents, operation and payload coherence, and whether each semantic path (/imp/{id}, /imp/{id}/pmp/deals/{id}, /user/data/segment, /seatbid/{seat}/bid/{id}) resolves to something the auction actually carries. With apply=true the mutations are written into the payloads and revalidated, reporting the OpenRTB findings the mutations introduced. Call this before proposing mutations to an orchestrator.
Parameters4
payload
string
required
The ARTF RTBResponse as a raw JSON string.
rtb_request
string
required
The ARTF RTBRequest envelope this response answers, as a raw JSON string. Required: a mutation is only meaningful relative to the auction it targets.
apply
boolean
optional
Apply the mutations and revalidate the result (default false). Returns the mutated payloads and the findings the mutations introduced, with pre-existing findings filtered out.
version
string
optional
OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0
Raw schema
{
"type": "object",
"properties": {
"payload": {
"type": "string",
"description": "The ARTF RTBResponse as a raw JSON string."
},
"rtb_request": {
"type": "string",
"description": "The ARTF RTBRequest envelope this response answers, as a raw JSON string. Required: a mutation is only meaningful relative to the auction it targets."
},
"apply": {
"type": "boolean",
"description": "Apply the mutations and revalidate the result (default false). Returns the mutated payloads and the findings the mutations introduced, with pre-existing findings filtered out."
},
"version": {
"type": "string",
"description": "OpenRTB version id to validate against (default 2.6-202606). One of: 2.0, 2.1, 2.2, 2.3, 2.3.1, 2.4, 2.5, 2.6-202204, 2.6-202210, 2.6-202211, 2.6-202303, 2.6-202309, 2.6-202402, 2.6-202409, 2.6-202501, 2.6-202505, 2.6-202606, 3.0"
}
},
"required": [
"payload",
"rtb_request"
]
}
list_openrtb_versions
List every OpenRTB version id this build can validate against, from 2.0 through the monthly 2.6 snapshots. Call this before pinning version on a validate_* tool.
AdCP protocol discovery. Returns the AdCP releases this agent speaks and the bid-stream conformance metrics it computes. Call this first when wiring rtblint into an agentic buying pipeline: it declares the experimental measurement protocol and the metric ids (openrtb_error_count, openrtb_warning_count, openrtb_conformance_rate) that validate_bid_request and validate_bid_response produce. Part of the Ad Context Protocol (AdCP 3.1) specification.
Parameters6
adcp_version
string
optional
Release-precision AdCP version the caller pins (for example "3.1"). When the pin is not in supported_versions the call returns a VERSION_UNSUPPORTED error naming the releases that would work. When omitted, the highest supported release is served.
adcp_major_version
integer
optional
Deprecated in favour of adcp_version. AdCP major version the caller's payloads conform to. When omitted, assumes the highest supported major.
protocols
array
optional
Filter which per-protocol capability blocks are returned. Does not narrow supported_protocols, which declares what the agent implements.
context
object
optional
Caller-supplied context object. Echoed back unchanged in the response.
idempotency_key
string
optional
AdCP 3.1 carries an idempotency key on every task request, reads included. This agent has no mutating surface and no replay store, so the key is accepted and ignored rather than rejected as an unknown field.
ext
object
optional
Caller extension object. Accepted and ignored; declared so envelope fields do not trip strict request-wrapper validation.
Raw schema
{
"type": "object",
"properties": {
"adcp_version": {
"type": "string",
"description": "Release-precision AdCP version the caller pins (for example \"3.1\"). When the pin is not in supported_versions the call returns a VERSION_UNSUPPORTED error naming the releases that would work. When omitted, the highest supported release is served."
},
"adcp_major_version": {
"type": "integer",
"description": "Deprecated in favour of adcp_version. AdCP major version the caller's payloads conform to. When omitted, assumes the highest supported major."
},
"protocols": {
"type": "array",
"items": {
"type": "string"
},
"description": "Filter which per-protocol capability blocks are returned. Does not narrow supported_protocols, which declares what the agent implements."
},
"context": {
"type": "object",
"description": "Caller-supplied context object. Echoed back unchanged in the response."
},
"idempotency_key": {
"type": "string",
"description": "AdCP 3.1 carries an idempotency key on every task request, reads included. This agent has no mutating surface and no replay store, so the key is accepted and ignored rather than rejected as an unknown field."
},
"ext": {
"type": "object",
"description": "Caller extension object. Accepted and ignored; declared so envelope fields do not trip strict request-wrapper validation."
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "GetAdcpCapabilitiesInput"
}
OpenRTB linter. Validates OpenRTB 2.x bid requests and bid responses against versioned IAB Tech Lab spec snapshots, from 2.0 through the monthly 2.6 releases (currently up to 2.6-202606).
Required fields, including required non-empty arrays
Unknown objects and fields per version catalog (ext subtrees stay open)
Type mismatches (string, integer, float, boolean, object, and array forms)
Documented enum values, including AdCOM lists and vendor ranges (500+)
Deprecated, moved, removed, and not-yet-available fields across versions
Semantic rules: site/app/dooh exclusivity, imp media type presence, skippable video dependencies, duration exclusivity, seatbid/nbr presence on responses, and more
Response markup coherence: bid.adm content vs the declared bid.mtype (native JSON encoding, VAST/DAAST roots, double-encoded payloads)
Request/response cross-validation: with the originating request supplied, every bid's impid, mtype, adm markup, dealid, seat, and currency are checked against what the request actually offered
JSON dialect: spec JSON types flag fields such as imp.secure and regs.coppa as integers, while the IAB OpenRTB protobuf schema declares 28 of them bool. Either encoding is correct on its own transport and wrong on the other, so the caller declares which one it meant
Exchange profiles: documented protocol extras on top of the spec. --profile google-ab accepts at: 3 (FIXED_PRICE) and requires Imp.ext.billing_id. --profile prebid-server requires each Imp to name a bidder or stored request and refuses wseat/bseat. --profile xandr requires ext.appnexus.seller_member_id and video ext.appnexus.context. --profile magnite requires xAPI identity fields (imp.ext.rp.zone_id, site/app ext.rp.site_id, publisher.ext.rp.account_id). Business policy stays out
Nested specs OpenRTB carries as strings or opaque ext: Native Ads 1.2 markup (imp.native.request and native bid.adm, including required-asset pairing), GPP header vs gpp_sid and TCF 2 shape, ${AUCTION_*} macros on billing and loss URLs, EID/SUA structure, SKAdNetwork ext.skadn
Privacy signal contradictions: regs.coppa / device.lmt / device.dnt / regs.gdpr versus identifiers on the same payload, TCF Purpose 1 versus device-storage IDs, US Privacy Opt-Out Sale versus hashed EIDs, email-shaped user.id / site.page, DSA Transparency field presence. Findings describe the document. They do not state a legal conclusion.
ARTF envelopes and mutation sets, including applying the mutations and revalidating what comes out
Every finding carries a stable rule id, a severity, a message, and a JSON path.
ARTF
ARTF, the IAB Tech Lab Agentic Real Time Framework, hands an agent an OpenRTB payload inside an RTBRequest envelope and takes back mutations: proposed changes the orchestrator may accept or reject one at a time. Nothing in the framework checks that the auction still validates once they are applied, and a mutation is only meaningful relative to the request it targets.
bash
# The envelope, plus full OpenRTB validation of what it carries
rtblint validate --type artf-request rtb-request.json
# The mutation set against the auction it targets
rtblint validate --type artf-response --request rtb-request.json rtb-response.json
# Apply the mutations, revalidate, and report only what the mutations broke
rtblint validate --type artf-response --apply --request rtb-request.json rtb-response.json
Three passes:
Envelope. Required members, lifecycle against the payloads actually carried, tmax plausibility for an in-auction call, originator and applicable_intents enum values, and the carried bid request and bid response validated as protobuf JSON.
Mutations. The response id echoes the extension point request id (not the bid request id), each declared intent is in applicable_intents, the operation and payload oneof member match the intent, and every semantic path (/imp/{id}, /imp/{id}/pmp/deals/{id}, /user/data/segment, /seatbid/{seat}/bid/{id}) resolves to something the auction carries. ADJUST_DEAL_MARGIN is reported as having no OpenRTB field to write to, because it does not.
Applied. The mutations are written in and the result revalidated, reporting the OpenRTB findings the mutations introduced with pre-existing findings filtered out. What the agent broke, not what arrived broken.
The ARTF v1.0 document and its .proto use different vocabularies for the same mutation (activateSegments and a value: {IDsPayload: ...} wrapper against ACTIVATE_SEGMENTS and top-level oneof members). Payloads written from the document are mapped and reported as artf.mutation.legacy_spec_encoding rather than dismissed as unknown.
OpenRTB 3.0 validates through its layered envelope: the transport objects (Openrtb, Request, Item, Deal, Source, Response, Seatbid, Bid) and the AdCOM 1.0 domain objects under item.spec (Placement), bid.media (Ad), and request.context. A 2.x payload sent to a 3.0 validator gets a migration diagnostic rather than a bare parse error. The 2.6-202204 snapshot has no extracted catalog and reports itself as unsupported instead of passing payloads silently. See ROADMAP.md for what's next and CHANGELOG.md for release history.
--request supplies the originating bid request so the response is also cross-validated against it (works with --batch too: one request, many response lines). --dialect proto-json validates a payload that came off a gRPC bidstream integration. --profile google-ab applies Google Authorized Buyers' documented protocol extras (at: 3 FIXED_PRICE, required Imp.ext.billing_id) on top of the spec. --profile prebid-server applies Prebid Server /openrtb2/auction extras (bidder or stored request on each Imp, no wseat/bseat). --profile xandr applies Microsoft Monetize extras (ext.appnexus.seller_member_id, video ext.appnexus.context). --profile magnite applies Magnite xAPI identity fields. --resolve --cache <dir> checks SupplyChain hops against sellers.json and the publisher's ads.txt / app-ads.txt from a local directory:
Nothing is fetched; populate the cache yourself. --batch lints one JSON object per line from a file or stdin. --summary adds rule-frequency totals for a captured stream (--summary bids.ndjson for the histogram alone). See ARTF for --type artf-request and --type artf-response.
version selects the CLI release (auto follows the action's own v* tag). spec-version is the OpenRTB snapshot. Linux and macOS runners, x86_64 and aarch64.
Node
js
import { validate, validateResponse, validateResponseAgainstRequest } from"rtblint-core";
const report = validate(JSON.stringify(bidRequest), "2.6-202505");
if (!report.valid) {
for (const issue of report.issues) {
console.log(`[${issue.severity}] ${issue.path}: ${issue.message} (${issue.id})`);
}
}
// Cross-validate a response against the request it answers.const paired = validateResponseAgainstRequest(
JSON.stringify(bidResponse),
JSON.stringify(bidRequest)
);
For gRPC bidstream payloads and ARTF:
js
import {
validateDialect,
validateProfile,
validateArtfRequest,
validateArtfResponseApplied,
protoBoolDivergences,
} from"rtblint-core";
validateDialect(JSON.stringify(bidRequest), "proto-json");
validateProfile(JSON.stringify(bidRequest), "google-ab");
validateProfile(JSON.stringify(bidRequest), "prebid-server");
validateArtfRequest(JSON.stringify(rtbRequest));
// { result, application }: what the mutations broke, and the payloads they producedconst { result, application } = validateArtfResponseApplied(
JSON.stringify(rtbResponse),
JSON.stringify(rtbRequest)
);
protoBoolDivergences(); // the 28 fields the two schemas type differently
MCP server
Hosted Streamable HTTP (no install): https://rtblint.org/mcp. Smithery listing: aleksander/rtblint (same account as vastlint). Payloads sent to the hosted endpoint may be stored (identifiers stripped) so the rules can be improved; see rtblint.org/privacy. Local rtblint-mcp over stdio does not send payloads.
rtblint-mcp also speaks MCP over stdio. Tools: validate_bid_request, validate_bid_response (optional bid_request for cross-validation), validate_artf_request, validate_artf_response (apply writes the mutations and revalidates), list_openrtb_versions, get_adcp_capabilities. Validation tools take optional dialect and profile arguments.
The ARTF tools are the guardrail an agent calls around its own work: check the envelope it was handed, then check the mutation set it is about to propose, before the orchestrator sees it.
validate_bid_request_with_profile applies an exchange profile (Profile::GoogleAuthorizedBuyers, Profile::PrebidServer) on top of the spec. validate_bid_request_with_dialect selects spec JSON vs protobuf JSON.
JSON Schemas
schemas/ holds a JSON Schema (draft 2020-12) per tracked version, for both payload types, generated from the same catalogs the validator uses. IAB Tech Lab publishes no JSON Schema for 2.6 or 3.0, so these are the machine-readable contract for each monthly snapshot:
They cover structure, types, required fields, documented value sets, and AdCOM enum lists. What they cannot express is what the linter adds: deprecated and moved paths, version-specific removals, and the semantic rules. Validating against a schema is not the same as linting.
Regenerate after any catalog change (CI fails if they drift):
bash
cargo run -p rtblint-core --example export_json_schemas
Documentation
Beyond this README, rtblint.org hosts the reference material:
Diagnostic code reference: every stable issue id, each with a page covering what it means, why it matters, and how to fix it
The validator runs on structured catalogs extracted from the IAB Tech Lab OpenRTB specifications: object names, field names, type notations, enumerated value sets, and section citations. The catalogs carry no spec prose. The dialect table is derived the same way, by comparing those catalogs against the field types the IAB OpenRTB protobuf schema declares. RTBlint is not affiliated with or endorsed by IAB Tech Lab. See NOTICE for attribution.
Supply chain
OpenSSF Scorecard runs weekly and publishes a public score. Dependabot covers Cargo, npm, and GitHub Actions. CodeQL scans Rust and JavaScript on every push and PR.
Three cargo-fuzz targets (validate, validate_response, validate_artf) run for 30 seconds each on every CI push. The validator must not panic on arbitrary input.
bash
cargo +nightly fuzz run validate -- -max_total_time=60
License
Apache-2.0. See LICENSE and NOTICE.
Research
Sekowski, A. (2026). How Machine-Checkable Is OpenRTB? Classifying the Normative Content of the Protocol That Clears Real-Time Advertising. Preprint.
DOI: 10.13140/RG.2.2.27937.57448
Sekowski, A. (2026). Measuring OpenRTB Dialects in Client-Side Header Bidding. Preprint.
DOI: 10.13140/RG.2.2.26572.78720
Sekowski, A. (2026). Why CTV Ad Fraud Keeps Working: A Verifiability Analysis of the Connected TV Supply Chain. Preprint.
ResearchGate. Field inventory extracted from this catalog. Artifacts: github.com/aleksUIX/ctv-verification-gap.