Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-brief-stage-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"adcontextprotocol": minor
---

Add optional `brief_stage` context to `get_products` requests so buyers can declare pre-buy stage, planning horizon, and response deadline without affecting pricing or product eligibility.
41 changes: 41 additions & 0 deletions docs/media-buy/task-reference/get_products.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ asyncio.run(discover_with_filters())
|-----------|------|----------|-------------|
| `buying_mode` | string | Yes | `"brief"`, `"wholesale"`, or `"refine"`. `"brief"`: publisher curates products from the brief. `"wholesale"`: raw product feed access for buyer-directed targeting, `brief` must not be provided. `"refine"`: iterate on products and proposals from a previous response using the `refine` array of change requests. v3 clients MUST include `buying_mode`. Sellers receiving requests from pre-v3 clients without `buying_mode` SHOULD default to `"brief"`. **Timing semantics:** `"wholesale"` is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a `"wholesale"` request through the async/Submitted arm. Partial completion is signalled via [`incomplete[]`](#incomplete-array), not a task handoff. `"brief"` and `"refine"` MAY complete synchronously OR MAY return a `Submitted` envelope when curation requires upstream-system queries or HITL review the seller cannot complete inside `time_budget`. Buyers needing predictable fast wholesale product feed access MUST use `"wholesale"`. |
| `brief` | string | Conditional | Natural language description of campaign requirements. Required when `buying_mode` is `"brief"`. Must not be provided when `buying_mode` is `"wholesale"` or `"refine"`. |
| `brief_stage` | BriefStage | No | Buyer-supplied pre-buy lifecycle context for the brief. See [Brief stage](#brief-stage) below. Absence means unknown, not `exploratory`, `planning`, or `active_sourcing`. Meaningful for `"brief"` and `"refine"` requests; sellers ignore it for `"wholesale"` feed reads. |
| `refine` | [Refine[]](#refine-array) | Conditional | Array of change requests for iterating on products and proposals. Required when `buying_mode` is `"refine"`. Must not be provided when `buying_mode` is `"brief"` or `"wholesale"`. See [Refine array](#refine-array) below. |
| `brand` | BrandRef | No | Brand reference (domain + optional brand_id). Resolved to full identity at execution time. |
| `account` | AccountRef | No | Account reference for account-specific pricing. Returns products with pricing from this account's rate card. |
Expand Down Expand Up @@ -272,6 +273,46 @@ If a product has both USD and EUR media pricing and the buyer sends `pricing_cur

*At least one of `min` or `max` must be specified.

### Brief stage

`brief_stage` lets a buyer tell the seller where the brief sits in the buyer's pre-buy lifecycle. It is advisory operational metadata, not a purchase commitment, insertion order, inventory hold, or authorization. Seller commitment to firm pricing or an inventory hold remains expressed through proposal `finalize`; buyer acceptance and execution remain expressed through `create_media_buy`.

Use `brief_stage` for curated discovery (`buying_mode: "brief"`) and proposal iteration (`buying_mode: "refine"`). Sellers ignore it for `buying_mode: "wholesale"` because wholesale requests are feed reads, not curated pre-buy interactions.

```json
{
"$schema": "/schemas/media-buy/get-products-request.json",
"buying_mode": "brief",
"brief": "Q4 launch campaign for a new electric bicycle line",
"brand": {
"domain": "acmecorp.com"
},
"brief_stage": {
"phase": "active_sourcing",
"planning_horizon": {
"start": "2026-10-01",
"end": "2026-12-31"
},
"response_deadline": "2026-08-15T21:00:00Z"
}
}
```

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `phase` | string | Yes | `"exploratory"`: early market scan or option gathering with no active proposal process. `"planning"`: concrete pre-buy planning with likely campaign timing or budget, but not yet active seller sourcing. `"active_sourcing"`: the buyer is actively requesting seller responses or proposals for a specific opportunity, but has not requested proposal finalization, an inventory hold, or a committed media buy. |
| `planning_horizon.start` | string | No* | Inclusive ISO 8601 date (`YYYY-MM-DD`) for the advisory planning or campaign window. |
| `planning_horizon.end` | string | No* | Inclusive ISO 8601 date (`YYYY-MM-DD`) for the advisory planning or campaign window. |
| `response_deadline` | string | No | ISO 8601 date-time by which the buyer would like a seller response or proposal update. |

*If `planning_horizon` is present, at least one of `start` or `end` is required. When both are present, `start` SHOULD be on or before `end`.

`planning_horizon` is not a hard availability filter. Use `filters.start_date` and `filters.end_date` when the buyer needs products available for specific flight dates.

If `brief_stage` is absent, the seller MUST treat the pre-buy stage as unknown. The seller MUST NOT infer `exploratory`, `planning`, or `active_sourcing` from absence alone, and absence MUST NOT reduce response quality, product availability, or pricing fairness.

Sellers MAY use `brief_stage` for queueing, SLA expectations, async routing, human-review prioritization, and internal CRM or pipeline labels. Sellers MUST NOT use `brief_stage` to alter price, rate card, product eligibility, inventory availability, or buyability. `response_deadline` does not require or authorize firm pricing, inventory availability confirmation, inventory hold, proposal finalization, or buyer/seller commitment.

### Refine array

The `refine` array is a list of change requests. Each entry declares a `scope` and what the buyer is asking for. At least one entry is required. The seller considers all entries together when composing the response, and replies to each via `refinement_applied`.
Expand Down
59 changes: 56 additions & 3 deletions static/schemas/source/media-buy/get-products-request.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/media-buy/get-products-request.json",
"title": "Get Products Request",
"description": "Request parameters for discovering or refining advertising products. buying_mode declares the buyer's intent: 'brief' for curated discovery, 'wholesale' for raw wholesale product feed access, or 'refine' to iterate on known products and proposals.",
"description": "Request parameters for discovering or refining advertising products. buying_mode declares the product discovery mode: 'brief' for curated discovery, 'wholesale' for raw wholesale product feed access, or 'refine' to iterate on known products and proposals.",
"type": "object",
"allOf": [
{
Expand Down Expand Up @@ -44,15 +44,15 @@
"wholesale",
"refine"
],
"description": "Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'."
"description": "Declares the product discovery mode for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'."
},
"brief": {
"type": "string",
"description": "Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'."
},
"refine": {
"type": "array",
"description": "Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.",
"description": "Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; a buyer asking to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose campaign requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon that requirement if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery beyond sequencing single-finalize calls and accepting the looser commit guarantee.",
"minItems": 1,
"items": {
"type": "object",
Expand Down Expand Up @@ -158,6 +158,59 @@
"$ref": "/schemas/core/catalog.json",
"description": "Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items."
},
"brief_stage": {
"type": "object",
"description": "Optional buyer-supplied context describing where this brief sits in the buyer's pre-buy lifecycle. Meaningful only for buying_mode: 'brief' and buying_mode: 'refine'; sellers MUST ignore it for buying_mode: 'wholesale' feed reads. Absence means the stage is unknown: sellers MUST NOT infer exploratory, planning, or active_sourcing from absence alone, and absence MUST NOT reduce response quality, product availability, or pricing fairness. Sellers MAY use the declared phase, planning_horizon, and response_deadline for queueing, SLA expectations, async routing, human-review prioritization, and internal CRM or pipeline labels. Sellers MUST NOT use brief_stage to alter price, rate card, product eligibility, inventory availability, or buyability. The field is advisory operational metadata, not a purchase commitment, insertion order, inventory hold, or authorization. Seller commitment to firm pricing or an inventory hold remains expressed through proposal finalization; buyer acceptance and execution remain expressed through create_media_buy.",
"properties": {
"phase": {
"type": "string",
"enum": [
"exploratory",
"planning",
"active_sourcing"
],
"description": "Buyer-declared stage for this brief. 'exploratory': early market scan or option gathering with no active proposal process. 'planning': concrete pre-buy planning with likely campaign timing or budget, but the buyer is not yet actively sourcing seller proposals. 'active_sourcing': the buyer is actively requesting seller responses or proposals for a specific opportunity, but has not requested proposal finalization, an inventory hold, or a committed media buy."
},
"planning_horizon": {
"type": "object",
"description": "Advisory planning or campaign window for the opportunity. This is context for seller routing and planning, not a hard availability filter; use filters.start_date and filters.end_date for availability constraints. If both start and end are present, start SHOULD be on or before end.",
"properties": {
"start": {
"type": "string",
"format": "date",
"description": "Inclusive start date for the advisory planning or campaign window."
},
"end": {
"type": "string",
"format": "date",
"description": "Inclusive end date for the advisory planning or campaign window."
}
},
"anyOf": [
{
"required": [
"start"
]
},
{
"required": [
"end"
]
}
],
"additionalProperties": false
},
"response_deadline": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 date-time by which the buyer would like a seller response or proposal update. Sellers MAY use it for routing and SLA handling only; it does not require or authorize firm pricing, inventory availability confirmation, inventory hold, proposal finalization, or buyer/seller commitment."
}
},
"required": [
"phase"
],
"additionalProperties": false
},
"account": {
"$ref": "/schemas/core/account-ref.json",
"description": "Account for product lookup. Returns products with pricing specific to this account's rate card."
Expand Down
Loading
Loading