You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Today there's no protocol-level way for a buyer to know which mutations are valid on a given buy before calling update_media_buy. The buyer sends an update, the seller rejects it mid-flight, and the buyer learns the rule by failing. Examples:
can i extend the flight on this buy?
can i decrease budget on a guaranteed buy that's already live?
can i reallocate budget across packages once pacing has started?
agentic-api caught some of these with coarse checks (tool presence, feature flags, terminal status), but per-mutation rules fall through to runtime rejection because the spec doesn't declare them. context: scope3data/agentic-api#2060 and the discussion in #4425.
#4425 (orchestrator specialisms) solves an adjacent problem: telling sellers what kind of buyer i am. this RFC solves the other half: telling buyers what mutations a seller will actually accept on a given product, in a given state.
Proposal
frame it as actions, not fields. buyers think "can i extend, can i pause, can i reallocate," not "can i mutate end_time." actions wrap the per-field constraints (raisable vs lowerable, extendable vs shortenable) cleanly.
freeform strings kill interop. easier to extend the enum later than retract a vocabulary that's already in the wild.
2. normative action -> update_media_buy field mapping
each action must pin exactly which fields on update_media_buy it covers, otherwise sellers will disagree (does changing pacing_override count as update_pacing or update_targeting?) and the vocab collapses. straw-man table, WG to ratify:
available_actions[] on the buy (returned in get_media_buys / get_media_buy_delivery): authoritative. resolved per-buy against current status, negotiated terms, account tier, and any per-buy overrides. divergence from product-level is expected, not exceptional — negotiated terms and IO addenda live on the deal, not the SKU. same shape minus allowed_statuses (already filtered).
modes is a closed enum:
self_serve: seller honors the request synchronously
conditional_self_serve: seller auto-approves within tolerances, escalates outside them (covers PG on FreeWheel/Magnite/GAM, where small budget bumps clear but large ones queue)
requires_proposal: routes through the proposal lifecycle (create_proposal / finalize_proposal); ties to media_buy.supports_proposals
requires_approval: human-in-the-loop, async, no proposal artifact
binary self_serve vs requires_approval collapsed the PG middle and the proposal-lifecycle case into the same bucket; the four-way split keeps them distinct so SDKs know whether to expect a proposal task back, a pending-status poll, or an IO addendum.
4. ACTION_NOT_ALLOWED error with structured detail
reason is a closed enum: wrong_status | not_supported_on_product | mode_mismatch | not_supported_on_buy. lets buyer SDKs offer a recovery path without re-fetching.
Out of scope (v1)
constraint metadata on actions (max delta, bounds, "decrease budget but not below already-spent"). real, but defer to v2 so this proposal doesn't bog down. natural home: extend the requires predicate language being proposed in #4425 rather than ship a parallel grammar. v1 ships the action vocabulary + normative field mapping + modes + structured rejection; sellers reject mid-flight on constraint violations and the SDK surfaces the rejection to the buyer.
Open questions
action granularity edge cases. is reallocate_budget one action or split inter-package vs intra-package? does update_creatives need to split metadata-vs-asset changes? worth WG discussion before locking the enum.
declaration drift.supports_proposals and similar flags rot when sellers list them defensively. should allowed_actions carry a last_verified timestamp or honored-rate expectation, or is that a v2 concern alongside constraint metadata?
Background
Today there's no protocol-level way for a buyer to know which mutations are valid on a given buy before calling
update_media_buy. The buyer sends an update, the seller rejects it mid-flight, and the buyer learns the rule by failing. Examples:agentic-api caught some of these with coarse checks (tool presence, feature flags, terminal status), but per-mutation rules fall through to runtime rejection because the spec doesn't declare them. context: scope3data/agentic-api#2060 and the discussion in #4425.
#4425 (orchestrator specialisms) solves an adjacent problem: telling sellers what kind of buyer i am. this RFC solves the other half: telling buyers what mutations a seller will actually accept on a given product, in a given state.
Proposal
frame it as actions, not fields. buyers think "can i extend, can i pause, can i reallocate," not "can i mutate end_time." actions wrap the per-field constraints (raisable vs lowerable, extendable vs shortenable) cleanly.
1. closed
media_buy_actionenuminitial set:
freeform strings kill interop. easier to extend the enum later than retract a vocabulary that's already in the wild.
2. normative action ->
update_media_buyfield mappingeach action must pin exactly which fields on
update_media_buyit covers, otherwise sellers will disagree (does changingpacing_overridecount asupdate_pacingorupdate_targeting?) and the vocab collapses. straw-man table, WG to ratify:update_media_buyextend_flightend_time(later)shorten_flightend_time(earlier)update_flight_datesstart_timeand/orend_time(shift)increase_budgetbudget.total(raise)decrease_budgetbudget.total(lower)reallocate_budgetpackages[].budgetpause/resume/cancelstatusupdate_targetingtargeting_overlayupdate_pacingpacingupdate_frequency_capsfrequency_capsupdate_creativescreatives[]metadatareplace_creativecreatives[].creative_idswap, assignments unchangedupdate_creative_assignmentspackage_assignments[]add_package/remove_packagepackages[]add/removeevery entry MUST resolve to a disjoint field set (no action covers fields owned by another). buyer SDKs dispatch on the action -> field-set mapping.
3.
allowed_actions[]andavailable_actions[]allowed_actions[]on Product: advisory template. each entry:{ "action": "extend_flight", "modes": ["requires_proposal"], "allowed_statuses": ["live", "paused"] }available_actions[]on the buy (returned inget_media_buys/get_media_buy_delivery): authoritative. resolved per-buy against current status, negotiated terms, account tier, and any per-buy overrides. divergence from product-level is expected, not exceptional — negotiated terms and IO addenda live on the deal, not the SKU. same shape minusallowed_statuses(already filtered).modesis a closed enum:self_serve: seller honors the request synchronouslyconditional_self_serve: seller auto-approves within tolerances, escalates outside them (covers PG on FreeWheel/Magnite/GAM, where small budget bumps clear but large ones queue)requires_proposal: routes through the proposal lifecycle (create_proposal/finalize_proposal); ties tomedia_buy.supports_proposalsrequires_approval: human-in-the-loop, async, no proposal artifactbinary
self_servevsrequires_approvalcollapsed the PG middle and the proposal-lifecycle case into the same bucket; the four-way split keeps them distinct so SDKs know whether to expect a proposal task back, a pending-status poll, or an IO addendum.4.
ACTION_NOT_ALLOWEDerror with structured detailnot a bare code. payload:
{ "code": "ACTION_NOT_ALLOWED", "attempted_action": "decrease_budget", "reason": "wrong_status", "currently_available_actions": ["pause", "extend_flight", "update_creatives"] }reasonis a closed enum:wrong_status|not_supported_on_product|mode_mismatch|not_supported_on_buy. lets buyer SDKs offer a recovery path without re-fetching.Out of scope (v1)
constraint metadata on actions (max delta, bounds, "decrease budget but not below already-spent"). real, but defer to v2 so this proposal doesn't bog down. natural home: extend the
requirespredicate language being proposed in #4425 rather than ship a parallel grammar. v1 ships the action vocabulary + normative field mapping + modes + structured rejection; sellers reject mid-flight on constraint violations and the SDK surfaces the rejection to the buyer.Open questions
reallocate_budgetone action or split inter-package vs intra-package? doesupdate_creativesneed to split metadata-vs-asset changes? worth WG discussion before locking the enum.available_actions[]get filtered/narrowed per orchestrator role, or stay flat and let the buyer reconcile? leaning flat.supports_proposalsand similar flags rot when sellers list them defensively. shouldallowed_actionscarry alast_verifiedtimestamp or honored-rate expectation, or is that a v2 concern alongside constraint metadata?