feat(schema): redesign optimization_goal as optimization_goals array#1177
feat(schema): redesign optimization_goal as optimization_goals array#1177
Conversation
Replaces the singular optimization_goal object with optimization_goals (array) on packages, using a discriminated union on kind: - kind: "event" — optimize for advertiser-tracked conversion events (purchase, lead, app_install, etc.) with target cpa or roas; requires event_source_id - kind: "metric" — optimize for seller-native delivery metrics (clicks, views, completed_views) with target cpc, cpv, or cpcv; no event source required Both kinds support an optional priority field (1 = highest) for multi-goal packages where metric goals serve as proxy signals until event data accumulates. Adds custom_event_name to event goals for custom event types, and extends product.supported_optimization_strategies with target_cpc, target_cpv, target_cpcv. Updates all docs, examples, and the OpenAPI spec accordingly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nastassiafulconis
left a comment
There was a problem hiding this comment.
Missed file: static/schemas/source/protocol/get-adcp-capabilities-response.json line 317 still references the old singular field:
"...the buyer can choose from via optimization_goal.attribution_window on packages."
This schema wasn't touched by this PR but needs to be updated to reference the new array structure (e.g., optimization_goals[].attribution_window).
- Add "type": "object" to all oneOf variants and nested target variants - Add maximize_clicks/views/completed_views to supported_optimization_strategies for metric goals without a cost target - Add valid metric/target pairings table to docs (clicks→cpc, views→cpv, completed_views→cpcv) - Add "choosing a strategy" quick-reference table covering all 9 strategies - Add pairing constraint note to target description in schema Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A metric goal without a target already implicitly maximizes that metric within budget — no separate strategy declaration needed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace redundant metric-specific target kinds (cpc/cpv/cpcv/cpa/roas) with orthogonal target kinds that separate "what kind of target" from "what metric/event": - cost_per: cost per unit, works for both metric and event goals - rate: metric rate as proportion of impressions (CTR, VCR) - per_ad_spend: return ratio requiring value_field on event goals Event goals with per_ad_spend targets require a value_field property identifying which custom_data field carries the monetary value. This enables ROAS, profit-per-spend, and other value-based optimizations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nastassiafulconis
left a comment
There was a problem hiding this comment.
static/schemas/source/protocol/get-adcp-capabilities-response.json line 317 still references the old optimization_goal.attribution_window path
Event goals now use an event_sources array of source-type pair structs
instead of a single event_source_id/event_type. Each entry can specify
value_field and value_factor for per_ad_spend targets. The seller
deduplicates by event_id across sources — same conversion from web
analytics and an MMP counts once.
Rename rate → threshold_rate ("at least X per impression") and drop
maximum:1 constraint so the target works for both proportions (CTR,
viewability) and durations (attention seconds, time in view).
Add metrics: viewed_seconds, attention_seconds, attention_score.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Dedup value_field resolution: use array-position precedence (first matching entry in event_sources) instead of nondeterministic first-received - Note completed_views applies to audio completions too - Add "Pricing model vs. optimization goal" section distinguishing billing from delivery allocation - Add "Reach and frequency" section documenting that reach goals use CPP pricing and frequency_cap, not optimization_goals - Add "maximize clicks" example showing a metric goal without a target Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Kept optimization_goals (plural) with per_ad_spend target from this branch, adopted account (nested object) naming from main. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nastassiafulconis
left a comment
There was a problem hiding this comment.
did another pass on the latest commits — most of the earlier stuff is resolved, nice work on the cost_per/threshold_rate/per_ad_spend redesign and the event_sources array. few more things:
still-open stale refs (not in this diff so can't inline):
-
static/schemas/source/protocol/get-adcp-capabilities-response.jsonline 317 — still saysoptimization_goal.attribution_window(flagged this before, still there) -
static/schemas/source/index.jsonline 299 — schema registry description still says"target ROAS/CPA", should reflect the new target kinds -
docs/media-buy/task-reference/update_media_buy.mdxline 545 — the "What Can Be Updated" checklist still says"Optimization goal (event source, event type, target ROAS/CPA)"— not in a diff hunk so can't inline this either
| "type": "object", | ||
| "description": "Minimum per-impression rate for this metric. The metric defines the units: proportions for count metrics (e.g., 0.001 for 0.1% CTR, 0.70 for 70% viewability), seconds for duration metrics (e.g., 3.0 for 3s in view), or score for score metrics.", | ||
| "properties": { | ||
| "kind": { "type": "string", "const": "threshold_rate" }, |
There was a problem hiding this comment.
small naming thought — threshold_rate works great for proportions (CTR 0.001, viewability 0.70, VCR 0.85) but reads a bit odd for duration metrics like viewed_seconds: 3.0 or attention_seconds: 5.0 — those aren't really "rates" in the conventional sense.
not blocking on this, the docs explain it well. but have you considered just threshold or minimum_per_impression? would cascade to target_threshold / target_minimum_per_impression in supported_optimization_strategies too so it's not free. just flagging it.
There was a problem hiding this comment.
Good instinct — "rate" is a bit misleading for duration metrics. We considered threshold and minimum_per_impression during design. Brian specifically chose threshold_rate because plain threshold sounded like a count cap ("stop after 100 clicks") rather than a per-impression minimum. The value is always per-impression — clicks/impression, seconds/impression, score/impression — so "rate" is technically correct across all metric types, even if it reads more naturally for proportions.
The cascade cost is real too (target_threshold_rate in strategies, docs, all examples). Given the docs explain the units per metric and Brian signed off on the name, keeping it as-is.
| "type": "string", | ||
| "description": "Field on the event's custom_data that carries the monetary value. Required on at least one entry when target.kind is 'per_ad_spend'. The field must contain a numeric value in the buy currency. Common values: 'value', 'order_total', 'profit_margin'." | ||
| }, | ||
| "value_factor": { |
There was a problem hiding this comment.
value_factor has no constraints — a value of 0 would silently zero out all values from this source. could be intentional as a soft-disable, but could also be a nasty footgun if someone typos it. worth adding a not: { const: 0 } or at least a description note that 0 zeroes everything out?
There was a problem hiding this comment.
Good catch. Added a description note: "A value of 0 zeroes out this source's value contribution (the source still counts for event dedup)."
This documents the behavior without blocking it — there's a legitimate (if rare) use case for including a source purely for dedup/counting without contributing to the value calculation. A typo of 0 would show up as $0 ROAS in reporting, which is debuggable.
| "supported_optimization_strategies": { | ||
| "type": "array", | ||
| "description": "Optimization strategies this product supports when an optimization_goal is set on a package", | ||
| "description": "Optimization strategies this product supports when optimization_goals are set on a package. Target kinds: cost_per (cost per metric unit or conversion event), threshold_rate (minimum per-impression metric rate), per_ad_spend (return on ad spend — requires value_field on event sources). A goal without a target implicitly maximizes that metric or event within budget — no separate strategy declaration needed.", |
There was a problem hiding this comment.
the description says "a goal without a target implicitly maximizes within budget — no separate strategy needed" which is great. but what happens if a product has conversion_tracking but omits supported_optimization_strategies entirely? can buyers still set target-less goals (maximize within budget)? might be worth a one-liner clarifying the interaction.
There was a problem hiding this comment.
Great question. Yes — target-less goals (maximize within budget) work without any strategy declaration. Added a clarification to the description:
"When this field is omitted but conversion_tracking is present, buyers can still set target-less optimization goals (maximize within budget); they just cannot set specific cost_per, threshold_rate, or per_ad_spend targets."
…on clarity - Document that value_factor: 0 zeroes out value contribution but source still participates in event dedup - Clarify that omitting supported_optimization_strategies still allows target-less optimization goals (maximize within budget) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
nastassiafulconis
left a comment
There was a problem hiding this comment.
still missing updating some stale references but approving to not block before i log off!
- get-adcp-capabilities-response.json: attribution_windows description referenced old optimization_goal.attribution_window path - release-notes.mdx: referenced singular optimization_goal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Redesigns optimization goals as a composable, multi-goal system for media buy packages.
optimization_goal(singular) →optimization_goals(array) on packages, with optionalpriority(1 = highest) for multi-goal optimizationkind:"metric"(seller-native delivery metrics) and"event"(advertiser-tracked conversions)cost_per(CPC, CPA, etc.),threshold_rate(minimum per-impression value like CTR, viewability, attention),per_ad_spend(ROAS with value_field/value_factor)event_sourcesarray of structs with per-entryevent_source_id,event_type,value_field,value_factor— supports multi-source dedup byevent_id, refund netting, and unit conversionclicks,views,completed_views,viewed_seconds,attention_seconds,attention_scoresupported_optimization_strategieswithtarget_cost_per,target_threshold_rate,target_per_ad_spendTest plan
🤖 Generated with Claude Code