create_native_structure_campaign
Create a multi-channel campaign using the platform's "Channel First Structure" (N×N×N) — the UI also labels it "Native Structure" / "Platform-First Structure".
Writes campaigns
Creates or changes something in the account. Put it behind whatever review your agent uses.
What it does
Create 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.
════════════════════════════════════════════════════════════════════════════════
⚠️ TWO CAMPAIGN-CREATION TOOLS EXIST — READ BEFORE PICKING ONE ⚠️
════════════════════════════════════════════════════════════════════════════════
🔴 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.
⚠️ 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.)
⚠️ 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}Arguments
| Argument | Type | Notes | |
|---|---|---|---|
campaign_data |
object | required | 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. |
Request
curl
curl -s -X POST https://mcp-server.metadata.io/mcp \
-H "Authorization: $METADATA_PAT" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-11-25" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_native_structure_campaign","arguments":{"campaign_data":{}}}}'
Response
No recorded response. This tool writes to the account, so it is never executed to build these docs.
The request above is still the exact shape to send. Nothing on this page is invented.
Related
Other campaigns tools: