- 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
Voir sur GitHub