Product

In AdCP, a product is a unit of sellable advertising inventory offered by a seller and returned by get_products, describing the properties it runs on, how it delivers, what it costs and what the seller can report on.

A product in AdCP is a unit of inventory a seller is willing to sell. It is returned by get_products and turned into packages inside a media buy. The schema at dist/schemas/3.1.13/core/product.json describes it in four words: “Represents available advertising inventory”. AdCP’s glossary adds the part that separates it from a rate card line: inventory “discovered through natural language queries”.

Commercially, a product is the seller’s own definition of what it sells: which publisher properties it runs on, whether delivery is guaranteed, which pricing models are available on it, and what the seller undertakes to report. A buyer does not browse a catalog to find it; it describes a campaign and the seller returns the products it considers a match.

How many fields a valid product needs

core/product.json carries 49 properties, more than any other object in the release, and its required array lists seven. An anyOf adds a format field, so eight are needed to validate, and every shipped fixture also carries channels.

FieldStatusWhat it holds
product_idrequiredUnique identifier for the product
namerequiredHuman-readable product name
descriptionrequiredDetailed description of the product and its inventory
publisher_propertiesrequired, minItems: 1The properties the product runs on
delivery_typerequiredguaranteed or non_guaranteed
pricing_optionsrequired, minItems: 1Available pricing models for this product
reporting_capabilitiesrequiredWhat the seller can report on
format_optionsanyOf, absent from requiredInline format declarations, the 3.1+ path
channelsoptional, in every fixtureThe channels the product is sold as
format_idsanyOf, legacyNamed-format references

An object carrying only the seven required fields fails validation, because an anyOf demands format_ids or format_options as well. The rule that governs the eighth is prose inside the format_ids description: “Products MUST carry format_ids, format_options, or BOTH; at least one is required.” Either field satisfies it. The two paths are not equal: format_ids is legacy, on a published deprecation calendar, and when both are present the schema says “prefer format_options”.

Eight is a count of top-level keys. reporting_capabilities is an object with six required sub-fields of its own, so thirteen named values are needed before a single publisher property or pricing option is filled in.

publisher_properties carries a normative instruction addressed to SDK implementers: “SDK implementers MUST enforce singular-only at runtime: each entry uses the singular publisher_domain form; the compact publisher_domains[] form is rejected on products”. It names four toolchains that often drop the restriction silently and emit an unrestricted type, so a generated client will accept the compact form and a conformant seller will reject it.

Where the schema and the documentation differ

Both declare seven required fields, and they name different sevens. The docs page lists 39 fields against the schema’s 49: reporting_capabilities is required in the schema and absent from the docs, format_options is an anyOf branch in the schema and required in the docs, collections and installments are documented under the names shows and episodes, and five documented fields do not exist in the release at all. The live docs site carries the same omissions.

Because additionalProperties is true, a product written from the documentation validates clean, its invented shows key riding along as an ignored extra, and nothing tells the author that the targeting they wrote was dropped.

Custom and expiring products

Three optional fields say a product need not be a fixed catalog row. is_custom is “Whether this is a custom product”. expires_at is an “Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.” brief_relevance carries an “Explanation of why this product matches the brief (only included when brief is provided)”. Nothing pushes a notice when the window closes, and the firmest statement of what happens next is the PRODUCT_EXPIRED error, correctable by re-running get_products.

None of the three appears in any example payload in the repository. The canonical product fixtures under static/examples/products/canonical/ are static catalog rows carrying the same small set of keys, and nytimes_homepage_takeover_custom.json is named custom without setting is_custom.

Targeting permissions

Three booleans default to false: signal_targeting_allowed, property_targeting_allowed, collection_targeting_allowed. Each bundles something a buyer might have assumed they could filter on. With signal_targeting_allowed false, signals “are bundled into the product terms”. With collection_targeting_allowed false, “the product is a bundle — buyers get all listed collections”. With property_targeting_allowed false, the product goes “all or nothing”.

Only signal_targeting_allowed has a schema conditional behind it: an allOf rejects any product that declares signal_targeting_options without signal_targeting_allowed: true. The other two read false whether or not the seller set them. What the surviving signal fields address is on audience vs signal.

Three protocols define an object called Product

A field-name crosswalk breaks at delivery_type, the first field anyone would map: the three enums hold two, three and seven values. AdCP keeps delivery and exclusivity in separate enums, the second holding none, category and exclusive. AAMP folds exclusivity into DeliveryType, and OpenDirect folds deal type in on top of that. AdCP’s FAQ names AAMP but publishes no mapping to it, saying “AAMP is still defining its normative surface.”

AdCP ProductAAMP primitives ProductOpenDirect Product
Properties / required49 / 7 listed, 8 to validate16 / 3: product_id, seller_organization_id, name31 / 7
delivery_typeguaranteed, non_guaranteedExclusive, Guaranteed, PMP7 values, including three PMP flavours and two OpenRTB deal types
Where price livespricing_options, required on the objectbase_price optional list price; negotiated rate on RateCardbaseprice required, “Base retail price (not rate card)”
Per-brief mintingis_custom, brief_relevance, expires_atnot modelledactivedate and retirementdate, catalog lifecycle rather than offer expiry
Artifact and statuspublished release 3.1.13unreleased library, v0.5.0MCP tool manifest, version 2.1.0