From 0915ad078325a5fd2f48703d9da6b0418cb1af3d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Wed, 17 Jun 2026 23:56:10 +0200 Subject: [PATCH] feat(media-buy): add DOOH geo discovery fields --- .changeset/5538-dooh-geo-product-discovery.md | 5 + .../product-discovery/media-products.mdx | 53 ++++++++ docs/reference/media-channel-taxonomy.mdx | 2 + static/schemas/source/core/dooh-address.json | 36 +++++ .../source/core/dooh-inventory-summary.json | 19 +++ .../core/dooh-placement-attributes.json | 15 +++ .../source/core/dooh-placement-location.json | 31 +++++ .../schemas/source/core/dooh-venue-count.json | 124 ++++++++++++++++++ static/schemas/source/core/placement.json | 4 + static/schemas/source/core/product.json | 4 + 10 files changed, 293 insertions(+) create mode 100644 .changeset/5538-dooh-geo-product-discovery.md create mode 100644 static/schemas/source/core/dooh-address.json create mode 100644 static/schemas/source/core/dooh-inventory-summary.json create mode 100644 static/schemas/source/core/dooh-placement-attributes.json create mode 100644 static/schemas/source/core/dooh-placement-location.json create mode 100644 static/schemas/source/core/dooh-venue-count.json diff --git a/.changeset/5538-dooh-geo-product-discovery.md b/.changeset/5538-dooh-geo-product-discovery.md new file mode 100644 index 0000000000..dabd7e924a --- /dev/null +++ b/.changeset/5538-dooh-geo-product-discovery.md @@ -0,0 +1,5 @@ +--- +"adcontextprotocol": minor +--- + +Add optional DOOH product-discovery geography fields: placement-level `dooh_placement_attributes.location` and product-level `dooh_inventory_summary.venue_counts`. diff --git a/docs/media-buy/product-discovery/media-products.mdx b/docs/media-buy/product-discovery/media-products.mdx index 0c2b89ed74..e049a82674 100644 --- a/docs/media-buy/product-discovery/media-products.mdx +++ b/docs/media-buy/product-discovery/media-products.mdx @@ -202,6 +202,7 @@ Products can optionally declare specific public ad placements within their inven - **`audio_distribution_types`** - Declared audio distribution types for radio, streaming-audio, podcast, gaming, and other audio placements. Concrete placements usually declare one value; aggregate placements may declare multiple. - **`sponsored_placement_types`** - Declared sponsored-placement types for catalog-driven retail-media placements. Concrete placements usually declare one value; aggregate placements may declare multiple. - **`social_placement_surfaces`** - Declared social-placement surfaces for social placements. Concrete placements usually declare one value; aggregate placements may declare multiple. +- **`dooh_placement_attributes.location`** - Optional concrete location metadata for disclosed DOOH screens, frames, or venues. Use `lat` / `lon` and optional address fields; omit for aggregate packages or inventory where per-placement location is not disclosed. - **Publisher reference rule** - Publisher-referenced product placements resolve to `{publisher_domain, placement_id}` in the publisher's `adagents.json` - **Private inventory rule** - Seller-private delivery objects, ad-server mappings, and source/origin details must stay out of `get_products` - **Creative assignment** - Different creatives can be assigned to targetable placements @@ -277,6 +278,58 @@ The field is an array because a sellable product can aggregate multiple surfaces This is a discovery signal, not a verification claim. Buyers can filter for products that can satisfy a requested surface with `get_products.filters.social_placement_surfaces`, but sellers should not return mixed, non-targetable bundles unless they can constrain delivery to the requested surface during planning or purchase. +#### DOOH location and inventory summary + +DOOH products can expose planning-time geography without moving location data into `property.json`. Use placement-level `dooh_placement_attributes.location` when the seller is disclosing a concrete screen, frame, or venue. Use product-level `dooh_inventory_summary.venue_counts[]` when the product is an aggregate network, regional package, or other bundle where per-screen disclosure is unavailable, impractical, or not useful at discovery time. + +Location is descriptive metadata, not a stable inventory identifier. Buyers should use `placement_id` for protocol references and treat `location` as planning and verification context. + +This excerpt shows only the DOOH-specific discovery fields; a complete Product object still includes required product fields such as `description`, `publisher_properties`, `delivery_type`, `pricing_options`, `reporting_capabilities`, and `format_options` or `format_ids`. + +```json +{ + "product_id": "pinnacle_transit_downtown", + "name": "Pinnacle Transit Downtown Screens", + "channels": ["dooh"], + "placements": [ + { + "kind": "seller_inline", + "placement_id": "central_station_concourse_01", + "name": "Central Station Concourse Screen 1", + "mode": "targetable", + "dooh_placement_attributes": { + "location": { + "lat": 40.7527, + "lon": -73.9772, + "address": { + "line1": "100 Transit Plaza", + "city": "New York", + "region": "NY", + "postal_code": "10017", + "country": "US" + } + } + } + } + ], + "dooh_inventory_summary": { + "venue_counts": [ + { + "geo_level": "metro", + "system": "nielsen_dma", + "geo_code": "501", + "geo_name": "New York", + "venue_type": "transit.station", + "count_unit": "venues", + "count": 87 + } + ] + } +} +``` + +Use `lat` and `lon` together; do not emit `lng`. `venue_counts[]` uses the same `geo_level` / `system` / `geo_code` vocabulary as forecast geo dimensions, so buyer agents can compare discovery summaries with availability and forecast rows. Each aggregate row declares `count_unit` so buyers can distinguish venues, screens, frames, and faces. `coverage_polygon` is intentionally absent from this surface; if polygon coverage is standardized later, it should reuse the existing catchment geometry pattern rather than defining a new DOOH-only polygon object. + #### Format precedence with placements Product-level `format_ids` and `format_options` define the creative formats accepted by the product as a whole. Placement-level `format_ids` or `format_options`, whether returned inline on the product placement or inherited from a public publisher placement declaration, only narrow that product-wide set for the specific placement. diff --git a/docs/reference/media-channel-taxonomy.mdx b/docs/reference/media-channel-taxonomy.mdx index 0075e7e0ab..7c904fab4b 100644 --- a/docs/reference/media-channel-taxonomy.mdx +++ b/docs/reference/media-channel-taxonomy.mdx @@ -332,6 +332,8 @@ Digital out-of-home advertising on electronic screens in public spaces. **Typical Formats**: display, video +For product discovery, DOOH geography belongs on product and placement metadata rather than the property governance layer. Sellers can expose concrete disclosed screen or venue coordinates through `placements[].dooh_placement_attributes.location`, and aggregate network coverage through `dooh_inventory_summary.venue_counts[]`. Use `lat` / `lon` together for coordinates and the shared `geo_level` / `system` / `geo_code` vocabulary for aggregate rows. Aggregate rows declare `count_unit` so buyers know whether `count` represents venues, screens, frames, or faces. + #### `ooh` Classic out-of-home advertising on physical (non-digital) surfaces. diff --git a/static/schemas/source/core/dooh-address.json b/static/schemas/source/core/dooh-address.json new file mode 100644 index 0000000000..662a92c1a6 --- /dev/null +++ b/static/schemas/source/core/dooh-address.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/dooh-address.json", + "title": "DOOH Address", + "description": "Optional postal address details for a DOOH placement location. Use only when the seller can disclose a concrete venue or screen address for planning or verification.", + "type": "object", + "properties": { + "line1": { + "type": "string", + "description": "Street address or venue address line." + }, + "line2": { + "type": "string", + "description": "Optional secondary address line, such as suite, terminal, concourse, or level." + }, + "city": { + "type": "string", + "description": "City or locality." + }, + "region": { + "type": "string", + "description": "Region, state, province, or ISO 3166-2 subdivision code." + }, + "postal_code": { + "type": "string", + "description": "Postal or ZIP code." + }, + "country": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "ISO 3166-1 alpha-2 country code." + } + }, + "minProperties": 1, + "additionalProperties": true +} diff --git a/static/schemas/source/core/dooh-inventory-summary.json b/static/schemas/source/core/dooh-inventory-summary.json new file mode 100644 index 0000000000..2ac13dc0ee --- /dev/null +++ b/static/schemas/source/core/dooh-inventory-summary.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/dooh-inventory-summary.json", + "title": "DOOH Inventory Summary", + "description": "Optional product-level aggregate geography for DOOH products. Use this for network, regional, or package products where per-screen disclosure is unavailable, impractical, or not useful at discovery time.", + "type": "object", + "properties": { + "venue_counts": { + "type": "array", + "description": "Aggregate counts by geography and optional venue type. Rows use the same geo_level/system/geo_code vocabulary as forecast geo dimensions.", + "items": { + "$ref": "/schemas/core/dooh-venue-count.json" + }, + "minItems": 1 + } + }, + "minProperties": 1, + "additionalProperties": true +} diff --git a/static/schemas/source/core/dooh-placement-attributes.json b/static/schemas/source/core/dooh-placement-attributes.json new file mode 100644 index 0000000000..aefa0016cf --- /dev/null +++ b/static/schemas/source/core/dooh-placement-attributes.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/dooh-placement-attributes.json", + "title": "DOOH Placement Attributes", + "description": "Optional DOOH-specific metadata on a product placement. This object is scoped to buyer-facing discovery facts; do not use it for seller-private delivery mappings.", + "type": "object", + "properties": { + "location": { + "$ref": "/schemas/core/dooh-placement-location.json", + "description": "Concrete location metadata for a disclosed screen, frame, or venue. Omit for aggregate packages or inventory where the seller cannot disclose per-placement location." + } + }, + "minProperties": 1, + "additionalProperties": true +} diff --git a/static/schemas/source/core/dooh-placement-location.json b/static/schemas/source/core/dooh-placement-location.json new file mode 100644 index 0000000000..159d1d68b2 --- /dev/null +++ b/static/schemas/source/core/dooh-placement-location.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/dooh-placement-location.json", + "title": "DOOH Placement Location", + "description": "Optional concrete location metadata for a DOOH product placement. Location describes where a disclosed screen, frame, or venue is; it is not a stable inventory identifier.", + "type": "object", + "properties": { + "lat": { + "type": "number", + "minimum": -90, + "maximum": 90, + "description": "Latitude in decimal degrees." + }, + "lon": { + "type": "number", + "minimum": -180, + "maximum": 180, + "description": "Longitude in decimal degrees. Use `lon`, not `lng`, for consistency with OpenRTB and existing AdCP geo fields." + }, + "address": { + "$ref": "/schemas/core/dooh-address.json", + "description": "Optional postal address for the disclosed placement location." + } + }, + "dependencies": { + "lat": ["lon"], + "lon": ["lat"] + }, + "minProperties": 1, + "additionalProperties": true +} diff --git a/static/schemas/source/core/dooh-venue-count.json b/static/schemas/source/core/dooh-venue-count.json new file mode 100644 index 0000000000..6a7a50082c --- /dev/null +++ b/static/schemas/source/core/dooh-venue-count.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/core/dooh-venue-count.json", + "title": "DOOH Inventory Count", + "description": "Aggregate count of DOOH inventory units within a geographic bucket and optional venue type. Uses the same geo_level/system/geo_code vocabulary as forecast geo dimensions.", + "type": "object", + "properties": { + "geo_level": { + "$ref": "/schemas/enums/geo-level.json", + "description": "Geographic level for this aggregate." + }, + "system": { + "type": "string", + "description": "Classification system for metro or postal_area levels. Required when geo_level is 'metro' or 'postal_area'. Metro rows use metro-system enum values such as 'nielsen_dma'; native postal rows use country-local postal-system enum values such as 'zip' with country 'US'; deprecated legacy postal rows may use legacy-postal-system enum values such as 'us_zip'. Omit for country and region rows." + }, + "country": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "ISO 3166-1 alpha-2 country code. Required for native postal_area rows and omitted for legacy postal rows, metro rows, country rows, and region rows." + }, + "geo_code": { + "type": "string", + "description": "Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." + }, + "geo_name": { + "type": "string", + "description": "Human-readable geographic name, such as a country, region, metro, or postal area name." + }, + "venue_type": { + "type": "string", + "description": "Optional DOOH venue classification, preferably from OpenOOH or another seller-declared venue taxonomy." + }, + "count_unit": { + "type": "string", + "enum": ["venues", "screens", "frames", "faces"], + "description": "The inventory unit represented by count. Use 'venues' for physical locations, 'screens' for addressable digital displays, 'frames' for seller/registry frame records, and 'faces' for individual out-of-home display faces." + }, + "count": { + "type": "integer", + "minimum": 0, + "description": "Number of DOOH inventory units represented by this aggregate row, expressed in the unit declared by count_unit." + } + }, + "required": ["geo_level", "geo_code", "count_unit", "count"], + "allOf": [ + { + "if": { + "properties": { "geo_level": { "const": "country" } }, + "required": ["geo_level"] + }, + "then": { + "properties": { + "geo_code": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "ISO 3166-1 alpha-2 country code." + } + }, + "not": { + "anyOf": [ + { "required": ["system"] }, + { "required": ["country"] } + ] + } + } + }, + { + "if": { + "properties": { "geo_level": { "const": "region" } }, + "required": ["geo_level"] + }, + "then": { + "properties": { + "geo_code": { + "type": "string", + "pattern": "^[A-Z]{2}-[A-Z0-9]{1,3}$", + "description": "ISO 3166-2 subdivision code." + } + }, + "not": { + "anyOf": [ + { "required": ["system"] }, + { "required": ["country"] } + ] + } + } + }, + { + "if": { + "properties": { "geo_level": { "const": "metro" } }, + "required": ["geo_level"] + }, + "then": { + "properties": { + "system": { "$ref": "/schemas/enums/metro-system.json" } + }, + "required": ["system"], + "not": { "required": ["country"] } + } + }, + { + "if": { + "properties": { "geo_level": { "const": "postal_area" } }, + "required": ["geo_level"] + }, + "then": { + "anyOf": [ + { + "$ref": "/schemas/core/postal-country-system.json", + "required": ["country", "system"] + }, + { + "properties": { + "system": { "$ref": "/schemas/enums/legacy-postal-system.json" } + }, + "required": ["system"], + "not": { "required": ["country"] } + } + ] + } + } + ], + "additionalProperties": true +} diff --git a/static/schemas/source/core/placement.json b/static/schemas/source/core/placement.json index eb1627aeee..d4f176f7d9 100644 --- a/static/schemas/source/core/placement.json +++ b/static/schemas/source/core/placement.json @@ -92,6 +92,10 @@ }, "uniqueItems": true, "minItems": 1 + }, + "dooh_placement_attributes": { + "$ref": "/schemas/core/dooh-placement-attributes.json", + "description": "Optional DOOH-specific discovery metadata for this placement. In 3.1 this object carries concrete location metadata for disclosed screens, frames, or venues. Omit it for aggregate packages or inventory where per-placement location is not disclosed." } }, "required": [ diff --git a/static/schemas/source/core/product.json b/static/schemas/source/core/product.json index efc3b8a5ff..b36b109ca9 100644 --- a/static/schemas/source/core/product.json +++ b/static/schemas/source/core/product.json @@ -96,6 +96,10 @@ "uniqueItems": true, "minItems": 1 }, + "dooh_inventory_summary": { + "$ref": "/schemas/core/dooh-inventory-summary.json", + "description": "Optional product-level aggregate geography for DOOH products. Use this for network, regional, or package products where per-screen disclosure is unavailable, impractical, or not useful at discovery time." + }, "delivery_type": { "$ref": "/schemas/enums/delivery-type.json" },