Skip to content

RFC: allowed_actions on products + buys for pre-flight mutation discovery #4480

Description

@nastassiafulconis

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:

  • 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.

1. closed media_buy_action enum

initial set:

extend_flight, shorten_flight, update_flight_dates,
increase_budget, decrease_budget, reallocate_budget,
pause, resume, cancel,
update_targeting, update_pacing, update_frequency_caps,
update_creatives, replace_creative, update_creative_assignments,
add_package, remove_package

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:

action fields on update_media_buy semantic note
extend_flight end_time (later) end-date push only
shorten_flight end_time (earlier) end-date pull only
update_flight_dates start_time and/or end_time (shift) start-shift is distinct from extend/shorten
increase_budget budget.total (raise)
decrease_budget budget.total (lower)
reallocate_budget packages[].budget total unchanged, redistributed
pause / resume / cancel status state transitions
update_targeting targeting_overlay
update_pacing pacing
update_frequency_caps frequency_caps
update_creatives creatives[] metadata
replace_creative creatives[].creative_id swap, assignments unchanged distinct AM workflow from assignment changes
update_creative_assignments package_assignments[]
add_package / remove_package packages[] add/remove line-item add/remove is the most common IO change

every 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[] and available_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 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

not a bare code. payload:

{
  "code": "ACTION_NOT_ALLOWED",
  "attempted_action": "decrease_budget",
  "reason": "wrong_status",
  "currently_available_actions": ["pause", "extend_flight", "update_creatives"]
}

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

  1. 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.
  2. interaction with RFC: orchestrator specialisms (role-tagged compatibility for buyer/orchestrator agents) #4425. if orchestrator specialisms land, does available_actions[] get filtered/narrowed per orchestrator role, or stay flat and let the buyer reconcile? leaning flat.
  3. 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?

Metadata

Metadata

Assignees

Labels

claude-triagedIssue has been triaged by the Claude Code triage routine. Remove to re-triage.needs-wg-reviewBlocked on a working-group decision — surface in WG meeting agendasrfcProtocol change — auto-adds to roadmap boardschemaJSON Schema source-of-truth: definitions, codegen artifacts, validation, hygiene

Type

No type

Fields

No fields configured for issues without a type.

Projects

Status
Shipped

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions