DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

The ARTF Intent Cheat Sheet Every OpenRTB Bid Ops Engineer Needs

On 12 August 2026 IAB Tech Lab posted that ARTF v1.0 is FINAL and ready for production bidstreams. That is the week exit codes on container health checks start standing in for OpenRTB conformance. The Agentic Real Time Framework still ends on the same object buyers parse: a bid request or bid response inside an RTBRequest / RTBResponse envelope. Agents return patches, not replacements. Each patch line carries an intent (why), an operation (add, replace, remove), a semantic path (which auction entity), and a value oneof (ids, adjust_deal, adjust_bid, metrics, or content_data). If you wire agents without a sheet that maps intent to path and payload, you will green-light RPCs that leave deals on impressions that never carried them or shade bids that do not exist yet.

This table is for engineers who own the orchestrator: SSP curators, exchange sidecars, DSP shading hooks. It is not a substitute for the ARTF explained guide; it is the bookmark you open when a mutation JSON lands in logs.

Intent, lifecycle, path, and where it lands in OpenRTB

ARTF splits work by lifecycle. Request-stage intents run at LIFECYCLE_PUBLISHER_BID_REQUEST on the publisher ticket. Response-stage intents run at LIFECYCLE_DSP_BID_RESPONSE when a bid object exists. The host also sends applicable_intents: an ACL of what the agent is allowed to propose. An intent outside that list is policy, not syntax.

Semantic paths are not JSON pointers. They name business entities with stable segments such as /imp/imp-1/pmp/deals/deal-premium or /seatbid/seat-1/bid/bid-42. RTBlint resolves paths the same way the reference rules do: imp/{id}, imp/{id}/pmp/deals/{deal id} (or the shorter .../deals/{deal id} form), /user/data/segment, and /seatbid/{seat}/bid/{bid id}.

Intent Lifecycle Example semantic path Payload oneof OpenRTB field the host applies
ACTIVATE_SEGMENTS Publisher bid request /user/data/segment ids user.data[].segment
ACTIVATE_DEALS Publisher bid request /imp/imp-1/pmp/deals/deal-premium ids Deal id added under imp.pmp.deals[]
SUPPRESS_DEALS Publisher bid request /imp/imp-1 (remove op) ids Named deals removed from imp.pmp.deals[]
ADJUST_DEAL_FLOOR Publisher bid request /imp/imp-1/pmp/deals/deal-premium adjust_deal deals[].bidfloor
ADJUST_DEAL_MARGIN Publisher bid request /imp/imp-1/pmp/deals/deal-premium adjust_deal No OpenRTB 2.6 field (orchestrator only)
ADD_METRICS Publisher bid request /imp/imp-1 metrics imp.metric[]
BID_SHADE DSP bid response /seatbid/seat-1/bid/bid-42 adjust_bid seatbid[].bid[].price
ADD_CIDS Publisher bid request /user/data/segment ids or content_data data[].cids (OpenRTB 2.6-202505+)

One row worth reading twice: ADD_CIDS on an exchange pinned to an earlier 2.6 snapshot. The intent is legal ARTF. The member may not exist on the wire version the host runs, so apply either no-ops or produces a field downstream DSPs reject. Version negotiation sits outside the mutation list; the envelope does not carry a dated snapshot string for the agent to read.

Rows that pass the envelope and still break the auction

ADJUST_DEAL_MARGIN with a perfect protobuf JSON mutation. Floor and margin share the adjust_deal oneof on the wire, but margin is not part of the OpenRTB Deal object. The orchestrator is supposed to apply margin in commercial logic the bid object never sees. Static validation warns with artf.mutation.no_openrtb_target. Treating that warning as success means you billed for a mutation that never changed what buyers parse.

ACTIVATE_DEALS carrying adjust_bid. Copy-paste from a DSP shading template is the common source. Protobuf JSON parsing succeeds; gRPC returns OK; pass two fails because artf.mutation.payload_intent_mismatch binds ACTIVATE_DEALS to ids, not adjust_bid. The curator thought it activated a deal. The exchange got an illegal oneof.

BID_SHADE at publisher request lifecycle. Shading needs a bid. LIFECYCLE_PUBLISHER_BID_REQUEST has no bid_response payload to target. That is artf.mutation.intent_not_applicable plus missing target payload, not a floor tweak you can forward anyway.

ADD_METRICS with Metric.value as a string. ARTF can accept the mutation object; OpenRTB expects a numeric value. Framework v1.0 does not require a third pass after apply, so the failure shows up only if you revalidate the bid request the host will fan out. The longer walkthrough of apply-then-revalidate is in ARTF mutations and bid request validation.

Paths that look like JSON pointers. /imp/imp-1/pmp/deals/deal-premium/bidfloor is not a documented semantic reference. You get artf.mutation.path_unrecognized even when the deal id exists on the ticket. Deal floors use ADJUST_DEAL_FLOOR on the deal path, not a fake leaf segment.

How you catch it

I run three passes on every ARTF hop: envelope and carried OpenRTB as protobuf JSON, each mutation against lifecycle, applicable_intents, path resolution, and intent-specific oneof members, then apply accepted mutations and revalidate OpenRTB so field damage appears on the object buyers will see.

The ARTF simulator runs the same engine in a browser: paste an RTBRequest, paste mutations, watch apply-then-revalidate. CLI:

$ rtblint validate --type artf-request rtb-request.json
$ rtblint validate --type artf-response --request rtb-request.json rtb-response.json
$ rtblint validate --type artf-response --apply \
    --request rtb-request.json rtb-response.json
Enter fullscreen mode Exit fullscreen mode

MCP tools validate_artf_request and validate_artf_response (with apply when you need post-patch OpenRTB) let an agent implementation call the check on its own output. The RPC can still return OK; the wire contract inside the envelope does not move. rtblint (cargo install rtblint, npm install rtblint-core, MCP rtblint-mcp) is the deterministic check on that object and mutation set. It is independent of IAB Tech Lab and of AAO; the spec does not require it, and nothing in v1.0 replaces pass two before you forward.

Where to go next

Production ARTF means production mutation bugs. gRPC success is transport; OpenRTB validity is still what decides fill.

Top comments (0)