Skip to content

Commit 0d1737d

Browse files
nictukucursoragent
andcommitted
Fix bid_strategy validation and documentation for error 1815857
- Add validation to reject invalid 'LOWEST_COST' bid strategy (must use 'LOWEST_COST_WITHOUT_CAP' in Meta Ads API v22.0) - Add validation to require bid_amount when using LOWEST_COST_WITH_BID_CAP, COST_CAP, or LOWEST_COST_WITH_MIN_ROAS bid strategies - Provide helpful error messages with workarounds and examples - Update documentation for bid_strategy and bid_amount parameters in both create_adset and update_adset functions - Add comprehensive test suite for bid strategy validation This prevents Meta API error 1815857 "Bid Amount Required For The Bid Strategy Provided" by catching the issue before making the API call. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d0c0ebd commit 0d1737d

2 files changed

Lines changed: 573 additions & 4 deletions

File tree

‎meta_ads_mcp/core/adsets.py‎

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,29 @@ async def create_adset(
117117
lifetime_budget: Lifetime budget in account currency (in cents) as a string
118118
targeting: Targeting specifications including age, location, interests, etc.
119119
Use targeting_automation.advantage_audience=1 for automatic audience finding
120-
bid_amount: Bid amount in account currency (in cents)
121-
bid_strategy: Bid strategy (e.g., 'LOWEST_COST', 'LOWEST_COST_WITH_BID_CAP')
120+
bid_amount: Bid amount in account currency (in cents).
121+
REQUIRED for: LOWEST_COST_WITH_BID_CAP, COST_CAP, LOWEST_COST_WITH_MIN_ROAS.
122+
NOT REQUIRED for: LOWEST_COST_WITHOUT_CAP (recommended default).
123+
Note: May also be required if the parent campaign uses a bid strategy that requires it.
124+
bid_strategy: Bid strategy determines how Meta optimizes your bids.
125+
Valid values:
126+
- 'LOWEST_COST_WITHOUT_CAP' (recommended) - No bid_amount required, most flexible
127+
- 'LOWEST_COST_WITH_BID_CAP' - Sets max bid cap, REQUIRES bid_amount
128+
- 'COST_CAP' - Caps cost per result, REQUIRES bid_amount
129+
- 'LOWEST_COST_WITH_MIN_ROAS' - Sets minimum ROAS, REQUIRES bid_amount
130+
131+
IMPORTANT: 'LOWEST_COST' is NOT a valid value - use 'LOWEST_COST_WITHOUT_CAP' instead.
132+
133+
IMPORTANT: Campaign-level bid strategy constrains ad set choices. If the parent campaign
134+
uses LOWEST_COST_WITH_BID_CAP, all child ad sets MUST provide bid_amount regardless of
135+
their own bid_strategy setting.
136+
137+
Examples:
138+
1. Recommended (no bid amount needed):
139+
bid_strategy="LOWEST_COST_WITHOUT_CAP"
140+
141+
2. With bid cap (bid_amount required):
142+
bid_strategy="LOWEST_COST_WITH_BID_CAP", bid_amount=500
122143
start_time: Start time in ISO 8601 format (e.g., '2023-12-01T12:00:00-0800')
123144
end_time: End time in ISO 8601 format
124145
dsa_beneficiary: DSA beneficiary (person/organization benefiting from ads) for European compliance
@@ -209,6 +230,39 @@ async def create_adset(
209230
"targeting_automation": {"advantage_audience": 1}
210231
}
211232

233+
# Validate bid_strategy and bid_amount requirements
234+
if bid_strategy:
235+
# Check for invalid 'LOWEST_COST' value (common mistake)
236+
if bid_strategy == 'LOWEST_COST':
237+
return json.dumps({
238+
"error": "'LOWEST_COST' is not a valid bid_strategy value",
239+
"details": "The 'LOWEST_COST' bid strategy is not valid in Meta Ads API v22.0",
240+
"workaround": "Use 'LOWEST_COST_WITHOUT_CAP' instead (no bid_amount required)",
241+
"valid_values": [
242+
"LOWEST_COST_WITHOUT_CAP (recommended - no bid_amount required)",
243+
"LOWEST_COST_WITH_BID_CAP (requires bid_amount)",
244+
"COST_CAP (requires bid_amount)",
245+
"LOWEST_COST_WITH_MIN_ROAS (requires bid_amount)"
246+
],
247+
"example": '{"bid_strategy": "LOWEST_COST_WITHOUT_CAP"}'
248+
}, indent=2)
249+
250+
# Bid strategies that require bid_amount
251+
strategies_requiring_bid_amount = [
252+
'LOWEST_COST_WITH_BID_CAP',
253+
'COST_CAP',
254+
'LOWEST_COST_WITH_MIN_ROAS'
255+
]
256+
257+
if bid_strategy in strategies_requiring_bid_amount and bid_amount is None:
258+
return json.dumps({
259+
"error": f"bid_amount is required when using bid_strategy '{bid_strategy}'",
260+
"details": f"The '{bid_strategy}' bid strategy requires you to specify a bid amount in cents",
261+
"workaround": "Either provide the bid_amount parameter, or use bid_strategy='LOWEST_COST_WITHOUT_CAP' which does not require a bid amount",
262+
"example_with_bid_amount": f'{{"bid_strategy": "{bid_strategy}", "bid_amount": 500}}',
263+
"example_without_bid_amount": '{"bid_strategy": "LOWEST_COST_WITHOUT_CAP"}'
264+
}, indent=2)
265+
212266
endpoint = f"{account_id}/adsets"
213267

214268
params = {
@@ -305,8 +359,16 @@ async def update_adset(adset_id: str, frequency_control_specs: Optional[List[Dic
305359
adset_id: Meta Ads ad set ID
306360
frequency_control_specs: List of frequency control specifications
307361
(e.g. [{"event": "IMPRESSIONS", "interval_days": 7, "max_frequency": 3}])
308-
bid_strategy: Bid strategy (e.g., 'LOWEST_COST_WITH_BID_CAP')
309-
bid_amount: Bid amount in account currency (in cents for USD)
362+
bid_strategy: Bid strategy determines how Meta optimizes your bids.
363+
Valid values:
364+
- 'LOWEST_COST_WITHOUT_CAP' (recommended) - No bid_amount required
365+
- 'LOWEST_COST_WITH_BID_CAP' - REQUIRES bid_amount
366+
- 'COST_CAP' - REQUIRES bid_amount
367+
- 'LOWEST_COST_WITH_MIN_ROAS' - REQUIRES bid_amount
368+
369+
IMPORTANT: 'LOWEST_COST' is NOT a valid value - use 'LOWEST_COST_WITHOUT_CAP' instead.
370+
bid_amount: Bid amount in account currency (in cents for USD).
371+
REQUIRED when using LOWEST_COST_WITH_BID_CAP, COST_CAP, or LOWEST_COST_WITH_MIN_ROAS.
310372
status: Update ad set status (ACTIVE, PAUSED, etc.)
311373
targeting: Complete targeting specifications (will replace existing targeting)
312374
(e.g. {"targeting_automation":{"advantage_audience":1}, "geo_locations": {"countries": ["US"]}})
@@ -319,6 +381,39 @@ async def update_adset(adset_id: str, frequency_control_specs: Optional[List[Dic
319381
if not adset_id:
320382
return json.dumps({"error": "No ad set ID provided"}, indent=2)
321383

384+
# Validate bid_strategy if provided
385+
if bid_strategy is not None:
386+
# Check for invalid 'LOWEST_COST' value (common mistake)
387+
if bid_strategy == 'LOWEST_COST':
388+
return json.dumps({
389+
"error": "'LOWEST_COST' is not a valid bid_strategy value",
390+
"details": "The 'LOWEST_COST' bid strategy is not valid in Meta Ads API v22.0",
391+
"workaround": "Use 'LOWEST_COST_WITHOUT_CAP' instead (no bid_amount required)",
392+
"valid_values": [
393+
"LOWEST_COST_WITHOUT_CAP (recommended - no bid_amount required)",
394+
"LOWEST_COST_WITH_BID_CAP (requires bid_amount)",
395+
"COST_CAP (requires bid_amount)",
396+
"LOWEST_COST_WITH_MIN_ROAS (requires bid_amount)"
397+
],
398+
"example": '{"bid_strategy": "LOWEST_COST_WITHOUT_CAP"}'
399+
}, indent=2)
400+
401+
# Bid strategies that require bid_amount
402+
strategies_requiring_bid_amount = [
403+
'LOWEST_COST_WITH_BID_CAP',
404+
'COST_CAP',
405+
'LOWEST_COST_WITH_MIN_ROAS'
406+
]
407+
408+
if bid_strategy in strategies_requiring_bid_amount and bid_amount is None:
409+
return json.dumps({
410+
"error": f"bid_amount is required when using bid_strategy '{bid_strategy}'",
411+
"details": f"The '{bid_strategy}' bid strategy requires you to specify a bid amount in cents",
412+
"workaround": "Either provide the bid_amount parameter, or use bid_strategy='LOWEST_COST_WITHOUT_CAP' which does not require a bid amount",
413+
"example_with_bid_amount": f'{{"bid_strategy": "{bid_strategy}", "bid_amount": 500}}',
414+
"example_without_bid_amount": '{"bid_strategy": "LOWEST_COST_WITHOUT_CAP"}'
415+
}, indent=2)
416+
322417
params = {}
323418

324419
if frequency_control_specs is not None:

0 commit comments

Comments
 (0)