@mcp_server.tool()
@meta_api_tool
async def create_campaign(
account_id: str,
name: str,
objective: str,
access_token: Optional[str] = None,
status: str = "PAUSED",
special_ad_categories: Optional[List[str]] = None,
daily_budget: Optional[int] = None,
lifetime_budget: Optional[int] = None,
buying_type: Optional[str] = None,
bid_strategy: str = "LOWEST_COST_WITHOUT_CAP",
bid_cap: Optional[int] = None,
spend_cap: Optional[int] = None,
campaign_budget_optimization: Optional[bool] = None,
ab_test_control_setups: Optional[List[Dict[str, Any]]] = None,
use_adset_level_budgets: bool = False
) -> str:
"""
Create a new Facebook or Instagram ad campaign in a Meta Ads account. Use this to start
a new campaign with an ODAX objective (OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_AWARENESS,
OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_APP_PROMOTION), pick CBO (campaign budget
optimization) or ABO (ad-set-level budgets), and set bid strategy, spend cap, and special
ad categories. This is the first step of the campaign group → ad set → ad hierarchy on
Meta. Returns the new campaign id. Also known as: create campaign, new campaign, make
campaign, campaign group, ABO campaign, CBO campaign.
Note: Campaigns do not support start_time for scheduling — set start_time on the ad set instead.
Args:
account_id: Meta Ads account ID (format: act_XXXXXXXXX)
name: Campaign name
objective: Campaign objective (ODAX, outcome-based). Must be one of:
OUTCOME_AWARENESS, OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT,
OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_APP_PROMOTION.
Note: Legacy objectives like BRAND_AWARENESS, LINK_CLICKS,
CONVERSIONS, APP_INSTALLS, etc. are not valid for new
campaigns and will cause a 400 error. Use the outcome-based
values above (e.g., BRAND_AWARENESS → OUTCOME_AWARENESS).
access_token: Meta API access token (optional - will use cached token if not provided)
status: Initial campaign status (default: PAUSED)
special_ad_categories: List of special ad categories if applicable
daily_budget: Daily budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False)
lifetime_budget: Lifetime budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False)
buying_type: Buying type (e.g., 'AUCTION')
bid_strategy: Bid strategy (default: LOWEST_COST_WITHOUT_CAP). Must be one of: 'LOWEST_COST_WITHOUT_CAP', 'LOWEST_COST_WITH_BID_CAP', 'COST_CAP', 'LOWEST_COST_WITH_MIN_ROAS'. WARNING: If you use LOWEST_COST_WITH_BID_CAP or COST_CAP, all child ad sets will require bid_amount to be set.
bid_cap: Bid cap in account currency (in cents) as a string
spend_cap: Spending limit for the campaign in account currency (in cents) as a string
campaign_budget_optimization: Whether to enable campaign budget optimization (only used if use_adset_level_budgets=False)
ab_test_control_setups: Settings for A/B testing (e.g., [{"name":"Creative A", "ad_format":"SINGLE_IMAGE"}])
use_adset_level_budgets: If True, budgets will be set at the ad set level instead of campaign level (default: False)
"""
# Check required parameters
if not account_id:
return json.dumps({"error": "No account ID provided"}, indent=2)
if not name:
return json.dumps({"error": "No campaign name provided"}, indent=2)
if not objective:
return json.dumps({"error": "No campaign objective provided"}, indent=2)
account_id = ensure_act_prefix(account_id)
# Track whether the user explicitly provided special_ad_categories
_user_provided_categories = special_ad_categories is not None
# Special_ad_categories is required by the API, set default if not provided
if special_ad_categories is None:
special_ad_categories = []
# Only warn if user omitted special_ad_categories entirely.
# If they explicitly passed [] they are saying none are needed.
compliance_warning = None
if objective == "OUTCOME_LEADS" and not special_ad_categories and not _user_provided_categories:
compliance_warning = (
"Warning: Campaign objective is OUTCOME_LEADS but no special_ad_categories were specified. "
"If this campaign is for a regulated industry (insurance, housing, employment, credit), "
"you must set special_ad_categories (e.g., FINANCIAL_PRODUCTS_SERVICES, HOUSING, EMPLOYMENT, CREDIT) "
"to comply with Meta advertising policies. Ads without the correct category may be rejected."
)
# For this example, we'll add a fixed daily budget if none is provided and we're not using ad set level budgets
if not daily_budget and not lifetime_budget and not use_adset_level_budgets:
daily_budget = "1000" # Default to $10 USD
endpoint = f"{account_id}/campaigns"
params = {
"name": name,
"objective": objective,
"status": status,
"special_ad_categories": json.dumps(special_ad_categories) # Properly format as JSON string
}
# Only set campaign-level budgets if we're not using ad set level budgets
if not use_adset_level_budgets:
# Convert budget values to strings if they aren't already
if daily_budget is not None:
params["daily_budget"] = str(daily_budget)
if lifetime_budget is not None:
params["lifetime_budget"] = str(lifetime_budget)
if campaign_budget_optimization is not None:
params["campaign_budget_optimization"] = "true" if campaign_budget_optimization else "false"
else:
# Meta API v24 requires is_adset_budget_sharing_enabled when not using campaign budget
params["is_adset_budget_sharing_enabled"] = "false"
# Add new parameters
if buying_type:
params["buying_type"] = buying_type
if bid_strategy and not use_adset_level_budgets:
params["bid_strategy"] = bid_strategy
if bid_cap is not None:
params["bid_cap"] = str(bid_cap)
if spend_cap is not None:
params["spend_cap"] = str(spend_cap)
if ab_test_control_setups:
params["ab_test_control_setups"] = json.dumps(ab_test_control_setups)
try:
data = await make_api_request(endpoint, access_token, params, method="POST")
# Add a note about budget strategy if using ad set level budgets
if use_adset_level_budgets:
data["budget_strategy"] = "ad_set_level"
data["note"] = "Campaign created with ad set level budgets. Set budgets when creating ad sets within this campaign."
if compliance_warning:
data["compliance_warning"] = compliance_warning
return json.dumps(data, indent=2)
except Exception as e:
error_msg = str(e)
return json.dumps({
"error": "Failed to create campaign",
"details": error_msg,
"params_sent": params
}, indent=2)