Meta Ads
Boost Facebook and Instagram posts and manage Meta campaigns, ad sets, ads, audiences and insights through the Postgale REST API and MCP server.
Postgale lets your backend or AI agent run Facebook and Instagram ads through Meta's Marketing API: list ad accounts, boost a
published post, build campaigns, ad sets (targeting, budget, schedule), creatives and ads, manage customer-list custom audiences
and read insights. Everything is on the REST API (paths relative to https://staging.postgale.com/api/v1) and the
MCP server (tools ads_*). Calls act through a connected Facebook Page account
(accountId) whose user granted ads_management / ads_read.
Safety: nothing spends by accident
- Every call that can create spend (creating a campaign, ad set or ad, boosting, setting status
ACTIVE, changing budget, bid, schedule or targeting) needs"confirm": true. Without it the answer is400 confirmation_requiredand nothing is created. - Created objects are
PAUSEDunless you send"status": "ACTIVE"together withconfirm. Pausing (PATCH … {"status":"PAUSED"}) never needs confirm. - Agents should show the plan (ad account, budget, dates, targeting) to a human and only then send
confirm. - Budgets and bids are integers in the ad account currency's minor units (cents):
2000= 20.00 USD. - When approval applies to the key, organization or account, ads writes answer
403 approval_required. Writes are audited. - Instances without
PROVIDER_LIVE_ADS=1answer from a deterministic mock ("mock": truein every response) and never call Meta.
Ad accounts
curl https://staging.postgale.com/api/v1/ads/accounts?accountId=acc_fb_page \
-H 'Authorization: Bearer sk_…'
# {"adAccounts":[{"id":"act_1234567890","name":"Coffee Co. Ads","currency":"USD","account_status":1,…}],"mock":false}Boost a post
Turns a published Facebook Page post or Instagram post into an ad that keeps its likes and comments: Postgale creates the campaign,
ad set, creative and ad. Pass postId (a Postgale post with a published Facebook or Instagram target) or
platformPostId + platform. Instagram needs instagramUserId, the Instagram business account linked
to the Page. goal: engagement (default), traffic, awareness, video_views, lead_generation, conversions, app_promotion. A
lifetime budget needs schedule.endDate; linkUrl goes with callToAction.
curl -X POST https://staging.postgale.com/api/v1/ads/boost \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","adAccountId":"act_1234567890","postId":"cmpost123","goal":"traffic","budget":{"amount":2000,"type":"daily"},"schedule":{"startDate":"2026-11-01T09:00:00Z","endDate":"2026-11-08T09:00:00Z"},"targeting":{"countries":["US","CA"],"ageMin":25,"ageMax":45,"interests":[{"id":"6003139266461","name":"Coffee"}]},"linkUrl":"https://shop.example/beans","callToAction":"SHOP_NOW","confirm":true}'{
"boost": {
"platform": "facebook",
"platformPostId": "811889972008357_1029384756",
"status": "PAUSED",
"campaignId": "120250000000000002",
"adSetId": "120250000000000003",
"creativeId": "120250000000000004",
"adId": "120260000000000001"
},
"mock": false
}Campaigns, ad sets, creatives and ads
The full tree, step by step (each create answers 201 with the new object):
curl -X POST https://staging.postgale.com/api/v1/ads/campaigns \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","adAccountId":"act_1234567890","name":"Spring beans","goal":"traffic","confirm":true}'curl -X POST https://staging.postgale.com/api/v1/ads/adsets \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","adAccountId":"act_1234567890","campaignId":"120250000000000002","name":"US 25-45 coffee","dailyBudget":1500,"startTime":"2026-11-01T09:00:00Z","endTime":"2026-11-15T09:00:00Z","optimizationGoal":"LINK_CLICKS","targeting":{"countries":["US"],"ageMin":25,"ageMax":45,"publisherPlatforms":["facebook","instagram"]},"confirm":true}'curl -X POST https://staging.postgale.com/api/v1/ads/creatives \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","adAccountId":"act_1234567890","name":"Beans link ad","link":"https://shop.example/beans","message":"Fresh roast, shipped today","headline":"Spring beans","imageUrl":"https://media.example/beans.jpg","callToAction":"SHOP_NOW"}'curl -X POST https://staging.postgale.com/api/v1/ads/ads \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","adAccountId":"act_1234567890","name":"Beans ad","adSetId":"120250000000000003","creativeId":"120250000000000004","confirm":true}'Read with GET /ads/campaigns, /ads/adsets?parentId=, /ads/ads?parentId=, /ads/creatives
(?accountId&adAccountId&statuses&limit&after) and GET /ads/campaigns/{id}?accountId=. Update or pause with
PATCH /ads/campaigns|adsets|ads/{id}:
curl -X PATCH https://staging.postgale.com/api/v1/ads/campaigns/120250000000000002 \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","status":"PAUSED"}'curl -X PATCH https://staging.postgale.com/api/v1/ads/adsets/120250000000000003 \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","dailyBudget":2500,"targeting":{"countries":["US","CA"]},"confirm":true}'Targeting
targeting takes countries, regions, cities (with radius), zips,
ageMin/ageMax (13–65), genders (1 male, 2 female), interests,
customAudiences/excludedCustomAudiences and publisherPlatforms; or raw, a Meta targeting
spec used as is. Find interest and location keys with GET /ads/targeting/search?accountId&q=coffee&type=adinterest.
Custom audiences
curl -X POST https://staging.postgale.com/api/v1/ads/audiences \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","adAccountId":"act_1234567890","name":"Newsletter subscribers"}'curl -X POST https://staging.postgale.com/api/v1/ads/audiences/23850000000000001/users \
-H 'Authorization: Bearer sk_…' \
-H 'Content-Type: application/json' \
-d '{"accountId":"acc_fb_page","emails":["[email protected]","[email protected]"]}'Emails and phones are normalized and SHA-256 hashed by Postgale; raw values never reach Meta. Use the audience id in targeting.customAudiences. DELETE /ads/audiences/{id}?accountId= removes it.
Insights
curl https://staging.postgale.com/api/v1/ads/insights?accountId=acc_fb_page&objectId=120250000000000002&datePreset=last_7d&breakdowns=age,gender \
-H 'Authorization: Bearer sk_…'Rows carry impressions, reach, frequency, clicks, inline_link_clicks, spend, cpc, cpm, ctr and actions. objectId is an ad account (act_…), campaign, ad set or ad; since/until (YYYY-MM-DD), level, timeIncrement=1 for daily rows.
Permissions
The Facebook account must be (re)connected after the Meta app has Advanced Access to ads_management and
ads_read (plus business_management, pages_manage_ads for boosting). Postgale keeps the
connecting user's token encrypted for ads calls; it is never returned.
Errors
| Status | code | When |
|---|---|---|
| 400 | confirmation_required | A spend-capable call (create, boost, ACTIVE, budget, bid, schedule or targeting change) without confirm: true. Nothing was created or changed |
| 400 | invalid_request | A field failed validation (budget below 100 minor units, lifetime budget without endTime, unknown goal…) |
| 400 | not_supported | accountId is not a Facebook Page account, or the object level cannot be updated |
| 403 | approval_required | Approval applies to the key, organization or account: ads writes are refused |
| 403 | missing_permission | The Facebook account did not grant ads_management / ads_read: reconnect it after the app has the permissions |
| 409 | reconnect_required | The token is no longer valid: reconnect the Facebook account |
| 429 | rate_limited | Meta's Marketing API rate limits (development tier is low) |
| 502 | platform_error | Any other Meta failure (the message carries Meta's reason) |