fix(pmax): make create_pmax_campaign work + add asset group linking and signals - #17
Merged
Merged
Conversation
Two gaps blocked building a complete Performance Max campaign through the server: 1. upload_image_asset created an asset but nothing linked it to an asset group, so images landed unattached in the asset library. A PMax asset group without MARKETING_IMAGE / SQUARE_MARKETING_IMAGE / LOGO is "Not eligible" and never serves. 2. add_audience_targeting writes a campaignCriterion userList, which the API rejects for a PERFORMANCE_MAX campaign. PMax audience signals and search themes live on asset_group_signal. link_asset_to_asset_group validates the field type client-side against the 11 types PMax accepts, since the API error for a bad one is opaque. add_asset_group_signal takes search themes, audience IDs, or both, and enforces the 80-char search theme limit before the request goes out. Both operation keys were already whitelisted in VALID_MUTATE_OPERATION_KEYS, so no client changes were needed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… script
create_pmax_campaign could not create any Performance Max campaign. Every
call built a valid-looking plan and then failed at confirm_and_apply with
"Request contains an invalid argument". Two causes:
BIDDING_STRATEGY_TYPE_INCOMPATIBLE_WITH_SHARED_BUDGET
campaign_budget.explicitly_shared defaults to true server-side and PMax
rejects a shared budget. draft_campaign got this fix in 5670d2d; the
PMax path was missed.
fieldError=REQUIRED on contains_eu_political_advertising
Required on every campaign create since the EU political ads
regulation. Omitting it fails the whole mutate.
Neither was diagnosable from the server's output, which collapses Google's
error tree to a single generic string. scripts/gads_mutate.py replays a
mutate payload and prints the full tree, with --check for validateOnly.
That is what surfaced both errors, and it is worth keeping.
Verified end to end against a live account: a PMax campaign, asset group,
26 text assets, 9 image assets and 9 asset group signals now apply cleanly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
While building a real Performance Max campaign through this server I hit three blockers. Two are bugs that make
create_pmax_campaignunusable; one is a missing capability without which a PMax campaign cannot serve at all.All of it is verified end to end against a live Google Ads account (v23), not just against the test suite.
Fixed:
create_pmax_campaigncould not create any campaignEvery call built a plausible-looking plan and then failed at
confirm_and_applywith the server's genericRequest contains an invalid argument. Two independent causes:BIDDING_STRATEGY_TYPE_INCOMPATIBLE_WITH_SHARED_BUDGETcampaign_budget.explicitly_shareddefaults to true server-side, and PMax rejects a shared budget.draft_campaigngot this fix in 5670d2d; the PMax path was missed.fieldError=REQUIREDoncontains_eu_political_advertisingAdded:
link_asset_to_asset_groupupload_image_assetonly ever created the asset — nothing attached it, so uploaded images sat unused in the account's asset library. A PMax asset group without aMARKETING_IMAGE,SQUARE_MARKETING_IMAGEandLOGOreportsNOT_ELIGIBLEand never serves.The field type is validated client-side against the 11 types PMax accepts, because the API error for a bad one is opaque.
Added:
add_asset_group_signalPMax does not take audiences as campaign criteria.
add_audience_targetingwrites acampaignCriterionuserList, which the API rejects for a PMax campaign — so the server had no working way to set the single most important PMax targeting input. Signals belong onasset_group_signal.Takes search themes, audience IDs, or both, and enforces the 80-character search theme limit before the request goes out.
Added:
scripts/gads_mutate.pyNone of the above was diagnosable from the server's output, which collapses Google's nested error tree to one generic string. This replays a mutate payload and prints the full tree, with
--checkforvalidateOnly. It is what surfaced all three problems.Testing
cargo clippy -D warningsandcargo fmt --checkclean.Two behaviours worth recording for anyone debugging PMax here, both found the hard way:
validateOnlydoes not resolve negative temp resource names when it counts an asset group's minimum assets, so a single atomic create reports falseNOT_ENOUGH_HEADLINE_ASSETerrors. Pre-creating the text assets and referencing real IDs validates clean.BUSINESS_NAME,LOGOandLANDSCAPE_LOGOmust be linked asCampaignAssets and are rejected as asset-group assets.🤖 Generated with Claude Code