OpenRTB protocol

OpenRTB supply chain object (schain)

The SupplyChain object lists every entity that touched a bid request on its way to the buyer, one node per reseller hop. In OpenRTB 2.6 it lives at source.schain (2.5 used source.ext.schain). Each node requires asi, sid, and hp, and complete: 1 asserts the chain reaches all the way back to the inventory owner.

What SupplyChain solves

A bid request rarely travels straight from publisher to buyer. It passes through header wrappers, resellers, and intermediary exchanges, and before schain existed a buyer had no machine-readable record of who those parties were or who gets paid. That opacity is what made counterfeit inventory and unauthorized reselling cheap to run. SupplyChain closes the loop with the other two IAB transparency files: ads.txt declares who may sell a publisher's inventory, sellers.json declares which accounts an advertising system pays, and schain records the actual path a specific request took. A buyer can walk the chain node by node and verify every hop against the other two.

Where the object lives

The SupplyChain object was specified standalone in 2019, so its location depends on the OpenRTB version of the payload:

  • OpenRTB 2.6: source.schain, first-class. 2.6 added SupplyChain and SupplyChainNode to the core object model (Sections 3.2.25 and 3.2.26).
  • OpenRTB 2.5: source.ext.schain, per the SupplyChain object spec.
  • OpenRTB 2.4 and earlier: BidRequest.ext.schain.

The object shape is identical in all three locations; only the path changed. See the 2.5 vs 2.6 guide for the other fields that moved out of ext in 2.6.

SupplyChain fields

All three attributes of the container object are required:

  • complete: integer. 1 if the chain contains all nodes back to the owner of the site, app, or other medium; 0 otherwise.
  • nodes: array of SupplyChainNode objects in order. The first node is the initial advertising system and seller ID (the inventory owner's system) in a complete chain, or the first known node in an incomplete one. The last node is the entity sending this bid request.
  • ver: string version of the SupplyChain spec, currently "1.0".

SupplyChainNode fields

FieldTypeScopeMeaning
asistringrequiredCanonical domain of the advertising system (SSP, exchange, header wrapper) that bidders connect to. Same value used to identify the seller in ads.txt.
sidstringrequiredSeller or reseller account ID within that advertising system. Must match the ID used in transactions, typically publisher.id. Limit 64 characters.
hpintegerrequired1 if this node is involved in the flow of payment (asi pays sid, who pays the previous node). Must always be 1 in SupplyChain 1.0.
ridstringoptionalThe OpenRTB request ID of the request as issued by this seller.
namestringoptionalLegal name of the company paid for this inventory. Omit if it already exists in the advertising system's sellers.json.
domainstringoptionalBusiness domain of the entity for this node. Omit if it already exists in the advertising system's sellers.json.

Every node also allows an ext object for advertising-system specific extensions.

A two-node example

A publisher sells through an SSP (ssp.example), which resells through an exchange (exchange.example) that sends the final bid request:

"source": {
  "schain": {
    "ver": "1.0",
    "complete": 1,
    "nodes": [
      { "asi": "ssp.example", "sid": "pub-4791", "hp": 1 },
      { "asi": "exchange.example", "sid": "reseller-102", "hp": 1, "rid": "d8f2b7a1" }
    ]
  }
}

Node order is the payment path: ssp.example pays account pub-4791 (the publisher), exchange.example pays reseller-102 (the SSP's account), and the buyer pays the exchange.

How buyers verify a chain

For each node, a buyer fetches https://<asi>/sellers.json and looks up the sid as a seller_id. The entry reveals the seller's name and domain and whether the account is a PUBLISHER, INTERMEDIARY, or BOTH. The first node's sid should also match a line in the publisher's ads.txt naming that asi. A chain whose nodes all resolve, in order, with the final node matching the exchange that delivered the request, is verifiable end to end. Chains that fail lookups get discounted or blocked by DSP supply-path optimization.

Common mistakes

  • Missing hp. The field is required and must be 1 for every node under SupplyChain 1.0. Nodes without it fail strict parsers.
  • complete: 1 with an unknown first hop. If you cannot name the advertising system that first handled the impression, the chain is incomplete by definition and must say complete: 0. Overclaiming completeness is worse than admitting a gap.
  • schain in both the old and new paths. Requests migrated to 2.6 sometimes carry source.schain and a stale source.ext.schain copy. When they drift apart, bidders cannot tell which one to trust; send one.
  • sid that does not match sellers.json. Using an internal account alias instead of the transacted seller ID breaks every downstream lookup.

Frequently asked questions

Where does schain go in OpenRTB 2.6?

BidRequest.source.schain, as a first-class object. OpenRTB 2.5 integrations carry the same object at source.ext.schain, and the SupplyChain spec assigned BidRequest.ext.schain for 2.4 and earlier. Send it in exactly one location for the version you declare.

What does complete=1 mean in a SupplyChain object?

complete=1 asserts the chain contains every node involved in the transaction back to the owner of the site, app, or other medium, and that the first node in the nodes array is that initial advertising system and seller ID. If any upstream hop is unknown, complete must be 0.

Which SupplyChainNode fields are required?

asi (the advertising system's domain), sid (the seller account ID inside that system), and hp (payment involvement, always 1 in SupplyChain 1.0). rid, name, and domain are optional, and name and domain should be omitted when the data already exists in sellers.json.

Validate it

The bid request tester checks source.schain structurally against the 2.6 spec: a chain left in the 2.5-era source.ext.schain path is flagged as openrtb.field.moved, and misspelled or unknown node fields surface as openrtb.field.undefined with the exact JSON path. The same rules run in the CLI; the full list is in the rule catalog.