zuckerbot_create_campaign_from_spec
Build a complete Meta campaign VERBATIM from a declarative JSON spec — no strategy generation, no copy authoring. Everything is created PAUSED, always; launching remains a separate deliberate call.
Instructions
Build a complete Meta campaign VERBATIM from a declarative JSON spec — no strategy generation, no copy authoring. Everything is created PAUSED, always; launching remains a separate deliberate call. Recommended flow: send with dry_run=true first to get the fully resolved Graph API payloads without creating anything, review them, then re-send without dry_run to build. Validation failures return an errors array of per-field {path, message, kind: schema|semantic} entries — fix each path and retry. Spec shape: OPTIONAL identity {instagram_actor_id — the Instagram account's numeric id as Meta knows it for ads (NOT an @username); Ads Manager shows it under the ad's identity selector. OPTIONAL: when omitted, ZuckerBot automatically attaches the Instagram account linked to the connected Facebook Page and reports it in the response as instagram_identity {attached, instagram_actor_id, username, source}. Declare it only to override that, or when the Page has several accounts available and ZuckerBot declines to guess. If no account can be found the ads still build but do NOT deliver on Instagram placements, and the response says so. Discovery runs on the real build, not on dry_run (a dry run states what it will attempt). Not needed for EXISTING_POST ads, which keep the original post's identity}; campaign {name, objective OUTCOME_LEADS|OUTCOME_SALES, budget {type CBO_DAILY, amount, bid_strategy HIGHEST_VOLUME|LOWEST_COST_WITHOUT_CAP|COST_CAP} — spend caps, lifetime budgets and campaign/ad-set end dates are NOT supported yet; an unknown top-level campaign, budget, ad-set or ad field is rejected with a validation error, never silently dropped (fields nested inside targeting/placements/attribution/asset are validated per their documented shapes — do not add undocumented keys there), so set spend limits in Ads Manager after the PAUSED build if needed, special_ad_categories}, ad_sets [{name, conversion_location WEBSITE|INSTANT_FORM, attribution {click_days 1|7, view_days 0|1}, targeting {geo — ARRAY of 2-letter country codes e.g. ["AU"], age_min, advantage_audience, excluded_custom_audiences; OPTIONAL cities/regions for sub-country targeting — arrays of either {key} (Meta's numeric location key) or {name, region?, country?} which ZuckerBot resolves against Meta's location search. Cities also take {radius, distance_unit mile|kilometer}; Meta bounds a city radius to 10-50 miles / 17-80 km, so a bigger catchment needs MORE cities or regions, not a bigger radius. IMPORTANT: when cities or regions are present they REPLACE the country in the ad set's geo_locations (Meta unions those fields, so keeping the country would target the whole country); geo then only supplies the country to resolve names within. A name must match Meta's own spelling exactly — a near miss is rejected with the candidate list and their keys, never silently substituted, and an ambiguous name (two Las Vegases) is rejected the same way. Specs that NAME places need Meta credentials even for dry_run; key-only and country-only specs do not. The dry run and the build both echo resolved_locations so you can confirm which real place each name became}, placements {mode MANUAL|ADVANTAGE_PLUS, exclude}; WEBSITE additionally: pixel_id, optimisation_event {type CUSTOM_CONVERSION, id}|{type STANDARD, event e.g. Lead}, performance_goal MAXIMISE_CONVERSIONS (the Ads Manager label, not the Graph enum); INSTANT_FORM instead: lead_form_id — the Meta instant form on the connected Page (no pixel_id, no optimisation_event, and its ads take NO final_url — the form is the destination; the creative's required display link is set automatically to the business's stored website, else its Facebook Page URL; single-image creative, no multi-ratio placement customisation)}], ads [{name, asset {type IMAGE_SET, refs {1x1,4x5,9x16 — https URLs, or image hashes already uploaded into THIS ad account (a hash from the business media library or another ad account is rejected)}; the ad is built as a SINGLE-image creative from the preferred ratio (1x1, else 4x5, else 9x16) — multi-ratio placement customisation is currently unavailable because Meta now rejects asset_feed_spec creatives outside Dynamic Creative ad sets}|{type VIDEO, ref — pre-uploaded Meta video id (upload one with zuckerbot_upload_ad_asset)}|{type EXISTING_AD, ad_id — clones that ad's image/video asset from the SAME ad account; copy, CTA and destination come from THIS spec}|{type EXISTING_POST, ad_id — reuses the SAME page post as that ad (object_story_id), keeping the post's social proof and engagement; or object_story_id "_" directly; the post must belong to the connected Page and the source ad to the connected ad account; the post carries ALL copy/CTA/destination, so OMIT primary_text, headline, description, cta and final_url on EXISTING_POST ads — declaring any is rejected as existing_post_copy_conflict}, primary_text, headline, description, cta, final_url (WEBSITE ad sets only; all five omitted for EXISTING_POST), ad_set_name?}]. EXISTING_AD and EXISTING_POST (ad_id form) specs need Meta credentials even for dry_run (the source ad is read from Meta). Use zuckerbot_list_custom_conversions to find custom conversion ids. After PAUSED creation, review spend controls and use zuckerbot_resume_campaign for each intended ad, then ad set, then the campaign. Use Meta IDs, not zuckerbot_launch_campaign.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| spec | Yes | The declarative campaign spec (see tool description for the shape) | |
| dry_run | No | true = return the resolved Graph payloads without creating anything. Strongly recommended before a real build | |
| business_id | No | Optional business ID override for the authenticated API key |