Install
$ agentstack add skill-gquthier-meta-ads-publisher-meta-ads-publisher ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Meta Ads Publisher — Claude Code Skill
Automate the full Meta Ads creation pipeline via the Marketing API (Graph API v21.0). Claude handles every API call: campaign → ad sets → targeting → creatives → ads. Everything is created PAUSED — nothing spends until you manually activate in Ads Manager.
> See references/graph-api-cheatsheet.md for a quick endpoint + error-code reference. > See scripts/ for ready-to-run Python/bash helpers.
1. Developer Setup (one-time)
Step 1 — Create a Meta App
- Go to developers.facebook.com/apps → Create App
- Type: Business
- Add products: Marketing API + Pages API
- Note your App ID and App Secret (Settings → Basic)
Step 2 — Create a System User (recommended)
System Users have non-expiring tokens tied to a Business, not a personal account.
- Meta Business Suite → Settings → Users → System Users → Add
- Role: Admin
- Click the system user → Generate Token
- Select your app from Step 1
- Required scopes:
| Scope | Purpose | |---|---| | ads_management | Create/read/update campaigns, ad sets, ads | | pages_manage_ads | Manage ads on behalf of Pages | | leads_retrieval | Read Lead Ad form responses (LEADS mode only) | | business_management | Access Business Manager objects |
- Grant the System User Admin access to:
- The Ad Account (Business Settings → Ad Accounts)
- The Facebook Page used in ads (Business Settings → Pages)
Step 3 — Verify your token
curl -s "https://graph.facebook.com/v21.0/debug_token\
?input_token=$META_ACCESS_TOKEN\
&access_token=${META_APP_ID}|${META_APP_SECRET}" | python3 -m json.tool
Confirm "is_valid": true and that all required scopes are listed under "scopes".
Step 4 — Store credentials as environment variables
export META_ACCESS_TOKEN="your_system_user_token"
export META_APP_ID="your_app_id"
export META_APP_SECRET="your_app_secret"
export META_AD_ACCOUNT="act_XXXXXXXXXX" # from Ads Manager URL
export META_PAGE_ID="XXXXXXXXXX" # Page → About → Page ID
export META_GRAPH_VERSION="v21.0"
Store these in a .env file (never commit it — add to .gitignore).
2. Two Objectives
| Mode | Meta Objective | Destination | Key Requirement | |---|---|---|---| | LEADS | OUTCOME_LEADS | Native Meta Instant Form (on-ad) | Page must have accepted Lead Ads ToS (human, one-time) | | CONVERSION | OUTCOME_SALES or OUTCOME_LEADS | Landing page + Pixel | Meta Pixel installed + conversion event firing |
When in doubt: service businesses (agencies, consulting, coaching) → LEADS. Products with a tracked funnel → CONVERSION.
3. Pipeline — 7 Phases
A. Token verify → debug_token, confirm scopes
B. Preflight → account_status, funding source, page access, ToS / Pixel check
C. Form or Pixel → LEADS: create or reuse lead form · CONVERSION: resolve pixel_id + event
D. Campaign(s) → objective, PAUSED, budget-sharing flag
E. Ad Sets → targeting, budget, optimization, DSA compliance
F. Creatives + Ads → upload images → creatives → ads (DCO or per-placement)
G. Verify + Recap → re-read all statuses → deliver JSON recap of created IDs
Phase A — Token + Identity
BASE="https://graph.facebook.com/$META_GRAPH_VERSION"
# Verify token
curl -s "$BASE/debug_token\
?input_token=$META_ACCESS_TOKEN\
&access_token=${META_APP_ID}|${META_APP_SECRET}" | python3 -m json.tool
# List accessible ad accounts
curl -s "$BASE/me/adaccounts\
?fields=id,name,account_status,currency\
&access_token=$META_ACCESS_TOKEN"
Phase B — Preflight (read-only)
ACT=$META_AD_ACCOUNT
# Account status + payment method
curl -s "$BASE/$ACT\
?fields=name,account_status,disable_reason,currency,funding_source\
&access_token=$META_ACCESS_TOKEN"
# account_status must = 1 (active). If 2 → STOP (see Gotcha #10)
# funding_source must be present to create Ads (see Gotcha #5)
# Confirm page access
curl -s "$BASE/$META_PAGE_ID\
?fields=name\
&access_token=$META_ACCESS_TOKEN"
# LEADS mode — check Lead Ads ToS acceptance
curl -s "$BASE/$META_PAGE_ID\
?fields=leadgen_tos_accepted\
&access_token=$META_ACCESS_TOKEN"
# CONVERSION mode — list pixels on account
curl -s "$BASE/$ACT/adspixels\
?fields=id,name\
&access_token=$META_ACCESS_TOKEN"
If any check fails (account disabled, no payment, ToS not accepted, no pixel) → stop and ask the user to fix it. Do not attempt to work around it.
Phase C — Lead Form (LEADS) or Pixel (CONVERSION)
LEADS — list existing forms (reuse if possible):
curl -s "$BASE/$META_PAGE_ID/leadgen_forms\
?fields=id,name,status\
&access_token=$META_ACCESS_TOKEN"
LEADS — create a new form (use scripts/create_lead_form.py):
# Get a Page token first (required for lead form creation)
PAGE_TOKEN=$(curl -s "$BASE/$META_PAGE_ID\
?fields=access_token\
&access_token=$META_ACCESS_TOKEN" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
curl -s -X POST "$BASE/$META_PAGE_ID/leadgen_forms" \
-d "name=My Lead Form" \
-d "questions=[{\"type\":\"FULL_NAME\"},{\"type\":\"EMAIL\"},{\"type\":\"PHONE\"}]" \
-d "privacy_policy={\"url\":\"https://yoursite.com/privacy\"}" \
-d "thank_you_page={\"title\":\"Thank you!\",\"body\":\"We will be in touch shortly.\"}" \
-d "access_token=$PAGE_TOKEN"
# → returns { "id": "FORM_ID" }
CONVERSION — note your pixel_id and event name (e.g. Lead, Purchase, CompleteRegistration, Schedule).
Phase D — Create Campaign
Use scripts/create_campaign.py.
curl -s -X POST "$BASE/$META_AD_ACCOUNT/campaigns" \
-d "name=My Campaign — $(date +%Y-%m)" \
-d "objective=OUTCOME_LEADS" \
-d "status=PAUSED" \
-d "is_adset_budget_sharing_enabled=false" \
-d "special_ad_categories=[]" \
-d "access_token=$META_ACCESS_TOKEN"
# → returns { "id": "CAMPAIGN_ID" }
For the CONVERSION mode use objective=OUTCOME_SALES.
Phase E — Create Ad Set
Use scripts/create_adset.py. Adapt targeting, optimization_goal, and promoted_object to your mode.
LEADS example:
curl -s -X POST "$BASE/$META_AD_ACCOUNT/adsets" \
-d "name=Ad Set — Broad" \
-d "campaign_id=CAMPAIGN_ID" \
-d "billing_event=IMPRESSIONS" \
-d "optimization_goal=LEAD_GENERATION" \
-d "bid_strategy=LOWEST_COST_WITHOUT_CAP" \
-d "daily_budget=1500" \
-d "destination_type=ON_AD" \
-d "promoted_object={\"page_id\":\"$META_PAGE_ID\"}" \
-d "targeting={
\"geo_locations\":{\"countries\":[\"US\"]},
\"age_min\":18,
\"age_max\":65
}" \
-d "dsa_beneficiary=Your Company Name" \
-d "dsa_payor=Your Company Name" \
-d "status=PAUSED" \
-d "access_token=$META_ACCESS_TOKEN"
CONVERSION example — swap optimization_goal and add promoted_object:
-d "optimization_goal=OFFSITE_CONVERSIONS" \
-d "destination_type=WEBSITE" \
-d "promoted_object={\"pixel_id\":\"PIXEL_ID\",\"custom_event_type\":\"LEAD\"}"
Search for interest IDs:
curl -s "$BASE/search?type=adinterest&q=Yoga&limit=5&access_token=$META_ACCESS_TOKEN"
Phase F — Upload Images → Creative → Ad
Use scripts/upload_images.py + scripts/create_ads.py.
# 1. Upload image (multipart — get hash back)
curl -s -X POST "$BASE/$META_AD_ACCOUNT/adimages" \
-F "filename=@/path/to/image.jpg" \
-F "access_token=$META_ACCESS_TOKEN"
# → { "images": { "image.jpg": { "hash": "IMAGE_HASH" } } }
# 2. Create ad creative (LEADS — on-ad form)
curl -s -X POST "$BASE/$META_AD_ACCOUNT/adcreatives" \
-d "name=Creative — image 1" \
-d "object_story_spec={
\"page_id\": \"$META_PAGE_ID\",
\"link_data\": {
\"image_hash\": \"IMAGE_HASH\",
\"message\": \"Your ad copy here.\",
\"call_to_action\": {
\"type\": \"SIGN_UP\",
\"value\": {\"lead_gen_form_id\": \"FORM_ID\"}
}
}
}" \
-d "access_token=$META_ACCESS_TOKEN"
# → { "id": "CREATIVE_ID" }
# 3. Create ad
curl -s -X POST "$BASE/$META_AD_ACCOUNT/ads" \
-d "name=Ad — image 1" \
-d "adset_id=ADSET_ID" \
-d "creative={\"creative_id\":\"CREATIVE_ID\"}" \
-d "status=PAUSED" \
-d "access_token=$META_ACCESS_TOKEN"
# → { "id": "AD_ID" }
CONVERSION creative — replace call_to_action with:
{
"type": "LEARN_MORE",
"value": {"link": "https://yoursite.com/landing-page"}
}
Dynamic Creative Optimization (DCO): set is_dynamic_creative=true on the ad set, then use asset_feed_spec in the creative (max 10 images — see Gotcha #9).
Phase G — Verify + Recap
# Campaign
curl -s "$BASE/CAMPAIGN_ID?fields=name,status,effective_status&access_token=$META_ACCESS_TOKEN"
# Ad Set
curl -s "$BASE/ADSET_ID?fields=name,status,effective_status,daily_budget&access_token=$META_ACCESS_TOKEN"
# Ad
curl -s "$BASE/AD_ID?fields=name,status,effective_status&access_token=$META_ACCESS_TOKEN"
Deliver a JSON recap with all created IDs + any pending human actions (missing payment, ToS, activation).
4. Key Endpoints Reference
| Action | Method | Endpoint | |---|---|---| | Verify token | GET | /debug_token?input_token=T&access_token=APPID\|SECRET | | List ad accounts | GET | /me/adaccounts?fields=id,name,account_status,currency | | Account status | GET | /act_ID?fields=name,account_status,disable_reason,currency,funding_source | | Page token | GET | /PAGE_ID?fields=access_token | | Lead Ads ToS check | GET | /PAGE_ID?fields=leadgen_tos_accepted | | List lead forms | GET | /PAGE_ID/leadgen_forms?fields=id,name,status | | List pixels | GET | /act_ID/adspixels?fields=id,name | | Search interests | GET | /search?type=adinterest&q=Yoga&limit=5 | | Create campaign | POST | /act_ID/campaigns | | Create ad set | POST | /act_ID/adsets | | Create lead form | POST | /PAGE_ID/leadgen_forms (Page token required) | | Upload image | POST | /act_ID/adimages (multipart) | | Create creative | POST | /act_ID/adcreatives | | Create ad | POST | /act_ID/ads | | Pause object | POST | /OBJECT_ID with status=PAUSED | | Enable object | POST | /OBJECT_ID with status=ACTIVE | | Update daily budget | POST | /ADSET_ID with daily_budget=NEW_AMOUNT_CENTS | | Get insights | GET | /act_ID/insights?fields=spend,impressions,clicks&date_preset=last_7d | | List campaigns | GET | /act_ID/campaigns?fields=id,name,status,objective | | List ad sets | GET | /CAMPAIGN_ID/adsets?fields=id,name,status,daily_budget | | List ads | GET | /ADSET_ID/ads?fields=id,name,status,effective_status |
5. Known API Gotchas (real production errors)
G1 — is_adset_budget_sharing_enabled=false required at campaign creation
Without CBO, the API requires this field. Always include it when creating campaigns without campaign-level budget. → error_subcode 4834011 without it.
G2 — bid_strategy required on ad set
If no bid amount is set, bid_strategy=LOWEST_COST_WITHOUT_CAP must be explicit on the ad set. → error_subcode 2490487 without it.
G3 — DSA fields required for EU-targeting ad sets
Any ad set targeting EU audiences needs dsa_beneficiary + dsa_payor (your company name). → error_subcode 3858081 without them.
G4 — Advantage+ Audience forces age_max >= 65
targeting_automation.advantage_audience=1 prevents setting `age_max "Set up a Meta Ads campaign for [your product] in LEADS mode" > "Create a CONVERSION campaign on my Meta account" > "Publish my ads to Meta — use the meta-ads-publisher skill"
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: gquthier
- Source: gquthier/meta-ads-publisher
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.