Create Campaign — Channel First / Native Structure (N×N×N)
create_native_structure_campaignCreate a multi-channel campaign using the platform's "Channel First Structure" (N×N×N) — the UI also labels it "Native Structure" / "Platform-First Structure". Your audiences, ads, and offers are organized into the ad platform's NATIVE structure (the WizNativeAdContainer entity) and optimized TOGETHER by the platform's own algorithm — ~1 experiment per channel (LinkedIn is the only exception: one experiment per container), with unified reporting and pacing. You specify the structure as explicit per-container groupings (each container is one audience + its ad_offers rows); the "N×N×N" label refers to the native structure holding your full set of audiences, ads, and offers together (it is NOT one isolated experiment per combination — that's Precision Optimization, create_campaign). Platform product copy: "Traditional hierarchical setup that leverages native platform algorithms while maintaining Metadata's pacing control, unified user experience and reporting." Sets channel.structureType=NATIVE.
KEYWORDS (for tool discovery — many users ask by the math, not the product name): NxNxN, N×N×N, "NxNxN campaign", Channel First Structure, Native Structure, Platform-First Structure, channel-first, platform-first, native platform structure, WizNativeAdContainer, container-based campaign, traditional / hierarchical structure, unified reporting.
NAMING CONVENTION (from the platform UI's "Campaign Structure" selector — match these exact labels):
• "Channel First Structure (NxNxN)" = THIS tool (`create_native_structure_campaign`); the UI also shows "Native Structure" / "Platform-First Structure". Audiences/ads/offers grouped in the platform's native structure and optimized together (~1 experiment per channel). "native" / "platform" here = the native ad platform (LinkedIn / Facebook / Google) whose algorithms this leans on — and structureType=NATIVE matches.
• "Precision Optimization (1x1x1)" = the OTHER tool, `create_campaign` — each audience×ad×offer combination tested as its own isolated experiment. Metadata's signature approach; the DEFAULT.
Routing anchor: "native" / "channel first" / "platform first" / "NxNxN" → this tool; "precision" / "1x1x1" → create_campaign.
════════════════════════════════════════════════════════════════════════════════
WARNING: TWO CAMPAIGN-CREATION TOOLS EXIST — READ BEFORE PICKING ONE
════════════════════════════════════════════════════════════════════════════════
CRITICAL: REMEMBER — HOW TO CHOOSE THE STRUCTURE (the user's decision always wins):
1. If the user explicitly names one, RESPECT IT — "native" / "Native Structure" / "Channel First" / "Platform-First" / "NxNxN" → create_native_structure_campaign (this tool); "precision" / "Precision Optimization" / "1x1x1" → create_campaign.
2. ELSE, if the request involves MANY ads AND audiences (Precision Optimization makes one experiment per combination — a large experiment count), ASK the user which structure they want before creating — do not guess.
3. OTHERWISE, DEFAULT to Precision Optimization (1×1×1) → create_campaign (NOT this tool).
The two build structurally different, non-swappable campaigns — so once the user decides, respect that decision for the rest of the campaign.
USE THIS TOOL (`create_native_structure_campaign`) — "Channel First" / "Native Structure" (N×N×N) — WHEN:
• The user says "native", "Native Structure", "Channel First Structure", "channel-first", "Platform-First Structure", "platform-first", "NxNxN", "N×N×N", "WizNativeAdContainer", or asks for the traditional / hierarchical / native-platform structure.
• The user wants the ad platform's native structure to group and optimize audiences/ads/offers together (~1 experiment per channel, unified reporting) rather than an isolated experiment per combination.
• The user provides explicit per-container groupings of one audience + its (ad, offer) pairs.
USE `create_campaign` ("Precision Optimization", 1×1×1) INSTEAD — DO NOT USE THIS TOOL — WHEN:
• The user says "Precision Optimization", "precision", "1x1x1", "1×1×1", or wants every audience×ad×offer combination tested separately in its own experiment.
• The user wants maximum budget control / fastest per-combination learning.
• No structure was specified — Precision Optimization (`create_campaign`) is the DEFAULT.
If you're unsure whether the user wants isolated per-combination experiments (→ `create_campaign`, Precision Optimization) or the native-platform grouped structure (→ this tool, Channel First), ASK which structure they want — switching modes after creation requires rebuilding the campaign from scratch.
The two tools produce structurally different campaigns on the backend (channel.structureType = METADATA for create_campaign, = NATIVE for this tool) and are NOT swappable mid-campaign. Pick correctly the first time.
════════════════════════════════════════════════════════════════════════════════
WHEN TO USE THIS TOOL:
- The user explicitly asks for "Channel First Structure" / "Native Structure" / "Platform-First Structure" / N×N×N / WizNativeAdContainer / the native-platform structure.
- You want the ad platform's algorithm to optimize audiences/ads/offers together under a unified structure (~1 experiment per channel) instead of isolating every combination.
- You want a compact, platform-managed experiment footprint with unified reporting.
WHEN NOT TO USE THIS TOOL:
- The user wants "Precision Optimization" (1×1×1) — every combination tested as its own isolated experiment — use `create_campaign`.
- No structure was specified (default is Precision Optimization) — use `create_campaign`.
CAMPAIGN TYPES (same as create_campaign):
1. "Lead Gen" — only Lead Gen (LG) offers allowed in any container.
2. "Brand Awareness" — both LG and LP offers allowed.
WARNING: OFFER-CHANNEL CHANNEL-LOCK STILL APPLIES
Lead Gen offers are channel-locked at the offer's creation time. A container under `linkedin.containers` MUST reference an offer whose data.channel='LINKEDIN' (or any Landing Page offer). Cross-channel reuse silently produces a non-serving campaign. The server does NOT validate the pairing. Same rules as create_campaign:
- linkedin.containers[i].ad_offers[].offer → LINKEDIN-locked LG OR any LP
- facebook.containers[i].ad_offers[].offer → FACEBOOK-locked LG OR any LP
- instagram.containers[i].ad_offers[].offer → FACEBOOK/INSTAGRAM-locked LG OR any LP
- google.containers[i].ad_offers[].offer → Landing Page offers ONLY (Google Ads has no native LG form support)
- reddit.containers[i].ad_offers[].offer → Landing Page offers ONLY (Reddit has no native LG form support)
- microsoft.containers[i].ad_offers[].offer → MICROSOFT_ADS-locked LG OR any LP
Each container is one audience × a LIST of (ad, offer) pairs (`ad_offers`, 1+). Put several pairs in one container to group multiple ads/offers under the same audience — the platform's native ad-set with multiple Ad/Offer rows. "1 audience → 3 ads → 1 offer" is ONE container whose `ad_offers` holds 3 pairs that repeat the same `offer` id. (Each pair is exactly one ad with one offer; to run the same audience as two separate ad-sets, send two containers instead.)
WARNING: SEARCH CHANNELS ARE KEYWORD-FIRST — `audience` IS OPTIONAL FOR GOOGLE ADS & MICROSOFT ADS
Google/Microsoft search ad groups are keyword-targeted, so a search container may OMIT `audience` and run keyword-only (just keywords + ad_offers). The platform allows audience-less native search containers, so this tool no longer forces an audience there. `audience` STAYS REQUIRED for the audience-targeted social channels (LinkedIn / Facebook / Instagram / Reddit). A search container with neither an audience nor keywords has nothing to target and is rejected.
REQUIRED FIELDS:
- campaignType: "Lead Gen" | "Brand Awareness"
- name: unique campaign name
- startDate / endDate: YYYY-MM-DD
- budgetGroup: budget group name
- At least one channel block with a non-empty containers list
PER-CHANNEL SHAPE:
Every channel block (linkedin / facebook / instagram / google / reddit / microsoft) takes:
containers: [
{
name: "string (<=255 chars) — visible in the platform UI",
audience: "audience name (resolved server-side via the same matched-poll create_campaign uses). REQUIRED for LinkedIn/Facebook/Instagram/Reddit; OPTIONAL for Google/Microsoft search (omit it for keyword-only ad groups)",
ad_offers: [ {ad: <int WizCreative ID>, offer: <int offer ID>}, ... ] // 1+ Ad/Offer pairs sharing this container's audience; repeat the same offer id to run several ads on it,
target_group: "optional Targeting Group name (singular — one per container)",
exclude_audience: "optional audience name to use as the exclude group for this container"
}
]
SEARCH-CHANNEL EXTRAS (Google Ads + Microsoft Ads, per container):
keywords: ["string", ...] // free-text phrases — created in the keyword library and grouped into this container's keyword group
keyword_ids: [<int>, ...] // optional; existing keyword-library IDs (from list_keywords) to put in this container's group — unioned with `keywords`
match_type: "PHRASE"|"BROAD"|"EXACT" // optional; match type for this container's keyword group (default PHRASE)
negative_keyword_lists: ["name", ...] // optional; applied at the channel level
On a search channel `audience` is OPTIONAL: a keyword-only container (keywords + ad_offers, no audience) is valid and is how non-brand / pure-keyword search is normally structured. You can still pass an audience to layer one on. The keyword group is created and linked to the container automatically. A search container with neither an audience nor keywords (`keywords`/`keyword_ids`) is rejected (nothing to target).
AUDIENCE NAME RESOLUTION:
- `audience` and `exclude_audience` are NAMES, not IDs. Same lookup path as create_campaign — the tool waits up to ~20s per channel for a freshly-created audience to become attachable (firmographics + FB/IG can take 8-15s).
- Audience names are resolved BEFORE any container elements are created: a container whose `audience` cannot be resolved is dropped up front (no keyword group or other element is left behind for it) and the failure is reported in the response `errors[]`. The error states whether retrying can help ("may still be matching") or not ("not available on this channel") — do NOT blind-retry the latter. The rest of the campaign still gets built.
- On Google/Microsoft search containers `audience` may be omitted entirely (keyword-only) — only names that are actually provided are resolved, so leaving it out costs no polling and raises no error.
AD AND OFFER ARE IDS (one `ad_offers` pair = one ad + one offer):
- `ad` is the ID returned by `create_update_image_ad` or `create_update_video_ad`.
- `offer` is the ID returned by `create_update_offer`. The tool looks up each unique offer to discover whether it's a Landing Page or Lead Gen Form and routes it into the right `nativeAds[].landingPageId` vs `nativeAds[].leadGenFormId` slot. Every pair becomes one `nativeAds[]` entry under the container.
BID STRATEGY (automatic):
Same defaults as create_campaign:
- LinkedIn / Facebook / Instagram → AUTO_BID
- Google Ads / Microsoft Ads → TARGET_SPEND (device modifiers reset)
- Reddit → objective-driven, mirroring the platform UI:
Lead Gen forces CLICKS (Traffic) + AUTO_BID; Brand Awareness keeps
IMPRESSIONS + MANUAL_BID. Adjust later with update_reddit_channel_settings.
WHAT THE TOOL DOES FOR YOU (single call, no follow-up needed):
Creates the campaign shell and its budget group, enables each requested
channel and sets it to the NATIVE structure, builds the per-container
keyword group on Google / Microsoft Ads search channels, creates one
native ad container per grouping with its ad/offer pairs, applies the
bid strategy, and sets the schedule. The response reflects the final
campaign state — you do not need to sequence or repeat any of this.
RESPONSE: Same top-level shape as create_campaign (`success`, `campaign_id`, `campaign_url`, `status`, `optimization_group`, `summary`, `full_response`). The per-channel breakdown returns a `containers` array — `[{id, name, experiment_id, audience_group_id, targeting_group_id, group_exclude_id, keyword_group_id, native_ads: [{id, ad_id, landing_page_id, lead_gen_form_id}]}]` — instead of the legacy `audiences`/`ad_groups`/`lead_gen_forms` arrays. On partial failures, returns success=false plus `errors[]` with per-container detail.
EXAMPLE (Lead Gen, LinkedIn-only, two containers):
create_native_structure_campaign(campaign_data={
"campaignType": "Lead Gen",
"name": "Q3_ABM_Native_2026",
"startDate": "2026-06-01",
"endDate": "2026-08-31",
"budgetGroup": "BoFu",
"linkedin": {
"containers": [
{"name": "IT Mgrs > Demo > Form",
"audience": "IT Managers - NA",
"ad_offers": [{"ad": 156502, "offer": 57538}]},
{"name": "CTOs > Demo > Form",
"audience": "CTOs - Tech",
"ad_offers": [{"ad": 156485, "offer": 57504}]}
]
}
})
EXAMPLE (one audience → 3 ads on the SAME offer, ONE container — the multi-ad ad-set):
create_native_structure_campaign(campaign_data={
"campaignType": "Brand Awareness",
"name": "Q3_3Ads_1Offer_2026",
"startDate": "2026-06-01",
"endDate": "2026-07-31",
"budgetGroup": "Brand",
"facebook": {
"containers": [
{"name": "ICP > 3 creatives > TOF offer",
"audience": "26-Q2-ICP-AllPersonas-NA",
"ad_offers": [
{"ad": 210801, "offer": 67269},
{"ad": 210802, "offer": 67269},
{"ad": 210803, "offer": 67269}
]}
]
}
})
EXAMPLE (Brand Awareness, Google + LinkedIn, with keywords):
create_native_structure_campaign(campaign_data={
"campaignType": "Brand Awareness",
"name": "Q3_Brand_Native_2026",
"startDate": "2026-06-01",
"endDate": "2026-07-31",
"budgetGroup": "Brand",
"linkedin": {
"containers": [
{"name": "Professionals A",
"audience": "Professional Audience",
"ad_offers": [{"ad": 173669, "offer": 57538}]}
]
},
"google": {
"containers": [
{"name": "CRM Search",
"audience": "Tech Decision Makers",
"ad_offers": [{"ad": 175260, "offer": 57600}],
"keywords": ["crm software", "lead gen tools"],
"negative_keyword_lists": ["Competitor Brands"]}
]
}
})
EXAMPLE (Brand Awareness, Google keyword-only — NO audience, pure non-brand search):
create_native_structure_campaign(campaign_data={
"campaignType": "Brand Awareness",
"name": "Q3_NonBrand_Search_2026",
"startDate": "2026-06-01",
"endDate": "2026-07-31",
"budgetGroup": "Search",
"google": {
"containers": [
{"name": "Incident Response",
"ad_offers": [{"ad": 210804, "offer": 67269}],
"keywords": ["incident response platform", "on-call alerting"],
"negative_keyword_lists": ["Competitor Brands"]}
# note: no "audience" key — keyword-targeted ad group
]
}
})
BEST PRACTICES:
1. Decide each container's audience and its `ad_offers` pairs up front — this tool will not invent pairings for you. Group several ads under one audience by adding pairs to `ad_offers`. On Google/Microsoft search, audience-less (keyword + ad_offers) containers are valid; pure non-brand search normally has no audience. On Reddit, targeting is the `target_group` (create_reddit_target_group) and `audience` is optional — an audience only exists on a channel the account has CONNECTED, so naming one on a disconnected Reddit loses the container.
2. Use descriptive container names — they show up in the platform UI as the ad-set/ad-group label.
3. For Google Ads, put related keywords in the same container (each container becomes one keyword group). Don't pile dozens of unrelated keywords into a single container.
4. Use Brand Awareness for any container that needs Landing Page offers — Lead Gen campaigns reject LP offers.
5. The campaign URL is returned in the response: https://platform.metadata.io/hub/advertise/campaigns/{campaign_id}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| campaign_data | Yes | Complete Channel First / Native Structure (N×N×N) campaign configuration. Each channel takes a `containers` list; each container groups one audience with an `ad_offers` list of {ad, offer} pairs. |