Skip to content
Back to skills

Meta Ads Publisher

BSecurity

Create and publish ads to Meta (Facebook/Instagram) Ads Manager via the Marketing API — campaigns, ad sets, ads, image upload, carousel + video-placeholder support, all created PAUSED for human review. Runs a mandatory holistic pre-publish review and dual user confirmation. Use after /creative_director has prepared creatives, copy, and landing pages. Triggers (EN): "publish to Meta", "create Meta ads", "push ads to Ads Manager", "launch the campaign on Facebook/Instagram", "/meta_ads_publishe...

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 2, 2026
ai-agentsgobashapi

Works with

  • cli
  • api

Security analysis

B75/100
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study

Pro shows the line behind each finding and how to fix it

Scanned October 2, 2026

npx -y skills add agentik-os/claude-code-skills --skill meta_ads_publisher --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Meta Ads Publisher?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Meta Ads Publisher
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/agentik-os-meta-ads-publisher/badge)](https://www.skillsdirectory.com/skills/agentik-os-meta-ads-publisher)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: meta_ads_publisher
description: Create and publish ads to Meta (Facebook/Instagram) Ads Manager via the Marketing API — campaigns, ad sets, ads, image upload, carousel + video-placeholder support, all created PAUSED for human review. Runs a mandatory holistic pre-publish review and dual user confirmation. Use after /creative_director has prepared creatives, copy, and landing pages. Triggers (EN): "publish to Meta", "create Meta ads", "push ads to Ads Manager", "launch the campaign on Facebook/Instagram", "/meta_ads_publisher". Triggers (FR): "publie sur Meta", "crée les pubs Meta", "lance la campagne Facebook/Instagram", "envoie les pubs dans Ads Manager", "mets les pubs en ligne (en pause)".
---

# Meta Ads Publisher

Publish campaign assets to Meta Ads Manager via Marketing API.

## Single-execution craft (do NOT parallelize)

This skill performs ONE stateful, sequential publish operation — it is not a fan-out audit.
The chain is strictly ordered and each step's output is the next step's input:
**campaign → `campaign_id` → ad set → `adset_id` → upload image → `image_hash` → ad.**

- **NEVER run parallel agents against the same ad account.** Concurrent writers race on
  the same campaign/ad-set state and create duplicate or orphaned objects (R-SCOPE: one writer
  per ad account). If multiple ads are needed, create them serially within the single ad set.
- **Craft upgrades (apply every run):**
  1. **Idempotency** — before creating a campaign, list existing campaigns
     (`GET /act_{AD_ACCOUNT}/campaigns?fields=name,status`) and reuse/abort on an exact-name
     match instead of silently creating a duplicate.
  2. **Copy ↔ creative fidelity** — the ad copy, CTA, and landing URL must be carried verbatim
     from the /creative_director output; never paraphrase, re-write, or "improve" approved copy
     at publish time. This skill is a faithful courier, not an editor.
  3. **Fail loud, fail paused** — if any API call errors mid-chain, STOP, report the exact Graph
     API error + the IDs already created, and never proceed to the next step on a guess.

> **Portability note (VPS-only infra):** credentials below assume a local `.env` and the OmegaOS
> `TOOLS.md`. On any other host, export `META_AD_ACCOUNT_ID`, `META_PAGE_ID`, `META_ACCESS_TOKEN`
> in the environment (or your secret manager) — the curl logic is host-agnostic and needs no change.

## Prerequisites

- Meta Marketing API token (system user with ads_management permission)
- Ad Account ID
- Facebook Page ID
- Payment method on ad account (required by API)
- Approved creatives from /creative_director

## Credentials

Set these in your `.env` file:

```
META_AD_ACCOUNT_ID=act_your_account_id
META_PAGE_ID=your_facebook_page_id
META_ACCESS_TOKEN=your_access_token
```

See TOOLS.md for how to obtain these credentials.

## Workflow

### 1. Gather Assets

Before publishing, confirm you have:
- [ ] Campaign name and objective
- [ ] Ad set targeting (geo, age, interests)
- [ ] Daily/lifetime budget
- [ ] Image files (uploaded or local paths)
- [ ] Ad copy for each creative
- [ ] Landing page URLs
- [ ] CTA type (LEARN_MORE, SIGN_UP, etc.)

### 2. Pre-Publish Holistic Review ⚠️ MANDATORY

**Before creating ANY ads, review the full picture:**

```
┌─────────────────────────────────────────────────────────┐
│  HOLISTIC REVIEW CHECKLIST                              │
├─────────────────────────────────────────────────────────┤
│  □ Ad copy matches creative visuals                     │
│  □ Landing page URL is correct for each ad              │
│  □ CTA matches the funnel stage (TOFU/MOFU/BOFU)        │
│  □ Targeting makes sense for the offer                  │
│  □ Budget is appropriate for test/scale phase           │
│  □ All ads in set are thematically consistent           │
│  □ No typos or broken links                             │
│  □ DSA compliance fields filled (EU targeting)          │
│  □ Image dimensions correct (1:1, 4:5, etc.)            │
│  □ Video placeholders have complete copy                │
└─────────────────────────────────────────────────────────┘
```

**Present summary to user:**
```markdown
## 📋 Pre-Publish Review

**Campaign:** [name]
**Objective:** [TRAFFIC/LEADS/etc]
**Budget:** €[X]/day

**Ad Set:** [name]
**Targeting:** [countries], ages [X-Y]

**Ads to create:**
| # | Name | Type | Landing Page | CTA |
|---|------|------|--------------|-----|
| 1 | ... | Image | /path | LEARN_MORE |
| 2 | ... | Video | /path | SIGN_UP |

**Copy preview:**
> [First 100 chars of each ad's copy...]

✅ All checks passed. Ready to publish?
```

**Wait for user confirmation before proceeding.**

### 3. Create Campaign

```bash
curl -X POST "https://graph.facebook.com/v21.0/act_{AD_ACCOUNT}/campaigns" \
  -d "name=Campaign Name" \
  -d "objective=OUTCOME_TRAFFIC" \
  -d "status=PAUSED" \
  -d "special_ad_categories=[]" \
  -d "is_adset_budget_sharing_enabled=false" \
  -d "access_token=$TOKEN"
```

**Objectives:**
- `OUTCOME_AWARENESS` — Brand awareness
- `OUTCOME_TRAFFIC` — Website traffic
- `OUTCOME_ENGAGEMENT` — Post engagement
- `OUTCOME_LEADS` — Lead generation
- `OUTCOME_SALES` — Conversions

### 4. Create Ad Set

```bash
curl -X POST "https://graph.facebook.com/v21.0/act_{AD_ACCOUNT}/adsets" \
  -d "name=Ad Set Name" \
  -d "campaign_id={CAMPAIGN_ID}" \
  -d "status=PAUSED" \
  -d "billing_event=IMPRESSIONS" \
  -d "optimization_goal=LINK_CLICKS" \
  -d "bid_strategy=LOWEST_COST_WITHOUT_CAP" \
  -d "daily_budget=2000" \
  -d 'targeting={"geo_locations":{"countries":["DE","AT","CH"]},"age_min":25,"age_max":55,"targeting_automation":{"advantage_audience":0}}' \
  -d "dsa_beneficiary=Company Name" \
  -d "dsa_payor=Company Name" \
  -d "access_token=$TOKEN"
```

**Required for EU targeting:**
- `dsa_beneficiary` — Who benefits from the ads
- `dsa_payor` — Who pays for the ads
- `targeting_automation.advantage_audience` — 0 to disable, 1 to enable

### 5. Upload Images

```bash
curl -X POST "https://graph.facebook.com/v21.0/act_{AD_ACCOUNT}/adimages" \
  -F "filename=@/path/to/image.png" \
  -F "access_token=$TOKEN"
```

Returns `image_hash` for use in ad creative.

### 6. Create Ads

**Single Image Ad:**
```bash
curl -X POST "https://graph.facebook.com/v21.0/act_{AD_ACCOUNT}/ads" \
  -d "name=Ad Name" \
  -d "adset_id={ADSET_ID}" \
  -d "status=PAUSED" \
  -d 'creative={"object_story_spec":{"page_id":"{PAGE_ID}","link_data":{"image_hash":"{HASH}","link":"https://...","message":"Ad copy here","call_to_action":{"type":"LEARN_MORE"}}}}' \
  -d "access_token=$TOKEN"
```

**Carousel Ad:**
```bash
curl -X POST "https://graph.facebook.com/v21.0/act_{AD_ACCOUNT}/ads" \
  -d "name=Carousel Ad" \
  -d "adset_id={ADSET_ID}" \
  -d "status=PAUSED" \
  -d 'creative={"object_story_spec":{"page_id":"{PAGE_ID}","link_data":{"message":"Main copy","link":"https://...","child_attachments":[{"link":"...","image_hash":"...","name":"Slide title","description":"Slide desc"}],"multi_share_optimized":true}}}' \
  -d "access_token=$TOKEN"
```

**Video Ad (placeholder — copy only, no video):**
Create as link ad with copy. Video uploaded later:
```bash
curl -X POST "https://graph.facebook.com/v21.0/act_{AD_ACCOUNT}/ads" \
  -d "name=VIDEO PLACEHOLDER - Name" \
  -d "adset_id={ADSET_ID}" \
  -d "status=PAUSED" \
  -d 'creative={"object_story_spec":{"page_id":"{PAGE_ID}","link_data":{"link":"https://...","message":"Video script as ad copy","name":"Headline","description":"Subhead","call_to_action":{"type":"LEARN_MORE"}}}}' \
  -d "access_token=$TOKEN"
```

### 7. Post-Publish Confirmation

After creating all ads, provide summary:

```markdown
## ✅ Ads Published to Meta

**Campaign:** [name] (ID: xxx) — PAUSED
**Ad Set:** [name] (ID: xxx) — PAUSED

**Ads Created:**
| # | Name | Type | ID | Status |
|---|------|------|-----|--------|
| 1 | ... | Image | xxx | PAUSED |
| 2 | ... | Carousel | xxx | PAUSED |
| 3 | ... | Video Placeholder | xxx | PAUSED |

**Review in Ads Manager:**
https://www.facebook.com/adsmanager/manage/ads?act={AD_ACCOUNT}

**Next steps:**
1. Review ads in Ads Manager
2. Upload videos for placeholder ads
3. When ready, set status to ACTIVE
```

## CTA Types

| Type | Use Case |
|------|----------|
| LEARN_MORE | General info, tutorials |
| SIGN_UP | Email capture, courses |
| DOWNLOAD | Lead magnets, PDFs |
| GET_OFFER | Promotions, discounts |
| BOOK_NOW | Consultations, calls |
| CONTACT_US | B2B inquiries |
| SHOP_NOW | E-commerce |
| WATCH_MORE | Video content |

## Common Errors

| Error | Solution |
|-------|----------|
| No Payment Method | Add credit card to ad account |
| App in Development Mode | Switch app to Live mode |
| DSA fields required | Add dsa_beneficiary and dsa_payor |
| Advantage Audience required | Add targeting_automation.advantage_audience |
| Invalid image dimensions | Use 1:1, 4:5, or 9:16 aspect ratios |

## Integration with Creative Team

```
/campaign_planner → Strategy approved
        ↓
/creative_director → Assets created
        ↓
/meta_ads_publisher → Published to Meta (PAUSED)
        ↓
User reviews in Ads Manager → ACTIVE
```

## Output contract & verification

**Produces (Done Criteria):**
- One campaign, ≥1 ad set, ≥1 ad — **all `status=PAUSED`** — in the target ad account.
- A post-publish summary table with the **real** campaign/ad-set/ad IDs returned by the API.
- A working Ads Manager review link for the account.

**VERIFY (run before claiming done — runtime is the only truth, L1):**
```bash
# Confirm every created object exists AND is paused. Fail if any row is ACTIVE.
curl -s "https://graph.facebook.com/v21.0/{CAMPAIGN_ID}?fields=name,status&access_token=$TOKEN"
curl -s "https://graph.facebook.com/v21.0/{ADSET_ID}?fields=name,status&access_token=$TOKEN"
curl -s "https://graph.facebook.com/v21.0/act_{AD_ACCOUNT}/ads?fields=name,status&access_token=$TOKEN"
```
Done only when each GET returns the object with `"status":"PAUSED"`. A 200 on POST is not proof —
read back the object.

**No-hallucination guardrail (R-CITE):**
- Every ID in the summary table MUST come from an actual API response body — **never invent,
  guess, or pattern-fill an ID.** If a call failed, the ID cell reads `FAILED: <error>`, not a number.
- Quote the exact Graph API error string on any failure; do not summarize it as "something went wrong".
- The review checklist (§2) is graded against the real assets on disk/URL, not assumed.

## Safety Rules

1. **Always create as PAUSED** — Never auto-activate ads
2. **Always do holistic review** — Check copy ↔ creative match
3. **Always confirm with user** — Before creating, after creating
4. **Always provide Ads Manager link** — For manual review
5. **Never modify active ads** — Only paused/draft

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…