AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Meta Ads Publisher

skill-gquthier-meta-ads-publisher-meta-ads-publisher · by gquthier

Publish, launch and manage Meta Ads (Facebook/Instagram) campaigns end-to-end via the Graph API — campaign → ad sets → creatives → ads, all created PAUSED (ready to go, nothing spends until you activate). Supports LEADS and CONVERSION objectives. Includes dev token setup, preflight checks, and the 12 known API gotchas that cause real errors in production.

No reviews yet
0 installs
25 views
0.0% view→install

Install

$ agentstack add skill-gquthier-meta-ads-publisher-meta-ads-publisher

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-gquthier-meta-ads-publisher-meta-ads-publisher)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

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 →
Are you the author of Meta Ads Publisher? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

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

  1. Go to developers.facebook.com/appsCreate App
  2. Type: Business
  3. Add products: Marketing API + Pages API
  4. 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.

  1. Meta Business Suite → Settings → Users → System Users → Add
  2. Role: Admin
  3. Click the system user → Generate Token
  4. Select your app from Step 1
  5. 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 |

  1. 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.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.