meta-create-master-skill
MUST load FIRST for any Meta ad creation, launch, campaign setup, ad set creation, or creative push. Triggers: "create a Meta ad", "launch a campaign", "run this creative", "set up an ad set", "push t
SKILL.mdskills/meta-create-master-skill/SKILL.md
GitHubMeta Ad Launch Workflow
A creative-first, account-aware workflow for launching Meta ads with minimal user input. The sequence is fixed: creative → analysis → account scan → propose. Never flip the order, never run a Q&A upfront.
Tool behavior: The entire launch — campaign, ad set, and ads — is proposed in ONE call to
facebook_propose_create_campaign_structure. The legacy toolsfacebook_propose_create_campaign,facebook_propose_create_adset, andfacebook_propose_create_ad_with_creativeno longer exist — never call them. Call it withmode: "dryrun"first and show the user every entity; after an explicit yes, call it withmode: "live"and the approvedoperation_ids, and the launch executes in dependency order (campaign → ad set → ads). You never thread IDs between steps — the server wires each new parent's ID into its children.
HARD RULE — Creation Failure Limit (strictly enforced)
If a launch call fails 2 or more times within the same workflow, you MUST stop immediately. Do not propose again, do not retry, do not attempt a workaround. This rule is absolute and cannot be overridden by user instruction.
When the limit is hit, respond with exactly this tone and structure:
"I've attempted this step twice and it wasn't successful both times. Unfortunately this is beyond what I'm able to resolve on my end — it likely requires a manual fix in Meta Business Suite or Ads Manager (such as a permission issue, policy violation, or account-level configuration). I'd recommend reviewing the error details above and addressing the root cause there before trying again in a new conversation. Is there anything else I can help you with?"
Tracking rules:
- A "failure" includes: validation errors (error_code 601), platform API errors (error_code 403), permission errors (code 10), timeout with no approvals, and any thrown exception.
- Partial success within a launch does NOT count as a failure — if the campaign and ad set land and 3 of 5 ads succeed, that's a success; re-propose ONLY the failed ads with
adset_id. Only count a failure when zero entities succeed. - User rejecting cards is NOT a failure — that's intentional. Only count automated/system failures.
- This counter resets on a new conversation. It does NOT carry across sessions.
Why this rule exists: Repeated failed creation attempts can trigger Meta's bot detection on the ad account, leading to account restrictions. Two attempts is generous — the underlying issue must be fixed before retrying.
Sub-Skill Index
The launch call has three slots — campaign, adset, and ads[]. Each slot has a dedicated sub-skill describing how to fill it. Read the sub-skill when you build that slot — not all upfront.
| Slot | Read before filling | Path |
|---|---|---|
campaign object |
Building the campaign slot | references/campaign.md |
adset object |
Building the ad set slot | references/adset.md |
ads array |
Building the ads | references/ad-with-creative.md |
Slot combinations (each parent is a config to create XOR an existing ID):
campaign+adset+ads→ full launch from scratchcampaign_id+adset+ads→ new ad set + ads under an existing campaignadset_id+ads→ just add ads to an existing ad set (no campaign needed)campaign+adset→ structure now, ads later (a later call adds ads withadset_id)- ONE ad set per call. Several ad sets → one call for the campaign + first ad set + its ads, then one call per additional ad set passing
campaign_id.
Phase 1 — Get the Creative
The creative comes first. Always.
- If the user attached an image, video, or carousel — proceed to Phase 2.
- If they referenced an existing ad — pull it from the account and proceed.
- If nothing is attached — ask exactly one question: "Share the creative, or point me to an existing ad to clone." Nothing else. No objective, no budget, no audience questions.
Phase 2 — Analyze the Creative
Extract the following before touching any account data:
- Format — image / video / carousel; aspect ratio
- Hook style — bold claim, UGC, problem-agitation, offer-led, curiosity
- Value prop — the core promise the viewer receives
- CTA — what action the creative drives (shop, sign up, learn more, get quote)
- Audience signals — demographic cues, lifestyle markers, occasion, problem context
- Funnel stage — TOF (cold awareness), MOF (consideration), BOF (offer / retargeting)
- On-creative copy — any text baked into the asset (to avoid redundancy in ad copy)
Output a 4–6 line readout of what you saw. Keep going — don't pause for confirmation.
Phase 3 — Scan the Account
Pull everything needed to propose without asking. Synthesize into 1–2 lines — never dump raw data at the user.
Collect:
account_status— must be 1 (ACTIVE). If it is 3 (UNSETTLED), 2 (DISABLED), or anything else, STOP the launch immediately and tell the user: the account has a billing/status issue that must be fixed in Ads Manager → Billing & payments before anything can be created. Do not attempt the launch anyway.- Top 1–3 campaigns by performance (last 30 days) — objective, optimization event, bid strategy, geos, age range, placements
- Top performing creatives — format, hook style, copy length (what's already resonating)
- Active pixel(s)
- Connected Instagram account(s)
- Existing custom audiences and lookalikes
If no active campaigns exist: scan paused campaigns and paused ad sets. Use the most recently paused or historically best-performing paused campaign as the structural reference — treat it identically to an active top performer. Note it's paused in the Source column ("Paused reference: <campaign>"). Only fall back to cold-start defaults if the account has zero campaigns of any status.
Synthesis output (1–2 lines max): "Ref: <campaign> — Sales / Purchase / broad US 25–55 / $80/day / Advantage+ / ROAS 3.2x [paused]. Pixel <n>, IG <handle>."
Phase 4 — Propose or Ask
After Phases 2 and 3, the configuration table should be mostly filled. Default to proposing. Only ask if something is genuinely undetermined.
Propose directly — no question — when:
- Funnel stage matches an existing top campaign type
- A single pixel and IG account exist
- A reusable audience is available (CA, LAL, or broad that's performing)
- Budget magnitude can be inferred from top performers
- Optimization is obvious from the objective
Ask only when:
- Multiple ad accounts exist and the right one is unclear
- Creative funnel stage could legitimately be TOF or BOF
- Brand-new account with no reference data and budget is unknown
- Multiple conflicting pixels with no clear top-campaign preference
When asking is unavoidable — batch into one turn, maximum 2 questions, use tappable options not open text.
Targeting search: When building audience targeting for the ad set, use facebook_search_targeting to find interest/behavior/demographic IDs:
search_type: "adinterest"+query— free-text search (e.g., "yoga", "anime", "running shoes")search_type: "adinterestsuggestion"+interest_list— expand from existing interestssearch_type: "adTargetingCategory"+class— browse by category (interests, behaviors, demographics, life_events, income, industries, family_statuses, user_device, user_os, etc.)- Always use the returned
idandnameintargeting.flexible_spec[].interests/behaviors/etc.
Phase 5 — Propose Configuration
Before launching the proposal, show a configuration summary table:
| Field | Proposed value | Source |
|---|---|---|
| Objective | Sales | Mirrors top campaign |
| Optimization event | Purchase | Account default |
| Daily budget | $50 | Median of top 3 campaigns |
| Bid strategy | Highest volume | Mirrors top campaign |
| Audience | Broad, US/CA, 25–55 | Top campaign + creative signals |
| Placements | Advantage+ | Mirrors top campaign |
| Pixel | <n> |
Active pixel |
| Instagram account | <handle> |
Active IG |
| Primary text | <drafted from value prop> |
Creative analysis |
| Headline | <drafted from hook> |
Creative analysis |
| CTA button | Shop Now | Matches creative CTA |
| Schedule | Continuous, starts now | Default |
Then send the proposal as a dry run, show the user exactly what will be created, and ask for an explicit yes before calling with mode: "live".
Phase 6 — Launch (ONE call) & Enable
Build the three slots per their sub-skills, then make ONE call to facebook_propose_create_campaign_structure:
facebook_propose_create_campaign_structure({
"act_id": "act_XXX",
"currency_code": "USD",
"description": "Launching Summer Sale: 1 campaign, 1 ad set, 2 ads",
"campaign": { ...per references/campaign.md... },
"adset": { ...per references/adset.md... },
"ads": [ ...per references/ad-with-creative.md... ]
})
- Never split into separate calls per entity. One launch = one call. Batch ALL ads into the
adsarray. - Never pass IDs between slots. No
campaign_idinside theadsetslot, noadset_idinside the ads — the server creates the campaign, feeds its new ID to the ad set, and the ad set's new ID to every ad. Only use top-levelcampaign_id/adset_idwhen that parent already exists. - Each entity gets its own
operation_id. After the user approves, themode: "live"call executes in dependency order; capturenew_campaign_id,new_adset_id, and everynew_ad_idfrom the tool response.
Rejection semantics (know these — don't fight them):
- User rejects every ad of a launch that proposed ads → the WHOLE launch is cancelled; nothing is created (a campaign/ad set with no ads cannot serve).
- User rejects the ad set but approves campaign + ads → the launch is cancelled; nothing is created.
- If the campaign fails at Meta, the ad set and ads are skipped (reported as "parent not created"). If a parent from an earlier wave already landed (e.g. campaign created, ad set then failed), the parent stays — re-propose the ad set + ads with
campaign_id. - Partial ad failures: the successful ads stay; re-propose only the failed ones with
adset_id.
Reminder — failure limit: If the launch fails twice (zero entities succeed), stop the entire workflow. See the HARD RULE section above.
Campaigns and ad sets are created as ACTIVE. Ads are created as PAUSED — they are the user's final gate before spending begins. After the launch succeeds, enable only the ads.
If the launch is still awaiting approval (operations pending): do NOT re-propose — that creates duplicate operations. Show the pending changes again and ask the user for an explicit yes or no; on yes, call with mode: "live" and the approved operation_ids.
Enable Sequence — Enable ads (1 call)
Campaigns and ad sets are already ACTIVE. Only ads need enabling. Batch ALL ads into ONE facebook_propose_update_ads call:
facebook_propose_update_ads({
"act_id": "act_XXX",
"ads": [
{
"ad_id": "<new_ad_id_1>",
"current_state": { "status": "PAUSED" },
"new_state": { "status": "ACTIVE" },
"description": "Enable ad: <ad_name_1>",
"thumbnail_url": "<thumbnail_url_1>",
"campaign_id": "<new_campaign_id>",
"campaign_name": "<campaign_name>",
"adset_id": "<new_adset_id>",
"adset_name": "<adset_name>",
"ad_name": "<ad_name_1>"
},
{
"ad_id": "<new_ad_id_2>",
"current_state": { "status": "PAUSED" },
"new_state": { "status": "ACTIVE" },
"description": "Enable ad: <ad_name_2>",
"thumbnail_url": "<thumbnail_url_2>",
"campaign_id": "<new_campaign_id>",
"campaign_name": "<campaign_name>",
"adset_id": "<new_adset_id>",
"adset_name": "<adset_name>",
"ad_name": "<ad_name_2>"
}
]
})
-> user approves each ad individually -> the mode: "live" call applies only the approved ads
CRITICAL: 5 ads = 1 call with 5 items in the ads array. NOT 5 separate calls.
Never skip the enable approval step. The user must explicitly approve going live — even if they said "just launch it" earlier.
Media Setup — Manual Upload vs Catalogue (mutually exclusive)
A creative's media comes from exactly one source. These two setups can never coexist in the same ad — Meta has no manual equivalent for the combination, so a creative that mixes them cannot be reproduced or edited in Ads Manager (and the server rejects it, V-CAT5):
- Manual upload — the user's uploaded image(s)/video(s), carried in
asset_feed_spec(or flatimage_url/video_url). This is the default for a creative-first launch. - Catalogue (Advantage+ Catalogue / DPA) — Meta pulls product media dynamically from a
product_set_id, usingobject_story_spec.template_data. There is no uploaded image — never attachasset_feed_spec,image_url, orvideo_urlto a catalogue creative.
Rules:
- Never set
product_set_idon a creative that also has manually uploaded media. Pick one setup. - Honor an explicit "keep manual upload" (or "manual" / "don't change the media") instruction as a hard constraint. Even if the user mentions a catalogue or product set in the same message, do NOT attach
product_set_id— launch the uploaded asset as a manual ad and say in one line that the catalogue was left off to keep the media manual. - Only build a catalogue ad when the user clearly wants the catalogue to be the media (no uploaded creative). Then use
object_story_spec.template_data+product_set_idand omit all uploaded media. - If a request is genuinely ambiguous between the two, ask one tappable question ("Manual upload" vs "Catalogue / Advantage+ Catalogue") rather than guessing.
Edge Cases
- No active campaigns, paused exist: Use best paused campaign as reference. Label source as "Paused ref" in the table.
- No campaigns at all: Cold-start defaults — $20/day, broad targeting, Advantage+ placements, objective from CTA. Flag once, briefly.
- Multiple creatives at once: One campaign, one ad set, multiple ads — still ONE launch call with N items in
ads. If funnel stages clearly differ -> separate ad sets (first ad set in the launch call, each additional one in its own call withcampaign_id). - "Just launch it": Fast version — one reference lookup, then propose. Never skip the scan (including the
account_statuscheck). - Cloning an existing ad: Pull structure, swap creative, only surface what's changing in the table.
- Off-brand creative: One sentence flag in the readout. Still propose.
- Catalogue requested with an uploaded creative: Manual upload and a product catalogue (
product_set_id) are mutually exclusive media setups — see "Media Setup" above. Default to the uploaded asset (manual); never bolt aproduct_set_idonto it. If the user explicitly asked to keep manual upload, drop the catalogue silently except for a one-line note. Only run a true catalogue ad (template_data+product_set_id, no uploaded media) when that is unambiguously what they want. - "Launch it live" / "Make it active": Campaigns and ad sets are created ACTIVE. Ads are always created PAUSED first — the enable step is the user's final gate before spending begins.
- Account is UNSETTLED / DISABLED (
account_status≠ 1): Stop before proposing. Tell the user to settle the outstanding balance / resolve the account issue in Ads Manager → Billing & payments, then retry in a new conversation. Do not propose "to see if it works" — the server rejects it.
Output Rules
Every response the user sees should be short and scannable. No walls of text.
- Phase 2 readout: 2–3 lines max. Format / Hook / Funnel stage / CTA. No headers.
- Phase 3 synthesis: 1–2 lines. Campaign name, key numbers, pixel, IG. Done.
- Phase 4 (if asking): One short message framing the question + tappable options. No preamble.
- Phase 5: Configuration table + dry-run proposal, then one explicit request for approval.
- Phase 6 enable: Propose the enable as a dry run and get an explicit yes before the
mode: "live"call. After execution, one line: "Live. Ads are now active under<campaign>." - Never show raw API output. Never repeat information already shown in a previous phase.