Facebook Ads API: Create a Campaign, Ad Set, and Ad
To create a campaign with the Facebook Ads API, make four Marketing API calls in order: POST /act_{id}/campaigns with an OUTCOME_ objective and special_ad_categories, then /adsets with budget and targeting, /adcreatives, and /ads. Create everything PAUSED and activate once it's checked. Budgets are in the currency's smallest unit, such as cents. Adeli builds the whole chain from a Page post in one paused call.
To create a campaign with the Facebook Ads API, you make four calls to Meta's Marketing API in order: a campaign (the objective), an ad set (budget, schedule, and targeting), an ad creative (what people see), and an ad that joins the creative to the ad set. Every campaign needs special_ad_categories, new campaigns must use an OUTCOME_ objective, and the safe default is to create everything with status=PAUSED.
This guide walks through each call with curl, for developers at platforms that launch ads on their customers' behalf. It's part of our Meta Ads API series.
What do you need before you start?
- An ad account ID in
act_<digits>form. Our guide to creating an ad account with the Meta API covers making one per customer. - A token with
ads_management, from a person or system user who can advertise on that ad account. - A Facebook Page the ads will run as, with
pages_manage_adsif you promote its posts. - A payment method on the ad account. Without one, everything below gets created but nothing delivers.
How is a Facebook ad campaign structured?
A Facebook ad campaign is a three-level tree: one campaign holds ad sets, and each ad set holds ads. Each level owns different settings, which is why it takes four calls.
| Object | Endpoint | Owns |
|---|---|---|
| Campaign | POST /act_{id}/campaigns |
Objective, special ad categories, optional campaign budget |
| Ad set | POST /act_{id}/adsets |
Budget (unless the campaign has one), schedule, targeting, optimization goal, billing event |
| Ad creative | POST /act_{id}/adcreatives |
The post, image, video, text, and call to action |
| Ad | POST /act_{id}/ads |
Links one creative to one ad set |
Step 1: Create the campaign
Send a POST to the ad account's campaigns edge with a name, an objective, special_ad_categories, and status=PAUSED. Meta's getting started guide says to set PAUSED "to keep the campaign inactive until you finish setup."
curl -X POST "https://graph.facebook.com/v26.0/act_$AD_ACCOUNT_ID/campaigns" \
-F "name=Fall launch" \
-F "objective=OUTCOME_TRAFFIC" \
-F "special_ad_categories=[]" \
-F "status=PAUSED" \
-F "access_token=$ACCESS_TOKEN"The response is {"id": "<CAMPAIGN_ID>"}. Only ACTIVE and PAUSED are valid at creation; DELETED and ARCHIVED are for updates.
Which objective should I use?
Use one of the six outcome objectives, because new campaigns can't use the original ones. Meta moved campaign creation to these simplified objectives, sometimes called ODAX (outcome-driven ad experiences), in Marketing API v17.0:
"Beginning with Marketing API v17.0 (to be released in Spring 2023), campaigns can only be created with simplified objectives." Jay Wei, Meta for Developers blog, February 13, 2023
That retired 15 original objectives for new campaigns, including LINK_CLICKS, CONVERSIONS, REACH, and LEAD_GENERATION. Older tutorials still use them, and the call fails. The current set, from Meta's campaigns reference:
| Objective | Use it when the customer wants |
|---|---|
OUTCOME_AWARENESS |
Reach and ad recall |
OUTCOME_TRAFFIC |
Visits to a site or app |
OUTCOME_ENGAGEMENT |
Post engagement, video views, messages |
OUTCOME_LEADS |
Leads from instant forms, calls, or messages |
OUTCOME_APP_PROMOTION |
App installs and in-app events |
OUTCOME_SALES |
Purchases and other conversions |
Special ad categories are the other required field. Meta's special ad category guide lists HOUSING, EMPLOYMENT, FINANCIAL_PRODUCTS_SERVICES, and ISSUES_ELECTIONS_POLITICS. Any category restricts targeting and needs special_ad_category_country. For a vertical SaaS serving real estate agents or recruiters, ask for this in your UI rather than defaulting to NONE.
Step 2: Create the ad set
Create the ad set with the campaign ID, a budget, a schedule, targeting, an optimization goal, and a billing event. Check the budget unit first when this call misbehaves.
curl -X POST "https://graph.facebook.com/v26.0/act_$AD_ACCOUNT_ID/adsets" \
-F "name=Fall launch - US" \
-F "campaign_id=$CAMPAIGN_ID" \
-F "daily_budget=2500" \
-F "bid_strategy=LOWEST_COST_WITHOUT_CAP" \
-F "billing_event=IMPRESSIONS" \
-F "optimization_goal=LINK_CLICKS" \
-F "start_time=2026-11-01T09:00:00-0500" \
-F "end_time=2026-11-08T09:00:00-0500" \
-F 'targeting={"geo_locations":{"countries":["US"]}}' \
-F "status=PAUSED" \
-F "access_token=$ACCESS_TOKEN"- Budgets are in the smallest currency unit. Meta's budgets page says amounts are at the account currency's "minimum denomination level, such as cents for USD", so
2500is $25.00 a day. - Meta can overspend a day. On days with better opportunities, Meta may spend up to 25% more than the daily budget.
- A lifetime budget needs an end time.
daily_budgetis allowed only for ad sets that run longer than 24 hours. - Set the bid strategy on purpose. A capped strategy such as
LOWEST_COST_WITH_BID_CAPneeds abid_amounton every ad set.
Should the budget sit on the campaign or the ad set?
Put the budget on the campaign when you want Meta to move money between ad sets, and on each ad set when your customer needs fixed amounts. Meta's reference says a budget can be set on the campaign or on its ad sets, not both.
| Campaign budget (Advantage campaign budget, formerly CBO) | Ad set budgets | |
|---|---|---|
| Where | daily_budget or lifetime_budget on the campaign |
daily_budget or lifetime_budget on each ad set |
| Who splits the money | Meta, in real time, toward the best results | You |
| Bid strategy | Set on the campaign | Set on each ad set |
| Best for | One goal, several audiences | Per-audience spend your customer has promised |
For a platform, ad set budgets are easier to explain in a UI ("$25 a day for this audience").
Step 3: Create the ad creative
Create a creative that points at an existing Page post with object_story_id, or describes a new one with object_story_spec. Promoting a post the customer already published is the simplest path:
curl -X POST "https://graph.facebook.com/v26.0/act_$AD_ACCOUNT_ID/adcreatives" \
-F "name=Fall launch creative" \
-F "object_story_id=${PAGE_ID}_${POST_ID}" \
-F "access_token=$ACCESS_TOKEN"The object_story_id is the Page ID and the post ID joined by an underscore. Meta's ad creatives reference notes that reusing a post already attached to a creative returns that creative's ID instead of making a new one.
Step 4: Create the ad
Create the ad with the ad set ID and the creative ID, still paused.
curl -X POST "https://graph.facebook.com/v26.0/act_$AD_ACCOUNT_ID/ads" \
-F "name=Fall launch ad" \
-F "adset_id=$AD_SET_ID" \
-F 'creative={"creative_id":"'"$CREATIVE_ID"'"}' \
-F "status=PAUSED" \
-F "access_token=$ACCESS_TOKEN"To launch, set status=ACTIVE on the campaign, the ad set, and the ad. Pausing any parent pauses its children: their effective_status reads CAMPAIGN_PAUSED or ADSET_PAUSED.
How do you know it worked?
Read the ad's effective_status. GET /{ad-id}?fields=status,effective_status tells you where it stands:
PENDING_REVIEW. Meta is reviewing the ad. Nothing delivers until it passes.DISAPPROVED. The ad broke an advertising policy; show the customer the reason.PENDING_BILLING_INFO. The ad account has no working payment method.ACTIVE. Delivering. Spend and results show up in the Facebook Ads Insights API.
If a later call fails, delete the objects the earlier calls created. Otherwise paused orphans pile up in the customer's Ads Manager.
Is there a faster way to do this?
Adeli creates the campaign, ad set, creative, and ad from one call, all paused, by promoting a post the customer's Page already published. Adeli holds the Meta app and the ads_management and pages_manage_ads grants, so you skip App Review. The endpoint is create campaign:
curl -X POST "https://app.tryadeli.com/api/v1/ads/campaigns?accountId=$ACCOUNT_ID" \
-H "Authorization: Bearer $ADELI_API_KEY" \
-H "Idempotency-Key: fall-launch-boost" \
-H "Content-Type: application/json" \
-d '{"adAccountId":"act_1234567890","pagePostId":"987654321098765","dailyBudget":25,"startDate":"2026-11-01","endDate":"2026-11-07","campaignName":"Fall launch"}'What the endpoint handles for you:
- Whole currency units. Adeli's
dailyBudgettakes25for 25.00. - Idempotent retries. The same
Idempotency-Keyreplays the original result rather than creating a second campaign. Adeli's docs call this the one call where a retry has a cost. - Rollback. If a step fails, Adeli deletes what it already created and returns the failing
step, plus anyorphanedObjectIdsit couldn't remove. - A preview. The response includes Meta's ad preview URL when Meta returns one, so your customer can see the ad before it spends.
Then update status sets the ad or ad set to ACTIVE, campaign tree returns campaigns, ad sets, and ads with spend and clicks at every level, and delete campaign removes a campaign with its children. The same API key publishes the posts you promote, and pricing is per customer profile.