Early access
Meta Ads API for Facebook and Instagram, over REST and MCP
The Spaces Ads API lets your code or AI agent build Facebook and Instagram campaigns that start paused, read spend and results, and pause ads, through one REST API with the same tools on MCP. Anything that would spend money comes back as a confirmation first, and only the owner can raise the caps.
Meta is the first network on the Spaces Ads API, and it is in early access. The routes live under https://api.spacesos.com/v1/ads and answer only for workspaces where ads have been turned on. Everywhere else they answer 404 NOT_ENABLED. The same tools are on the MCP server at https://api.spacesos.com/mcp, so a script and an AI agent follow exactly the same rules.
Every response says which network it came from and which currency it is in. Money is always in major units of the ad account currency, so 25.5 means 25.50. Keys need ads:read to read, ads:write to build and change, ads:spend to confirm a change that spends, and accounts:manage to connect or disconnect an ad account.
A request, start to finish
# 1. Is the ad account ready to run ads?
GET https://api.spacesos.com/v1/ads/accounts/{ad_account_id}
Authorization: Bearer spc_live_…
# 2. Create a campaign. It is always created paused.
POST https://api.spacesos.com/v1/ads/accounts/{ad_account_id}/campaigns
Authorization: Bearer spc_live_…
Idempotency-Key: autumn-menu-campaign
{ "name": "Autumn menu", "objective": "traffic", "spend_cap": 300 }
# 3. Add an ad set and an ad (create_ad_set, create_ad), then ask to launch.
POST https://api.spacesos.com/v1/ads/accounts/{ad_account_id}/campaigns/{campaign_id}/launch
Authorization: Bearer spc_live_…
Idempotency-Key: autumn-menu-launch
→ { "status": "confirmation_required",
"confirmation": { "id": "…", "summary": "…", "previews": ["…"] } }
# 4. Only after the owner says yes. Needs the ads:spend scope.
POST https://api.spacesos.com/v1/ads/confirmations/{confirmation_id}/confirm
Authorization: Bearer spc_live_…A shortened example: check the account, build a paused campaign, ask to launch it, then confirm once the owner has said yes. Amounts are in the ad account's currency.
Before you build anything
- Call get_ad_account first. Its readiness list names anything that would stop delivery, such as the Meta account status, a missing payment method, a missing Page or caps that are not set, with how to fix each one.
- Find interests and places by name with search_ad_targeting. Targeting takes countries, regions, cities with a radius, and postcodes.
- Check an audience with estimate_ad_reach. It changes nothing, so it is safe to call while you and the person decide.
- list_ad_pages returns the Facebook Pages the ad account can advertise as, and list_ad_pixels the pixels (datasets) it can send conversions to.
Why launching is a single call
Meta does not turn on ad sets and ads when you turn on their campaign. launch_ad_campaign turns on the campaign together with everything Spaces built under it, after one confirmation. Ads go on first and the campaign last, so nothing spends unless every step worked. Use resume_ad_object for something that ran before and was paused.
Meta specifics
- A campaign can carry spend_cap, the most it may ever spend, which Meta itself enforces. Lowering it is immediate. Raising it on a running campaign returns a confirmation.
- url_tags adds UTM tags to the link, and Meta fills in placeholders such as {{campaign.name}}, so your analytics can name the campaign each visit came from.
- get_ad_review says whether an ad passed Meta’s review, why it was rejected or has issues, and why it is not running right now.
- upload_ad_image takes the file as content_base64 and returns the image_hash that create_ad needs. upload_ad_video takes a public https url and returns a video_id.
- Ad sets aimed at EU countries need the DSA names: who the ads benefit and who pays. The owner sets them once in the dashboard, or you pass dsa_beneficiary and dsa_payor on the ad set.
How a change that spends gets approved
- Call the tool as normal, for example launch_ad_campaign or a budget increase on update_ad_set.
- Spaces changes nothing and answers with status confirmation_required, a plain summary of the change against the caps, and preview links for each ad.
- Show the summary and previews to the person. If the ad account is set so a person approves, send them approve_url instead.
- After a clear yes, call confirm_ad_change with the confirmation id. It needs the ads:spend scope, and Spaces checks the caps again before it acts.
- If the person says no, call cancel_ad_change. list_ad_confirmations shows what is waiting and what was decided.
The caps sit above all of this. The owner sets a daily budget cap, which limits the total daily budget set to run, and a monthly spend cap, at which Spaces pauses the campaigns it runs. update_ad_limits can lower either one. Nothing can raise them except the owner, in the dashboard.
The tools on Meta Ads
Tools marked “asks first” can spend money, so they answer with a confirmation before they change anything. One-line summaries of every tool are on the ads API page.
Launch
| MCP tool | Meta Ads | REST |
|---|---|---|
list_ad_pages | Your Facebook Pages | GET /v1/ads/accounts/{ad_account_id}/pages |
list_ad_pixels | GET /v1/ads/accounts/{ad_account_id}/pixels | |
create_ad_campaign | POST /v1/ads/accounts/{ad_account_id}/campaigns | |
create_ad_set | POST /v1/ads/accounts/{ad_account_id}/ad-sets | |
create_ad | POST /v1/ads/accounts/{ad_account_id}/ads | |
upload_ad_image | content_base64 | POST /v1/ads/accounts/{ad_account_id}/images |
upload_ad_video | POST /v1/ads/accounts/{ad_account_id}/videos | |
get_ad_preview | GET /v1/ads/accounts/{ad_account_id}/ads/{ad_id}/preview | |
launch_ad_campaign | Asks first. Turns on ad sets and ads too | POST /v1/ads/accounts/{ad_account_id}/campaigns/{campaign_id}/launch |
search_ad_targeting | GET /v1/ads/accounts/{ad_account_id}/targeting-search | |
estimate_ad_reach | POST /v1/ads/accounts/{ad_account_id}/reach-estimate |
Moderate
| MCP tool | Meta Ads | REST |
|---|---|---|
update_ad_limits | PATCH /v1/ads/accounts/{ad_account_id}/limits | |
update_ad_campaign | Asks first. spend_cap enforced by Meta | PATCH /v1/ads/accounts/{ad_account_id}/campaigns/{campaign_id} |
update_ad_set | Asks first | PATCH /v1/ads/accounts/{ad_account_id}/ad-sets/{ad_set_id} |
pause_ad_object | POST /v1/ads/accounts/{ad_account_id}/objects/{object_id}/pause | |
resume_ad_object | Asks first | POST /v1/ads/accounts/{ad_account_id}/objects/{object_id}/resume |
create_ad_routine | Asks first | POST /v1/ads/accounts/{ad_account_id}/routines |
list_ad_routines | GET /v1/ads/routines | |
update_ad_routine | PATCH /v1/ads/routines/{routine_id} | |
delete_ad_routine | DELETE /v1/ads/routines/{routine_id} | |
list_ad_confirmations | GET /v1/ads/confirmations | |
get_ad_confirmation | GET /v1/ads/confirmations/{confirmation_id} | |
confirm_ad_change | POST /v1/ads/confirmations/{confirmation_id}/confirm | |
cancel_ad_change | POST /v1/ads/confirmations/{confirmation_id}/cancel |
Report
| MCP tool | Meta Ads | REST |
|---|---|---|
list_ad_campaigns | GET /v1/ads/accounts/{ad_account_id}/campaigns | |
list_ad_sets | GET /v1/ads/accounts/{ad_account_id}/ad-sets | |
list_ads | GET /v1/ads/accounts/{ad_account_id}/ads | |
get_ad_review | Meta’s review result | GET /v1/ads/accounts/{ad_account_id}/ads/{ad_id}/review |
get_ad_insights | GET /v1/ads/accounts/{ad_account_id}/insights | |
send_ad_conversions | To a pixel (dataset) | POST /v1/ads/accounts/{ad_account_id}/conversions |
Connect and caps
| MCP tool | Meta Ads | REST |
|---|---|---|
list_ad_networks | GET /v1/ads/networks | |
create_ads_connect_link | POST /v1/ads/connect-link | |
list_ad_accounts | GET /v1/ads/accounts | |
get_ad_account | GET /v1/ads/accounts/{ad_account_id} | |
update_ad_account | PATCH /v1/ads/accounts/{ad_account_id} | |
disconnect_ad_account | DELETE /v1/ads/accounts/{ad_account_id} |
FAQ
Is there a separate API for each ad network?
No. One set of routes under /v1/ads and the same MCP tools serve every network. The connected ad account decides the network, and each response says which one it is.
What does confirmation_required mean?
The change was not made. The response carries a plain summary and links to preview each ad. Show them to the person, and call confirm_ad_change only after they say yes.
Can my code raise a cap?
No. A key can lower the daily budget cap or the monthly spend cap with update_ad_limits, but only the owner raises them, in the dashboard. With no caps set, nothing can be turned on and the API answers CAPS_REQUIRED.
Can an ad routine spend money on its own?
A routine that only pauses starts at once. One that turns things on or sets a budget waits for a one-time confirmation, and each run checks the caps and is skipped if it would go over. Runs show in list_routine_runs.
Why do I get 404 NOT_ENABLED?
Ads is in early access, and the routes answer only for workspaces it has been turned on for. Write to hello@spacesos.com to ask for access.
Where is the full list of routes and error codes?
Every ads tool, with its route and a one-line summary, is on the ads API page and in llms-full.txt, together with the rules and error codes.
Useful next: Meta ads for owners, Meta ads from your AI agent, Every ads tool, Spaces docs, Pricing.