Meta Ads

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://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

Ad accounts

GET/api/v1/ads/accounts
curl https://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.

POST/api/v1/ads/boost
curl -X POST https://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):

POST/api/v1/ads/campaigns
curl -X POST https://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}'
POST/api/v1/ads/adsets
curl -X POST https://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}'
POST/api/v1/ads/creatives
curl -X POST https://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"}'
POST/api/v1/ads/ads
curl -X POST https://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}:

PATCH/api/v1/ads/campaigns/{id}
curl -X PATCH https://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://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

POST/api/v1/ads/audiences
curl -X POST https://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"}'
POST/api/v1/ads/audiences/{id}/users
curl -X POST https://postgale.com/api/v1/ads/audiences/23850000000000001/users \
  -H 'Authorization: Bearer sk_…' \
  -H 'Content-Type: application/json' \
  -d '{"accountId":"acc_fb_page","emails":["ana@example.com","bo@example.com"]}'

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

GET/api/v1/ads/insights
curl https://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. Self-hosted: set FACEBOOK_ADS_SCOPES (classic login) or FACEBOOK_ADS_CONFIG_ID (Login for Business), then PROVIDER_LIVE_ADS=1.

Errors

StatuscodeWhen
400confirmation_requiredA spend-capable call (create, boost, ACTIVE, budget, bid, schedule or targeting change) without confirm: true. Nothing was created or changed
400invalid_requestA field failed validation (budget below 100 minor units, lifetime budget without endTime, unknown goal…)
400not_supportedaccountId is not a Facebook Page account, or the object level cannot be updated
403approval_requiredApproval applies to the key, organization or account: ads writes are refused
403missing_permissionThe Facebook account did not grant ads_management / ads_read: reconnect it after the app has the permissions
409reconnect_requiredThe token is no longer valid: reconnect the Facebook account
429rate_limitedMeta’s Marketing API rate limits (development tier is low)
502platform_errorAny other Meta failure (the message carries Meta’s reason)