# Xylo MCP Tool Reference

> Every tool the Xylo MCP server (310 tools across 47 groups) exposes to AI agents for running Meta (Facebook & Instagram), Google, and TikTok ads — with the full description each agent sees.

Connect at https://xyloapi.dev/api/mcp (bundled) or the platform-scoped connectors /api/mcp/meta, /api/mcp/google, /api/mcp/tiktok. Setup guide: https://xyloapi.dev/docs — overview for AI crawlers: https://xyloapi.dev/llms.txt

## Meta — Accounts

### `meta_list_accounts`

List or look up Meta ad accounts connected to the Xylo organization. USE THIS ANY TIME the user refers to an account by BRAND NAME (e.g. "Getmo.us", "Acme", "our client", "for <brand>") — pass the brand as `query` to resolve it to an ad_account_id. This is the entry point whenever you need an ad_account_id but the user only gave a name. Also use to discover which ad accounts are available before querying campaigns, ads, insights, Facebook Pages, Instagram posts, comments, audiences, or creatives. Returns account ID, name, currency, timezone, status, spend cap, and total amount spent. Also returns default_page_id, default_instagram_id, and default_catalog_id when configured — use these as page_id / instagram_actor_id / product_catalog_id when creating ads (e.g. Advantage+ Catalog Ads). Each account also includes `warehouse_enabled` (boolean) and `last_synced_at`. The response meta includes the org `plan`. IMPORTANT: `warehouse_enabled=true` means the local data warehouse is populated for this account — only then should you use query_ad_warehouse, get_warehouse_schema, or meta_check_ad_integrity. When warehouse_enabled=false, use live Meta tools (meta_list_campaigns, meta_get_ad_insights, etc.). Optional query filters accounts whose name or id contains this substring (case-insensitive). Keywords: find account, resolve brand, look up account, account for <brand>, which account is <brand>, ad account id, act_ id, Meta account, Facebook ad account, Instagram ad account, page post, IG post, comments on latest post, brand name resolver, account discovery, warehouse enabled, sync status.

### `meta_connect_account`

Start the flow to connect a new Meta ad account. Returns a connect_url that the user must visit to authorize access via Meta OAuth. The URL expires in 30 minutes. Use this when the user wants to add a new ad account to Xylo.

### `meta_disconnect_account`

Disconnect (remove) a Meta ad account from the Xylo organization. This revokes Xylo's access to the account. The account can be reconnected later. WARNING: This action cannot be undone without re-authorizing.

### `meta_get_account_details`

Get full details for a Meta ad account, including status, billing info, spend cap, balance, currency, timezone, business info, capabilities, funding source, and more. Use this for a comprehensive view of an account beyond what list_accounts returns.

### `meta_get_account_activities`

Get the activity log for a Meta ad account. Returns a chronological list of events such as campaign creation, budget changes, status updates, and policy actions. Useful for auditing changes and understanding account history.

## Meta — Campaigns

### `meta_list_campaigns`

List Meta ad campaigns for the current ad account. Supports filtering by status (ACTIVE, PAUSED, DELETED, ARCHIVED), by campaign NAME (contains / does not contain, case-insensitive, multiple terms), and pagination. Returns campaign ID, name, status, delivery_status, objective, budgets, and schedule. Use this to get an overview of all campaigns, or to find campaigns whose name matches/excludes keywords (e.g. "cold", "hot", "Q3", "test"). RUN-STATE: read `delivery_status` (ACTIVE, PAUSED, COMPLETED, SCHEDULED, IN_PROCESS, WITH_ISSUES, ARCHIVED, DELETED) to tell whether a campaign is actually live — NOT raw `status`. Meta leaves a finished campaign at status=ACTIVE, so a completed campaign shows status=ACTIVE but delivery_status=COMPLETED. The `status` filter matches Meta's configured/effective status, so filtering status=ACTIVE still includes completed campaigns — check delivery_status to tell them apart. IMPORTANT: All budget values (daily_budget, lifetime_budget, budget_remaining) are returned in dollars, NOT cents. Response includes paging: { next_cursor, has_more }. Pass next_cursor as the "after" param to get the next page.

### `meta_get_campaign`

Get details of a specific Meta ad campaign by ID. Returns name, status, delivery_status, objective, daily/lifetime budget, budget remaining, spend today, and schedule dates. Read `delivery_status` (ACTIVE / PAUSED / COMPLETED / SCHEDULED / ...) — not raw `status` — to tell if the campaign is actually live: a finished campaign keeps status=ACTIVE but delivery_status=COMPLETED. All budget values are in dollars (already converted from cents).

### `meta_create_campaign`

Create a new Meta ad campaign. The campaign will default to PAUSED status for safety (it will not start spending immediately). You must specify a name and objective. Valid objectives: OUTCOME_AWARENESS, OUTCOME_ENGAGEMENT, OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_TRAFFIC, OUTCOME_APP_PROMOTION. IMPORTANT: When using campaign budget optimization (CBO — setting daily_budget or lifetime_budget on the campaign), bid_strategy MUST be set here on the campaign, NOT on the ad set. Always set bid_strategy to LOWEST_COST_WITHOUT_CAP unless the user specifically requests a bid cap or cost cap.

### `meta_update_campaign`

Update an existing Meta ad campaign. You can change the name, status, budgets, or schedule. Only the fields you provide will be updated.

### `meta_pause_campaign`

Pause an active Meta ad campaign. This immediately stops ad delivery and spend. The campaign can be resumed later with resume_campaign.

### `meta_resume_campaign`

Resume a paused Meta ad campaign. This reactivates ad delivery and spending. Make sure the campaign has ad sets and ads before resuming.

### `meta_delete_campaign`

Delete a Meta ad campaign. This sets the campaign status to DELETED. All ad sets and ads under this campaign will also stop delivering. WARNING: This cannot be easily undone.

### `meta_duplicate_campaign`

Duplicate a Meta campaign. Defaults to shallow (campaign shell only). Set include_children=true for a deep copy that also clones every non-archived ad set AND its ads (mirroring what Ads Manager does). Set include_ads=false with include_children=true to clone ad sets only. All new objects are created PAUSED. Returns an id map { campaign: {from, to}, adsets: [...], ads: [...], errors: [...] } so you can verify the copy.

### `meta_bulk_update_campaigns`

Update many campaigns at once — by explicit ID list or by filter. Two modes: (1) EXPLICIT: pass `campaign_updates` with per-campaign IDs and their individual update payloads. (2) FILTER: pass `filter` (e.g. {name_contains, status, objective}) plus `updates` — the same uniform update is applied to every matched campaign. COMMON USE CASES: pause all campaigns whose name contains "V1" → filter={name_contains:"V1"}, updates={status:"PAUSED"}. Bump every active campaign's daily budget to $100 → filter={status:["ACTIVE"]}, updates={daily_budget:100}. EFFICIENCY: filter mode does ONE Meta read (id-only field projection) then chunks writes into 50-op Meta Batch calls dispatched 3 in parallel, with omit_response_on_success=true to cut payload. For 200 campaigns: 1 read + 4 batch writes = 5 Meta HTTP calls total. DRY RUN: pass dry_run=true to preview which campaigns the filter matches without writing anything. LIMITS: default 20 campaigns per call, max 200. PARTIAL FAILURES: response includes {succeeded, succeeded_ids, retried_ids, failed:[{id,error_code,error_message,transient}]}. Always inspect failed[]. TRANSIENT FAILURES ARE AUTO-RETRIED: a write that times out inside Meta`s batch is automatically retried once after a settle (field updates are idempotent, so this is safe); ids that succeeded on the retry appear in BOTH succeeded_ids and retried_ids and need no further action. Anything left in failed[] survived both attempts — safe to re-run the call for just those ids (re-applying the same update twice is harmless).

## Meta — Ad Sets

### `meta_list_adsets`

List ad sets within a specific campaign. Ad sets control targeting, budget, schedule, and optimization for a group of ads. Returns ad set ID, name, status, delivery_status, budgets, billing event, optimization goal, and schedule. Supports filtering by status and by ad set NAME (contains / does not contain, case-insensitive, multiple terms OR). RUN-STATE: read `delivery_status` (ACTIVE / PAUSED / COMPLETED / SCHEDULED / ...) — not raw `status` — to tell if an ad set is actually live; a finished ad set keeps status=ACTIVE but delivery_status=COMPLETED. TARGETING IS OPT-IN: the heavy `targeting` spec (which can carry thousands of ZIP codes) is NOT returned by default — add "targeting" to `fields` to include it. All budget values are in dollars, NOT cents. Response includes paging: { next_cursor, has_more }. Pass next_cursor as the "after" param to get the next page.

### `meta_get_adset`

Get details of a specific ad set by ID. Returns name, status, delivery_status, campaign ID, budgets, billing event, optimization goal, bid strategy, and schedule. Read `delivery_status` (ACTIVE / PAUSED / COMPLETED / SCHEDULED / ...) — not raw `status` — to tell if the ad set is actually live; a finished ad set keeps status=ACTIVE but delivery_status=COMPLETED. TARGETING IS OPT-IN: the full targeting spec (geo / ZIPs / interests — potentially huge) is NOT returned by default. To get it, pass fields:["targeting"] (alongside any other fields you want). All budget values are in dollars, NOT cents.

### `meta_create_adset`

Create a new ad set within a campaign. An ad set defines the audience targeting, budget, schedule, and optimization goal. Created in PAUSED status by default. Required fields: name, billing_event (IMPRESSIONS, LINK_CLICKS, etc.), optimization_goal (REACH, LINK_CLICKS, OFFSITE_CONVERSIONS, etc.), and targeting. IMPORTANT: For conversion campaigns (OUTCOME_SALES objective, OFFSITE_CONVERSIONS optimization), you MUST include promoted_object with pixel_id and custom_event_type (e.g., PURCHASE). Use meta_list_pixels to find the account's pixel ID. Always set bid_strategy to LOWEST_COST_WITHOUT_CAP unless told otherwise.

### `meta_update_adset`

Update an existing ad set. You can change name, status, budgets, targeting, bid strategy, or schedule. Only the fields you provide will be updated.

### `meta_pause_adset`

Pause an active ad set. This stops all ads in the ad set from delivering. The ad set can be resumed later with resume_adset.

### `meta_resume_adset`

Resume a paused ad set. This reactivates ad delivery for all active ads in the ad set.

### `meta_delete_adset`

Delete an ad set. This sets the ad set status to DELETED and stops all ads within it. WARNING: This cannot be easily undone.

### `meta_duplicate_adset`

Duplicate a Meta ad set. Defaults to shallow (ad-set shell only, no ads). Set include_children=true for a deep copy that also clones every non-archived ad under it. Set include_ads=false with include_children=true to clone the shell only (equivalent to default). All new objects are created PAUSED. Optionally pass target_campaign_id to copy into a different campaign. Returns { adset: {from, to}, ads: [...], errors: [...] }.

### `meta_bulk_update_adsets`

Update many ad sets at once — by explicit ID list or by filter. Two modes: (1) EXPLICIT: pass `adset_updates` with per-adset IDs + payloads. (2) FILTER: pass `filter` (e.g. {campaign_id, name_contains, status}) + uniform `updates`. COMMON USE CASES: pause every ad set in a campaign → filter={campaign_id:"123"}, updates={status:"PAUSED"}. Rename all V1 ad sets → filter={name_contains:"V1"}, updates={name:"..."}. IMMUTABLE FIELDS: is_dynamic_creative and destination_type cannot be changed after creation — Meta returns 200 but silently ignores them. They are not exposed by this tool. EFFICIENCY: 1 Meta read for the filter (id-only field projection) + parallel 50-op batch writes. For 200 ad sets: 1 read + 4 batch writes = 5 HTTP calls. DRY RUN, LIMITS, PARTIAL FAILURES: same shape as meta_bulk_update_campaigns.

## Meta — Ads

### `meta_list_ads`

List ads within a specific ad set. Returns ad ID, name, status, delivery_status, creative details, and timestamps. Use this to see all ads running under an ad set. Each ad includes creative_format — a derived media classification (image, video, carousel, mixed, catalog, or unknown) — so you can segment ads by creative type (e.g. analyze only image ads vs video ads) without inspecting each creative individually. RUN-STATE: read `delivery_status` (ACTIVE / PAUSED / COMPLETED / SCHEDULED / ...) — not raw `status` — to tell if an ad is actually live; an ad under a finished campaign/ad set keeps status=ACTIVE but delivery_status=COMPLETED. LEAN READS: to skip the large creative blob and just check run-state, pass fields:["delivery_status"] (or whatever you need). Supports filtering by status and by ad NAME (contains / does not contain, case-insensitive, multiple terms OR). Response includes paging: { next_cursor, has_more }. Pass next_cursor as the "after" param to get the next page.

### `meta_get_ad`

Get details of a specific ad by ID. Returns name, status, ad set ID, creative details (headline, body, image, link, CTA), creative_format (derived media classification: image, video, carousel, mixed, catalog, or unknown), and timestamps. Placement-aware fields: creative.story_image_hash / story_video_id are set when the ad has a dedicated 9:16 story/reels asset (Placement Asset Customization). creative.creative_id is exposed so callers can drill into the underlying AdCreative via meta_get_creative when they need the raw asset_feed_spec or asset_customization_rules. Use these fields when recreating an ad in another ad set to preserve story coverage. MULTI-MEDIA ADS: creative.media_sourcing_spec lists EVERY uploaded asset (Meta "multi-media": up to 10 images+videos in one ad), each flagged is_primary — so you see all media, not just the primary in object_story_spec. creative_format reads "mixed" when it combines images + videos. For per-media download URLs use meta_get_ad_media; for per-media performance + analysis use meta_analyze_ad_creative with media_index. creative.branded_content is set when the ad is a partnership / branded content ad. When recreating a partnership ad with new text or media, pass this object through to meta_create_ad / meta_bulk_create_ads — otherwise the partner tag is dropped on the new ad. CLONE AN AD EXACTLY (swap only the media): the default response does NOT include the Advantage+ enhancement enrollment, the pinned catalog product set, or the site links — so cloning from it silently drops that config and the new ads fall back to the account defaults. To reproduce an ad faithfully, call with detail="full": the returned creative then also carries creative_features (the enrollment matrix as booleans), product_set_id, product_extensions, creative_features.site_links (the FULL list — pass all of them to clone exactly, not just the first few) — all shaped to drop STRAIGHT BACK into meta_bulk_create_ads. Read once with detail="full", swap image_hash / video_id (or story_image_hash) per new ad, and pass the rest of the creative through unchanged. detail="full" ALSO returns creative.advantage_plus_spec — the COMPLETE raw enrollment matrix (degrees_of_freedom_spec ~75 features, creative_sourcing_spec, destination_spec) exactly as Meta stores it. creative_features normalizes only the ~22 features the create tools can set; use advantage_plus_spec to SEE and DIFF everything (e.g. verify a clone matches the parent). It is READ-ONLY — set enhancements via creative_features / product_set_id / site_links, not by echoing it back. FORMAT DISPLAY OPTIONS: the Ads Manager "Format display options" panel (Single media / Carousel / Collection) maps to Meta`s format_transformation_spec. Read behavior (verified 2026-06-13): it comes back here ONLY for ads CREATED VIA THE API, and only the non-default entries (e.g. da_collection) — base formats single_media/carousel are implicit and never echoed. Ads configured in the Ads Manager UI do NOT expose it at all (returns nothing), so you can`t auto-clone a UI-built ad`s format options — pass format_transformation_spec EXPLICITLY in that case. Catalog (product_extensions) ads get it defaulted on automatically by the create tools.

### `meta_create_ad`

Create a SINGLE ad within an ad set. WHEN TO USE THIS vs the bulk tools: (1) one or two ads only — bulk overhead isn`t worth it. (2) the creative needs Meta to upload from a public image_url at creation time — bulk requires pre-uploaded image_hash / video_id (push assets via the Xylo dashboard first). For 3+ ads with already-uploaded assets, prefer meta_bulk_create_ads — it supports the SAME creative shapes (5x text rotation, feed/story placement customization, carousels, Flexible Ad Format, and Multi-Media) and runs ~10× faster. For fanning a single existing ad`s creative across many ad sets, use meta_duplicate_ads instead. Three creative modes: (1) PROMOTE INSTAGRAM POST: set source_instagram_media_id + instagram_actor_id + page_id. Most campaigns ALSO require call_to_action + link — first check existing ads in the ad set (meta_list_ads) to determine the correct CTA pattern and destination URL for that campaign. (2) PROMOTE FACEBOOK POST: set object_story_id + page_id. (3) NEW CREATIVE: set page_id + headline + body + link + call_to_action + image_hash (or image_url or video_id). IMPORTANT: ALWAYS include instagram_actor_id (from meta_list_connected_instagram_accounts or default_instagram_id) so the ad shows on Instagram placements. Without it, the ad only runs on Facebook. For placement-specific creatives (different images for feed vs story), set story_image_hash alongside image_hash. MULTIPLE TEXT VARIATIONS: You can provide up to 5 headlines and 5 primary_texts. Meta will rotate through them to find the best performer. Use this by default when creating ads — generate 5 headline variations and 5 body text variations for better performance. TEXT VARIATIONS + PLACEMENT IMAGES: When you provide both story_image_hash and primary_texts/headlines, the API builds an asset_feed_spec creative with labeled asset_customization_rules — images are labeled per placement (feed vs story) and texts share a label across all rules so every placement gets the same text pool. Always try this combo first — do NOT assume it won't work based on campaign objective. PRODUCT EXTENSIONS (Advantage+ "Add catalog items"): only enable creative.product_extensions when the user explicitly asks for it — never by default. Eligible only on SALES/TRAFFIC campaigns with single-image or single-video formats. Optionally pair with product_set_id to pin a set (look up via meta_list_product_sets). Enabling it auto-turns-on the Format display options (single media + carousel + collection) the catalog needs to render; pass your own format_transformation_spec to override. THREE WAYS TO PUT MULTIPLE MEDIA IN ONE AD — pick by intent: MULTI-MEDIA (format="multi_media"): up to 10 images+videos in one ad; Meta serves the BEST SINGLE asset per person/placement (it stays a single-media ad, just personalized). Works on ANY campaign objective. Pass your media in multi_media_images (image hashes) and/or multi_media_videos ([{video_id}]), plus primary_texts + headlines for copy and creative.page_id + link + call_to_action (+ instagram_actor_id). One asset becomes the primary (defaults to the first image; set creative.video_id to force a video-primary ad). For per-media text/destination/placement/crop control, pass media_sourcing_spec directly. Advantage+ enhancements work too — pass creative.creative_features (biz_ai, enhance_cta, inline_comment, site_extensions + site_links, text_translation); cloning a multi-media ad via meta_get_ad detail="full" reproduces them. (Image/video-pixel-specific and catalog features do not apply to multi-media.) FLEXIBLE AD FORMAT (format="flexible"): up to 10 media + texts that Meta MIXES and can render as single image, video, OR carousel per placement — the Dynamic Creative replacement. Pass ALL media in flexible_images and/or flexible_videos (do NOT put only one hash in creative.image_hash — that yields a 1-image ad), plus primary_texts + headlines and creative.page_id + link + call_to_action. ONLY OUTCOME_SALES / OUTCOME_APP_PROMOTION campaigns support it. For multi-group control use creative_asset_groups_spec. WHICH? multi_media = best single asset, any objective; flexible = recombine (incl. carousel), SALES/APP only. DYNAMIC CREATIVE (DCO) IS DEPRECATED FOR CREATION — Flexible replaces it; format="dynamic" / asset_feed_spec are rejected. (Reading & analyzing existing DCO ads still works via meta_get_ad / meta_analyze_ad_creative.) Created in PAUSED status by default.

### `meta_update_ad`

Update an existing ad. You can change the name, status, or creative fields. Only the fields you provide will be updated. FORMAT-PRESERVING: this tool will not change the creative shape. A single-text ad stays single-text; a rotation/asset_feed_spec ad stays a rotation ad; a Dynamic Creative ad stays Dynamic Creative. NARROW UPDATES (safe on asset_feed_spec creatives): pass `link` to change only the destination URL, or `primary_texts` / `headlines` to swap text rotations. When any of these three are provided alone (no other creative fields), the existing asset_feed_spec is preserved verbatim — the image, image_hash, CTA, and placement customization rules all stay intact, and only the specified field is swapped. CONSTRAINT: passing `primary_texts` / `headlines` arrays on an ad whose creative is NOT asset_feed_spec (a single-text ad) is REJECTED with an error — adding rotation arrays would convert the ad into Dynamic Creative. To add text rotation, create a NEW ad via meta_create_ad (or meta_bulk_create_ads) with the arrays at creation time. To edit text on a single-text ad, use the singular `body` / `headline` fields inside `creative` instead. EXISTING-POST ADS CANNOT BE UPDATED: ads that promote an existing FB/IG post (object_story_id or source_instagram_media_id creatives, i.e. boosted posts) get their text, media, and link from the post itself — `primary_texts` / `headlines` / `link` swaps are REJECTED on them, even when meta_get_ad shows a bodies array. FLEXIBLE ADS CANNOT BE TEXT-UPDATED: Flexible Ad Format ads keep their copy in creative_asset_groups_spec on the ad, not the creative — text swaps are REJECTED on them too; recreate with meta_create_ad format="flexible" for new copy. CATALOG ADS (DPA / Advantage+ Catalog): supported, with constraints. Their copy is the catalog template`s message/headline — SINGLE strings (template params like {{product.name}} allowed), so pass one-element primary_texts / headlines arrays; multi-element arrays are rejected. CONSENT GATE: when meta_get_ad shows multiple bodies on a catalog ad, those are Ads Manager AI-generated text options which the API cannot set or preserve — the first update attempt is REJECTED with an explanation. Relay it to the user; if they consent to permanently losing the AI options, retry with confirm_drop_ai_text_options=true. Skip existing-post and flexible ads when bulk-editing copy across an ad set; only `name`, `status`, and schedule changes work on them.

### `meta_pause_ad`

Pause an active ad. This stops the ad from being delivered. Can be resumed later with resume_ad.

### `meta_resume_ad`

Resume a paused ad. This reactivates the ad for delivery.

### `meta_upload_ad_video`

Upload a SINGLE ad-hoc video to Meta from a public URL. Returns video_id. IMPORTANT: If the user uploaded videos via the Xylo dashboard, OR you have multiple videos to send to Meta, use meta_finalize_media instead — it bulk-pushes all staged assets in one call. Do NOT loop this tool over a list of videos; that is slow and wastes rate limit. Use this tool only for one-off uploads from an external URL. After uploading, use meta_check_video_status to poll until the video is ready before using it in ads.

### `meta_check_video_status`

Check the processing status of an uploaded ad video. Status values: "ready" (can be used in ads), "processing" (still encoding), "error" (upload failed). Poll every 3-5 seconds until "ready".

### `meta_list_ad_videos`

List videos uploaded to the ad account for use in creatives. Returns video IDs, names, and processing status.

### `meta_create_catalog_ad`

Create a true Advantage+ Catalog Ad (Dynamic Product Ad / DPA) — the entire creative is rendered from a Meta product catalog at delivery time, no static image or video. Each impression shows cards generated per product (image, name, price, link) using your template strings with {{product.*}} placeholders. This matches the Ads Manager "Media setup: Advantage+ catalog ads" radio option. WORKS IN TWO CONTEXTS: (A) AS ONE OF MANY ADS IN A REGULAR SALES CAMPAIGN. Drop a catalog ad alongside manual-upload image/video/carousel ads in the SAME ad set under an OUTCOME_SALES (or OUTCOME_TRAFFIC) campaign. No special ad set config needed — the product_set_id rides on the creative. This is the common case when the user says "add a catalog ad to this ad set". (B) DEDICATED CATALOG-SALES AD SET. The ad set itself is anchored to a product set via promoted_object.product_set_id and every ad in it is a catalog ad. Either context works the same with this tool — just pass product_set_id at the ad/creative level here. WHEN NOT TO USE: (1) static creative + a small product carousel pinned underneath → use meta_create_ad with `creative.product_extensions: true` and `creative.product_set_id` instead. (2) testing multiple static images you uploaded → use meta_create_ad with format="flexible" (SALES/APP campaigns) or format="multi_media" (any objective). (3) a fixed carousel of specific products you hand-pick → use meta_create_ad with `child_attachments`. PREREQUISITES: the ad account must have a connected catalog with the named product set. Use meta_list_catalogs and meta_list_product_sets to discover IDs. The campaign objective should be OUTCOME_SALES (modern) or one of CONVERSIONS / LINK_CLICKS / APP_INSTALLS / PRODUCT_CATALOG_SALES (legacy). TEMPLATE PLACEHOLDERS: standard tokens are {{product.name}}, {{product.price}}, {{product.description}}, {{product.brand}}, {{product.current_price}}. Liquid filters supported, e.g. {{product.name | titleize}}. Per-product link is read from the catalog feed automatically; the `link` field here is required as a fallback / common destination. Created in PAUSED status by default.

### `meta_ad_preview`

Render a visual preview of an ad in a given placement format. Two modes: pass `ad_id` to preview an EXISTING ad, or omit ad_id and pass a `creative` spec to preview a DRAFT before publishing. Returns an HTML iframe snippet that renders the ad in a browser — AI agents cannot render this HTML directly, so use it only to hand a preview link to a human. For understanding an existing ad's content (text, headline, link, CTA), use meta_get_ad instead.

### `meta_list_partnership_partners`

List the partnership / branded_content partners this ad account has used in recent ads. Returns each unique partner once with their ID (the value that goes into creative.branded_content.partners[].id on meta_create_ad / meta_bulk_create_ads), display name, Instagram handle (when applicable), and how many recent ads tagged them. USE THIS BEFORE meta_create_ad WHEN: the user asks to make a partnership ad and refers to a partner by name or handle ("a partnership ad with @creatorbob", "tag XYZ Studio") — call this first to look up the ID without forcing the user to paste a numeric Facebook Page / Instagram Business Account ID by hand. The most-used partners are sorted first. If the partner the user mentioned does not appear here, they likely have not approved this brand for partnership ads yet (approval happens outside the API in Meta Business Suite); surface that to the user. Sorted by ad_count descending then by last_used_at descending. Results are cached for 60s.

### `meta_delete_ad`

Delete an ad. This sets the ad status to DELETED and stops delivery. WARNING: This cannot be easily undone.

### `meta_get_ad_media`

Fetch the creative media URLs (image and/or video) for a Meta ad. View ad image, see ad creative, look at the ad, describe the image in this ad, download ad creative asset, download all carousel images. Works for all Meta ad formats including single image/video, carousel ads, and modern asset_feed_spec (PAC / Dynamic Creative) ads. Resolves image hashes to full-size CDN URLs via the ad account in a single batched lookup. Returns top-level fields: format ("single" | "carousel" | "multi_media"), image_url, image_hash, video_id, video_source_url, video_source_kind, video_permalink_url, video_thumbnail_url, video_source_reason. CAROUSEL ADS: When the creative is a carousel (link_data.child_attachments has 2+ cards), the response also includes a carousel_cards array — one entry per card with link, name, description, image_hash, image_url, and (for video carousel cards) video_id + video_source_url. The top-level image_url/image_hash mirror the FIRST card for backward compatibility — for the full carousel use carousel_cards. format is "carousel" when this array is populated, "single" otherwise. MULTI-MEDIA ADS (Meta "multi-media": up to 10 images+videos in ONE ad, stored in media_sourcing_spec): format is "multi_media" and the response includes a media[] array (+ media_total) — one entry per uploaded asset with index, asset_type, is_primary, and the resolved video_source_url / image_url. The top-level fields mirror the PRIMARY asset; use media[] to see/download ALL of them. VIDEO RESOLUTION: Meta only returns a signed download URL (video_source_url, kind "source") for videos uploaded directly to the ad account`s library via /act_.../advideos. Page-post videos, shared videos, and creatives copied from another ad account return null for video_source_url. In that case, video_permalink_url points to the public Facebook page where the video can be viewed, and video_source_reason explains why download isn`t available — tell the user to download from Ads Manager / Meta Business Suite for those creatives. Video source URLs are short-lived — download immediately, do not store.

### `meta_duplicate_ads`

Bulk-duplicate EXISTING ads: copy N source ads into M target ad sets in a single call. WHEN TO USE THIS: you already have working ads (with creatives Meta has approved) and want to fan them out across more ad sets — same creative, more placements / targeting / campaigns. Equivalent to selecting multiple ads in Ads Manager and duplicating them into multiple ad sets at once. Creates source_ad_ids.length × target_adset_ids.length new ads (max 500 per call). Uses Meta`s native /{ad-id}/copies endpoint under the hood — the same path Ads Manager uses. Preserves every ad-level field automatically: partnership / branded_content (partner page tag), tracking_specs, conversion_specs, creative reference, and anything else Meta carries over. SILENT FALLBACK: when Meta refuses to /copies a source (most commonly because the source creative still carries the deprecated `standard_enhancements` Advantage+ bundle that Meta dropped in MAPI v22 / Jan 2025), the duplicate transparently falls back to manual rebuild — fetches the source creative, recreates it without `degrees_of_freedom_spec` (account-level Advantage+ defaults take over), and creates the new ad. The new ad appears with `rebuilt: true` in the response so you can mention to the user that the source was a legacy creative; partnership and tracking specs are still preserved through the rebuild path. WHEN NOT TO USE: (a) If the ads are net-new (you have image_hash / video_id / IG post id but no existing ad referencing that creative yet), use meta_bulk_create_ads instead — it builds new creatives in bulk and supports the same shapes (5x text rotation, feed/story split, carousels, etc). (b) If you want to recreate the ad with NEW text / link / image (the "remake" workflow), don`t use this — use meta_get_ad to read the source (including creative.branded_content), then meta_bulk_create_ads with your overrides and pass branded_content through verbatim. Uses Meta`s Batch API under the hood (50 ops/batch, parallel server-side), so this is dramatically faster than calling meta_create_ad in a loop. RESPONSE: { summary: {requested, created, skipped_existing, failed}, created: [...], skipped_existing: [...], errors: [...] }. summary is the source of truth for counts — report it to the user. Partial failures are expected; inspect errors[]. New ads are PAUSED by default. SELF-HEALING / TIMEOUTS: /copies ops that time out inside Meta`s batch are automatically verified against the target ad set and retried; such copies appear in created[] flagged verified_after_timeout or retried — they are REAL, do not re-duplicate them. The call is also IDEMPOTENT: re-running the exact same call skips source×target pairs whose copy already exists in the target ad set (matched by name, the source ad itself excluded) and reports them in skipped_existing — so after a client-side timeout, just call it again with the same arguments. To INTENTIONALLY create a second copy of the same ad in the same ad set, pass a name_template so the new copy gets a distinct name. Use cases: scaling a winning ad across a campaign`s ad sets, copying ads from one campaign into another after building the ad-set shells, duplicating into a new audience test.

### `meta_bulk_create_ads`

Bulk-create NET-NEW ads — many different ads in a single Meta Batch call. Different creatives, different ad sets, all submitted together. WHEN TO USE THIS: launching 3+ new ads at once. Most common cases: "12 ads across 5 ad sets," "4 creative variants in 3 different audiences," "10 different IG posts as ads." Each spec is independent and gets its own creative. Up to 500 ads per call. WHEN NOT TO USE: (a) Same creative fanning out across many ad sets → meta_duplicate_ads is faster (no creative recreation). (b) Truly one-off ads (1-2 ads) → meta_create_ad is fine. (c) Net-new creative that requires uploading from image_url at creation time → meta_create_ad (it can upload). For bulk, push assets via the Xylo dashboard first. CREATIVE SHAPE PARITY: bulk supports the SAME creative shapes as meta_create_ad — single image, single video, IG post promotion, FB post promotion, 5x headline / 5x primary_text rotation, feed+story placement customization (story_image_hash + image_hash, or story_video_id + video_id), carousels (child_attachments), creative_asset_groups_spec (Flexible Ad Format), and media_sourcing_spec (Multi-Media — up to 10 images+videos in one ad). The only thing it does NOT do is upload assets from URL. DYNAMIC CREATIVE (DCO) CREATION IS DEPRECATED — passing asset_feed_spec on a spec is rejected; use creative_asset_groups_spec (Flexible) instead. PRE-UPLOAD ASSETS: if the user has any new image/video that is not yet on Meta, tell them to open the Xylo dashboard, go to Media, and push the assets to Meta BEFORE calling this tool. Then they come back with the resulting image_hash and video_id values. (For existing IG/FB posts, just use source_instagram_media_id / object_story_id — no upload needed.) RECOMMENDED DEFAULT SHAPE: single-image-or-video ad with story_image_hash (or story_video_id) + bodies[5] + headlines[5] + instagram_actor_id. This is the same pattern Ads Manager emits for a modern PAC ad and is what should be generated unless the user explicitly asks for something else. Uses Meta`s Batch API with JSONPath result references — creative POST → ad POST chained server-side, 50 ops/batch (≈25 paired ads), parallel within each batch. ~10× faster than calling meta_create_ad in a loop. RESPONSE: { summary: {requested, created, skipped_existing, failed}, created: [{spec_index, new_ad_id, creative_id}], skipped_existing: [{spec_index, existing_ad_id, name, adset_id}], errors: [{spec_index, stage, message}] }. summary is the source of truth for counts — report it to the user. Partial failures are expected; always inspect errors[]. New ads are PAUSED by default. SELF-HEALING / TIMEOUTS: ops that time out inside Meta`s batch are automatically verified against the target ad set and retried, then reported in created[] with verified_after_timeout or retried flags — those ads are REAL, do not recreate them. The call is also IDEMPOTENT BY AD NAME: if the tool call itself times out client-side, simply CALL IT AGAIN WITH THE EXACT SAME SPECS — ads that already exist are skipped (reported in skipped_existing with their ids) and only the missing ones are created, so a re-run always yields the full picture of what exists. Never rename specs when retrying after a timeout (renaming defeats the duplicate protection). NAME UNIQUENESS: ad names must be unique within an ad set — a spec whose name already exists in its target ad set is skipped, and two specs with the same name + adset_id in one call are rejected.

### `meta_bulk_update_ads`

Update many ads at once — by explicit ID list or by filter. Two modes: (1) EXPLICIT: `ad_updates` with per-ad IDs + payloads. (2) FILTER: `filter` (e.g. {adset_id, name_contains, status}) + uniform `updates`. COMMON USE CASES: pause every ad in an ad set → filter={adset_id:"123"}, updates={status:"PAUSED"}. Pause all V1 ads → filter={name_contains:"V1"}, updates={status:"PAUSED"}. Swap creatives across many ads → ad_updates=[{ad_id, creative_id}, ...]. EFFICIENCY: 1 Meta read for filter resolution + parallel 50-op batch writes with omit_response_on_success. For 200 ads: 1 read + 4 batch writes = 5 HTTP calls. DRY RUN, LIMITS, PARTIAL FAILURES: same shape as meta_bulk_update_campaigns.

## Meta — Insights

### `meta_get_campaign_insights`

Get performance insights at the campaign level. Returns spend, impressions, Clicks (all), Link clicks, CTR, CPC (all), CPM, reach, frequency, purchases, leads, cost per purchase, cost per lead, and ROAS. The "clicks" field is Clicks (all) — includes link clicks, post reactions, comments, shares, and other interactions. The "link_clicks" field is Link clicks only — actual clicks to the destination URL. Can filter by a specific campaign ID (campaign_id) or get all campaigns. To COMPARE specific campaigns side by side, pass campaign_ids (an array of 2+ IDs) — each campaign's totals are returned together under a "comparison" key (decide which to scale, pause, or optimize). Defaults to the last 7 days. Also filterable by campaign NAME (name_contains / name_not_contains — case-insensitive, multiple terms OR) and by delivery (had_delivery returns only campaigns that spent/delivered in the window even if paused now; min_spend sets a floor). Top-level standard event counts ("purchases", "leads", "add_to_cart", "view_content", "initiate_checkout", "complete_registration", "search") are DEDUPED to match Ads Manager — they prefer the standard event count and fall back to MAX of the source-specific copies (Pixel vs onsite). DO NOT sum overlapping action_types (e.g. `purchase` + `offsite_conversion.fb_pixel_purchase`, or `lead` + `offsite_conversion.fb_pixel_lead` + `onsite_web_lead`) from the raw "actions" array — they double-count the same conversion when Pixel and Meta-hosted forms/Shop both fire for it. Use the top-level field. The raw "actions" array includes ALL action types for breakdowns by attribution source if needed. ROAS = purchase_value / spend. If the date range includes today, data for today is partial. Campaign costs are driven by the auction: Total Value = Bid × Estimated Action Rate + Ad Quality. Higher CPM/CPC with strong ROAS is expected — Meta optimizes for marginal efficiency (cost of next conversion), not average. Don't manually reallocate budget away from high-CPM campaigns that have strong ROAS. For deeper context on auction mechanics and pacing, call meta_ads_knowledge.

### `meta_get_adset_insights`

Get performance insights at the ad set level. Returns metrics broken down by ad set: spend, impressions, Clicks (all), Link clicks, CTR, CPC (all), CPM, reach, purchases, leads, and ROAS. The "clicks" field is Clicks (all); "link_clicks" is Link clicks to the destination URL. Can filter by campaign_id or adset_id. Defaults to the last 7 days. Also filterable by ad set NAME (name_contains / name_not_contains — case-insensitive, multiple terms OR) and by delivery (had_delivery returns only ad sets that spent/delivered in the window even if paused now; min_spend sets a floor). Top-level standard event counts ("purchases", "leads", "add_to_cart", "view_content", "initiate_checkout", "complete_registration", "search") are DEDUPED to match Ads Manager. DO NOT sum overlapping action_types (e.g. Pixel + onsite copies of the same event) from the raw "actions" array — they double-count. Use the top-level fields. ROAS = purchase_value / spend. If the date range includes today, data for today is partial. Ad set performance naturally fluctuates. Daily CPA variation of 20-30% is normal due to pacing and auction dynamics — evaluate over 7+ day windows. If an ad set is in learning phase (~50 optimization events needed), performance is volatile and not indicative of long-term results. Check ad set delivery status via meta_get_adset before judging performance. For deeper analysis of Meta ads mechanics, call meta_ads_knowledge.

### `meta_get_ad_insights`

Get performance insights at the individual ad level. Returns detailed metrics for each ad: spend, impressions, Clicks (all), Link clicks, CTR, CPC (all), CPM, reach, purchases, leads, and ROAS. The "clicks" field is Clicks (all); "link_clicks" is Link clicks to the destination URL. Can filter by campaign_id, adset_id, or ad_id. Defaults to the last 7 days. Also filterable by ad NAME (name_contains / name_not_contains — case-insensitive, multiple terms OR) and by delivery (had_delivery returns only ads that spent/delivered in the window even if paused now; min_spend sets a floor). Top-level standard event counts ("purchases", "leads", "add_to_cart", "view_content", "initiate_checkout", "complete_registration", "search") are DEDUPED to match Ads Manager. DO NOT sum overlapping action_types (e.g. Pixel + onsite copies of the same event) from the raw "actions" array — they double-count. Use the top-level fields. ROAS = purchase_value / spend. If the date range includes today, data for today is partial. Ad relevance diagnostics (quality_ranking, engagement_rate_ranking, conversion_rate_ranking, for ads with 500+ impressions) are returned when you pull a SINGLE ad (pass ad_id) or request them via `fields` — broad multi-ad pulls omit them by default so the query stays light and does not trip Meta's "reduce the amount of data" limit. To diagnose one ad, call this with its ad_id. These diagnose WHY an ad underperforms but are not auction inputs. Low quality → improve creative. Low engagement → test new hooks. Low conversion → check landing page. All low → audience-creative mismatch. For the full diagnostic framework, call meta_ads_knowledge.

### `meta_get_account_insights`

Get account-level performance insights. Returns aggregated metrics across all campaigns: spend, impressions, Clicks (all), Link clicks, CTR, CPC (all), CPM, reach, purchases, leads, and ROAS. The "clicks" field is Clicks (all); "link_clicks" is Link clicks to the destination URL. Useful for a high-level overview of ad account performance, including TOTAL ACCOUNT SPEND for a date range (account spend, total spend, how much did I spend, ad account spend). Top-level standard event counts ("purchases", "leads", "add_to_cart", "view_content", "initiate_checkout", "complete_registration", "search") are DEDUPED to match Ads Manager. DO NOT sum overlapping action_types (e.g. Pixel + onsite copies of the same event) from the raw "actions" array — they double-count. Use the top-level fields. ROAS = purchase_value / spend.

### `meta_get_top_performers`

Get the top-performing campaigns, ad sets, or ads ranked by a chosen metric. Fetches insights at the specified level and sorts by the given metric (spend, roas, purchases, leads, clicks, ctr, impressions, cost_per_purchase, cost_per_lead). Returns the top N results. Responses are compact by default — the raw `actions[]` / `action_values[]` / `cost_per_action_type[]` arrays are stripped because the deduped top-level metrics (purchases, purchase_value, roas, etc.) are the canonical interface and the raw arrays can blow the response size past the MCP token cap. Pass compact=false if you need the raw arrays (e.g. for an event type that doesn't have a top-level shortcut).

### `meta_ads_performance_by_copy`

Break down Meta ad performance by primary text (body copy) and headline variants — the best tool for analyzing which ad copy, messaging, hooks, headlines, or text variations are working. Works for ANY ad whose creative has multiple text options via asset_feed_spec — this includes Dynamic Creative (DCO), Advantage+ creative, Placement Asset Customization (PAC), and standard non-DCO ads that ship with text rotation. Up to 5 primary texts and 5 headlines per ad. Returns per-variant spend, impressions, clicks, CTR, CPM, CPC, purchases, ROAS, and leads using Meta's body_asset and title_asset insights breakdowns. Every row also includes "optimization_result" — the count of the event each ad set is actually optimizing for (app installs for app campaigns, leads for lead campaigns, purchases for sales campaigns, add-to-carts when that's the goal, etc.), with "optimization_result_metric" naming which event it counts. The summary lists the optimization_goals and optimization_result_metrics across the analyzed ad sets. For plain single-creative ads (one body + one headline via object_story_spec), attributes the ad's total metrics to its single text — no per-variant breakdown exists because there's only one variant. Each ad's creative_type is tagged "multi_variant" or "single" so callers can tell them apart. Use this when the user asks: "which copy is performing best", "break down performance by headline", "compare primary text variants", "which ad text has the highest CTR", "what messaging converts", "which hook works", or "analyze copy performance across active ads". Returns per-ad rows (with each variant's metrics) plus aggregated rankings across all analyzed ads: top copy by spend, top copy by CTR (filtered to variants with 500+ impressions so noisy low-volume rows don't dominate), and top copy by ROAS. Use the "view" param to control the response: "aggregated" returns ONLY the cross-ad copy rankings (best for ad-set-level analysis — combine with adset_id to roll every ad's copy into one deduped breakdown, no per-ad noise), "ads" returns ONLY the per-ad rows, "both" (default) returns everything. The aggregated rankings default to the top 100 variants per list; raise or lower with variant_limit (1-500). Defaults to ACTIVE ads over the last 30 days — override with status_filter, date_preset, or date_from/date_to. Meta does NOT support combining body_asset + title_asset in one call, so this tool issues two parallel breakdown requests for multi-variant ads; expect a short extra latency on accounts with many such ads. Purchases, leads, and add-to-cart counts are DEDUPED to match Ads Manager (prefer standard event, fall back to MAX of Pixel vs onsite) — do not sum raw action_type rows. ROAS = purchase_value / spend. If the date range includes today, today's row is partial.

### `meta_start_insights_job`

Start an async insights job for heavy queries that would time out synchronously. ENTERPRISE TIER ONLY — returns a 403 ENTERPRISE_ONLY error for non-enterprise orgs. Use when: (1) you need ad-level insights with 90+ day ranges, (2) any query with multiple breakdowns on a 1000+ ad account, or (3) a prior sync call returned "please reduce the amount of data" error. Returns a job_id. Poll with meta_check_insights_job every ~15 seconds. Estimated completion is returned up front; typical jobs complete in 2-8 minutes. The job result is cached for 24 hours — safe to call once and check back later.

### `meta_check_insights_job`

Check the status of an async insights job started with meta_start_insights_job. Returns { status: "running" | "complete" | "failed", percent?, rows? }. When complete, the "rows" array contains the full result set. Poll every 15-30 seconds — most jobs finish in 2-8 minutes.

## Meta — Audiences

### `meta_list_audiences`

List custom audiences for the ad account. Returns audience ID, name, type, approximate size (null if unavailable, e.g. for lookalike audiences), status (ready, expiring, out_of_date, too_small, or unknown), created time in ISO 8601, and detailed operation_status/delivery_status objects. Custom audiences are used in ad set targeting to reach specific groups of people.

### `meta_get_audience`

Get details of a specific custom audience by ID. Returns name, type, approximate count (null if unavailable), status (ready, expiring, out_of_date, too_small, or unknown), created time in ISO 8601, and operation_status/delivery_status objects.

### `meta_create_custom_audience`

Create a custom audience for targeting. Custom audiences let you target people who have already interacted with your business. Requires a name, type (e.g., WEBSITE, CUSTOMER_FILE, ENGAGEMENT), and a rule object defining the audience criteria.

### `meta_create_lookalike_audience`

Create a lookalike audience based on an existing custom audience. Lookalike audiences find people similar to your existing audience. Requires a source audience, target country, and a ratio (0.01-0.20) where lower values mean closer similarity.

### `meta_audience_users`

Add or remove users in a CRM custom audience by uploading hashed customer data. Set operation="add" to add users, or operation="remove" to remove (suppress) them. Data is SHA-256 hashed server-side before sending to Meta. Up to 10,000 records per request. The schema array defines which fields each data row contains (e.g. ["EMAIL", "PHONE", "FN", "LN"]). Supported schema fields: EMAIL, PHONE, FN (first name), LN (last name), ZIP, CT (city), ST (state), COUNTRY, EXTERN_ID.

### `meta_get_delivery_estimate`

Get a delivery estimate for a targeting spec and optimization goal. Returns estimated daily reach, impressions, and clicks for the given audience and goal. Use this to validate audience size before creating an ad set. Note: delivery estimates may return zeros for very broad targeting or when Meta cannot model the specific optimization goal. If estimate_ready is false, the estimate is still being calculated. For audience sizing only, use meta_get_reach_estimate instead — it is more reliable.

### `meta_get_reach_estimate`

Get a reach estimate for a targeting spec. Returns the estimated number of people who could be reached with the given audience targeting. Use this to gauge audience size before creating an ad set.

### `meta_search_targeting`

Search Meta detailed-targeting options by keyword. Set `type` to choose the category: "interests" (e.g. yoga, running shoes, B2B software), "behaviors" (e.g. frequent travelers, early tech adopters), "demographics" (income brackets, education levels, life events, parenting, relationship status, work industries), "education_schools", "education_majors" (fields of study), "work_employers" (specific companies), or "work_positions" (job titles). Returns matching options with id, name, and estimated audience_size — use the id in the corresponding ad set targeting array (targeting.interests / .behaviors / .demographics). For places/geo-targeting use meta_search_geo_locations; to expand a few known interests into adjacent ones use meta_get_interest_suggestions.

### `meta_get_interest_suggestions`

Given a list of seed interest names, return Meta-suggested related interests. Useful when the user names a few known interests and wants adjacent ones. Returns id, name, audience_size.

### `meta_search_geo_locations`

Search countries, regions, cities, zip codes, or designated market areas (DMAs) for geo-targeting. Returns id (a string key like "US" or "2421836"), name, audience_size. Use the id with type in targeting.geo_locations.

## Meta — Creatives

### `meta_list_creatives`

List ad creatives for the ad account. Creatives contain the visual and text content of ads (images, headlines, body text, links, call-to-action buttons). Note: In the Xylo API, creatives are embedded in ads. This tool fetches creative assets uploaded via the creatives/upload endpoint.

### `meta_get_creative`

Get details of a specific ad creative by ID. Returns the full creative specification including images, text, links, and call-to-action configuration. Also returns asset_feed_spec (with images, videos, bodies, titles, link_urls, call_to_action_types, and asset_customization_rules) for Placement Asset Customization / Dynamic Creative ads — use this to read the raw per-placement asset mapping (feed vs. story/reels) and the adlabel→placement rules. Returns degrees_of_freedom_spec.creative_features_spec — the Advantage+ creative enhancements actually enrolled on the ad, each as { enroll_status: OPT_IN | OPT_OUT } — plus creative_sourcing_spec (product_extensions catalog + site links). Use these to verify which Advantage+ enhancements a created ad really has.

### `meta_upload_creative_image`

Upload a SINGLE ad-hoc image to Meta and get back an image_hash for ad creation. IMPORTANT: If the user uploaded images via the Xylo dashboard, OR you have multiple images to send to Meta, use meta_finalize_media instead — it bulk-pushes all staged assets in one call. Do NOT loop this tool over a list of images; that is slow and wastes rate limit. Use this tool only for one-off uploads from an external URL or base64 blob (e.g., a single AI-generated image). Provide either an image_url (publicly accessible URL) or image_data (base64 encoded). Supported formats: JPG, PNG. Recommended size: 1200x628 pixels for feed ads.

## Meta — Page Posts

### `meta_list_page_posts`

List published posts on a Facebook Page. Returns post IDs (format: pageId_postId), message text, creation time, permalink URL, and engagement counts (likes, reactions, comments, shares) for each post. Use this to review Page performance, find post IDs for reading comments (meta_page_comment), promoting posts as ads (object_story_id), or managing existing content.

### `meta_create_page_post`

Create a post on a Facebook Page — published immediately, or scheduled for a future time. You can post plain text, include a link preview, or attach an image URL. By default the post publishes right away; to SCHEDULE it for later instead, pass scheduled_publish_time (the post stays queued until then — review queued posts with meta_list_scheduled_posts). To use a creative uploaded via the Xylo dashboard, call xylo_list_uploaded_media and pass the asset's `preview_url` as `image_url` — do NOT pass the image_hash (meta_ref), which is only valid for ads. Keywords: post to Facebook, publish Page post, schedule Facebook post, queue a post for later.

### `meta_list_scheduled_posts`

List all scheduled (unpublished) posts for a Facebook Page. Returns post ID, message, scheduled publish time, and creation time. Use this to review what content is queued before it goes live, or to find post IDs for updating or deleting scheduled posts.

### `meta_update_page_post`

Update the text message of an existing Facebook Page post (published or scheduled). Only the message field can be changed after creation — link and image cannot be swapped. Use the post_id returned from create_page_post or list_scheduled_posts.

### `meta_get_latest_page_post`

Shortcut for "the latest Facebook Page post" — resolves everything in one call. Returns the most recent published post for a Page plus its comments, so you don't need to chain meta_list_pages → meta_list_page_posts → meta_page_comment. Use this FIRST for any prompt like "comments on our latest Facebook post", "how did the last Page post do", "what are people saying on our Page", etc. If the user refers to an account by BRAND NAME (e.g. "Getmo.us", "our client Acme"), first call meta_list_accounts with `query` set to that name to resolve the ad_account_id — then pass it here as ad_account_id. If `page_id` is omitted, falls back to the ad account's `default_page_id` (set in the Xylo dashboard). Pass `page_id` explicitly if the user is asking about a specific Page that is not the default for the current ad account. Keywords: latest Facebook post, FB page post, Facebook comments, Page comments, latest post comments, reactions on our post, how did our post do, what are people saying.

### `meta_delete_page_post`

Delete a Facebook Page post (published or scheduled). WARNING: This permanently removes the post and cannot be undone. Published posts will be removed from the Page timeline immediately. Scheduled posts will be cancelled and will not publish.

## Meta — Instagram Publishing

### `meta_publish_instagram`

Publish content to an Instagram account. Set `media_type`: "IMAGE" (a single JPEG photo via image_url), "VIDEO" (a feed video via video_url), "REEL" (a short-form Reel via video_url — may be surfaced to non-followers), "STORY" (a 24-hour story — provide image_url OR video_url, not both), or "CAROUSEL" (2–10 items via `items`, each with an image_url or video_url). Media must be publicly accessible via URL; only JPEG images are supported. Videos, reels, and carousels containing video may take up to ~60s to process before going live. Instagram enforces a rate limit of 100 posts per 24 hours across all content types (check it with meta_check_instagram_publishing_limit). Keywords: post to Instagram, publish photo, publish image, publish video, publish reel, publish story, publish carousel.

### `meta_check_instagram_publishing_limit`

Check the current publishing rate limit for an Instagram account. Shows how many of the 100 allowed posts in the current 24-hour window have been used and how many remain. Use this before bulk publishing to avoid hitting the limit.

### `meta_instagram_media`

Instagram posts/media for a connected IG account — LIST recent posts OR GET one post in full detail. OMIT media_id to LIST the account's recent published posts (most recent first): returns media ID, type, a truncated caption preview (first line), permalink, like/comment counts, timestamp, and boost_eligibility_info. PASS media_id to GET a SINGLE post in full detail — the FULL caption plus the media_url + thumbnail_url CDN links. Use the media IDs with meta_create_ad's source_instagram_media_id field to promote IG posts as ads; check boost_eligibility_info to confirm a post is eligible. The LIST omits the heavy media_url/thumbnail_url CDN links by default to stay fast — set include_media_urls=true to include them on a list (or just fetch the single post). For only the single LATEST post plus its comments, call meta_get_latest_instagram_post instead. Keywords: list instagram posts, instagram media, get instagram post, IG post by id, promote IG post as ad, boost eligibility, reels, carousel, post caption, permalink, engagement counts.

### `meta_get_instagram_account`

Get profile metadata for an Instagram Business account — absolute current totals: follower count, follows count (accounts followed), media count (total posts), username, display name, biography, profile picture URL, and website. Use for prompts like "how many followers does {brand} have", "what is my total follower count", "show me my IG profile info", "how many posts has {handle} published". Distinct from meta_get_instagram_account_insights (which returns time-windowed engagement data like reach, profile_views, accounts_engaged, and a follower delta). For absolute current totals, use this tool. For activity / performance over a time window, use meta_get_instagram_account_insights.

### `meta_get_instagram_account_insights`

Get ACCOUNT-LEVEL performance insights for an Instagram Business account — metrics for the whole account, NOT for individual posts. Returns time-series metrics (reach, follower_count) and total-value metrics (accounts_engaged, total_interactions, profile_views, likes, comments, shares, saves, replies). Use for questions like "how is my Instagram account performing this week", "how many new followers did I get", "what is my account-level reach", or any prompt asking about the IG account as a whole rather than specific posts. For per-post / per-reel / per-story metrics, use meta_get_instagram_media_insights instead. IMPORTANT: Response format is an array of { name, period, values: [{ value, end_time? }] } objects per metric — NOT flat key-value pairs. Read a metric by finding the object with the matching name and accessing values[0].value. Period defaults to "day" (last 24 hours). Pass period="week" or period="days_28" for longer windows.

### `meta_get_instagram_media_insights`

Get performance insights for a specific Instagram media post. IMPORTANT: Response format differs from ad insights — returns an array of { name, values: [{ value }] } objects, NOT flat key-value pairs. To read a metric, find the object with the matching name and access values[0].value. Metrics vary by media type: Reels return reach, likes, comments, saves, shares, and average watch time; Stories return reach, shares, follows, navigation, and replies; Feed posts return reach, likes, comments, saves, shares, follows, and profile activity.

### `meta_toggle_instagram_comments`

Enable or disable comments on an Instagram media post. When comments are disabled, existing comments are hidden and new comments cannot be added. Requires the IG Business account ID that owns the post so the Page access token can be resolved.

### `meta_get_latest_instagram_post`

Shortcut for "the latest Instagram post" — resolves everything in one call. Returns the most recent published media for an Instagram account plus its comments, so you don't need to chain meta_search_instagram_accounts → meta_instagram_media → meta_instagram_comment. Use this FIRST for any prompt like "comments on our latest IG post", "how did the last reel do", "what are people saying about our newest post", etc. If the user refers to an account by BRAND NAME (e.g. "Getmo.us", "our client Acme"), first call meta_list_accounts with `query` set to that name to resolve the ad_account_id — then pass it here as ad_account_id. If `ig_account_id` is omitted, falls back to the ad account's `default_instagram_id` (set in the Xylo dashboard). Pass `ig_account_id` explicitly if the user is asking about a specific brand whose IG account is not the default for the current ad account. Keywords: latest Instagram post, IG post, Instagram comments, latest reel, latest story, IG engagement, what are people saying, reactions on our IG, how did the post do.

### `meta_list_connected_instagram_accounts`

List Instagram accounts connected to the ad account for advertising. Use this to get the correct instagram_actor_id for promoting IG posts as ads via meta_create_ad. This is the Meta-recommended source for instagram_user_id in ad creative creation.

### `meta_search_instagram_accounts`

List all Instagram business accounts connected to any Facebook Page the user manages. Unlike meta_list_connected_instagram_accounts (which needs a page_id), this searches across ALL the user's pages. Optional query filters by IG username or display name (case-insensitive substring).

### `meta_discover_instagram_business`

Pull PUBLIC Instagram profile data (followers, name, bio, website, profile pic) for ANY Business or Creator IG account by handle — NOT just connected accounts. Optionally fetch the most recent posts with public engagement (like_count, comments_count). Use for competitor research, influencer vetting, brand monitoring, or quick lookups when the user asks "how many followers does {brand} have on IG", "what does {handle}'s profile look like", "show me {competitor}'s recent posts", or "find me info on @username". LIMITATIONS: only works for Business/Creator accounts (not personal). Reach, impressions, and engagement rate are NOT available — those are private to the account owner. Pass ig_account_id = one of YOUR OWN connected IG accounts (use meta_search_instagram_accounts or meta_list_accounts.default_instagram_id) — Meta requires a caller IG identity. Keywords: instagram discover, competitor instagram, find IG account, look up handle, public IG profile, influencer profile, brand IG, IG followers for, IG bio for.

### `meta_list_instagram_tagged_media`

List posts (photos, videos, reels, carousels) where YOUR connected IG account has been tagged by other users. The single best tool for UGC monitoring, influencer activation tracking, and brand-mention discovery on Instagram. Returns post ID, caption, media type, permalink, timestamp, like_count, comments_count, and the tagging account's username. Use when the user asks "show me posts where we've been tagged", "UGC for our brand", "who tagged us this week", "influencer posts mentioning us", or "creator mentions". LIMITATIONS: only public engagement metrics — reach and impressions are NOT available because we don't own the posts. Response includes paging: { next_cursor, has_more }. Pass next_cursor as `after` to get the next page.

## Meta — Comments (FB & IG)

### `meta_instagram_comment`

Read or manage comments on an Instagram media object (post or reel). Set `action`: "list" (read the comments on a media_id), "create" (post a new top-level comment on a media_id as the IG Business account), "reply" (reply to an existing comment_id, creating a threaded child reply), "hide" (hide or unhide a comment_id via the `hide` flag — hidden comments stay but are not shown to others), or "delete" (permanently remove a comment_id — cannot be undone; prefer "hide" if unsure). Keywords: read Instagram comments, reply to IG comment, hide comment, delete/moderate Instagram comments, respond to comments. For comments on your LATEST IG post, meta_get_latest_instagram_post resolves account + media + comments in one hop. To find a media_id from scratch, use meta_instagram_media (needs an ig_account_id from meta_list_accounts.default_instagram_id or meta_search_instagram_accounts).

### `meta_page_comment`

Read or manage comments on a Facebook Page post. Set `action`: "list" (read the comments on a post_id — returns id, message, author, created time, like count, hidden status), "reply" (reply as the Page to a comment_id), or "delete" (permanently remove a comment_id — cannot be undone; use only for spam, hate speech, or policy violations). page_id is always required. Keywords: read Facebook Page comments, reply to comment as the Page, delete/moderate Page comments, audience engagement.

## Meta — Pixels & CAPI

### `meta_list_pixels`

List Meta Pixels (Facebook Pixels) connected to the ad account. Pixels are used to track website events and power conversion-based campaigns. Returns pixel ID, name, last fired time, and automatic matching settings.

### `meta_create_pixel`

Create a new Meta Pixel for the ad account. Each ad account can only have one pixel. The pixel must be installed on your website to track visitor actions and conversions. Returns the new pixel ID.

### `meta_get_pixel`

Get details of a specific Meta Pixel by ID. Returns the pixel name, base code snippet for installation, last fired time, creation time, and automatic matching configuration.

### `meta_update_pixel`

Update pixel settings such as its name or automatic matching configuration. Automatic matching improves attribution by hashing and matching customer data (email, phone, etc.) sent with pixel events.

### `meta_send_conversion_events`

Send conversion events to Meta via the Conversions API (CAPI). Use this to send server-side events that complement or replace browser pixel events. Supports standard events (Purchase, Lead, AddToCart, etc.) and custom events. IMPORTANT: Send user data (email, phone, name) as plain text — the server automatically SHA-256 hashes PII before sending to Meta. Do NOT pre-hash values. Fields like client_ip_address and fbc are sent unhashed. Maximum 1,000 events per request.

## Meta — Knowledge Base

### `meta_ads_knowledge`

Returns Meta Ads domain knowledge for interpreting ad performance data. Call this BEFORE analyzing performance issues, diagnosing underperformance, explaining metrics to users, or making optimization recommendations. Covers: auction mechanics (total value formula), learning phase (status interpretation, best practices), pacing (budget/bid pacing, why daily spend varies), bid strategies (how each strategy affects delivery and costs), auction overlap (self-competition detection), ad relevance diagnostics (quality/engagement/conversion ranking interpretation), and performance fluctuations (normal vs. concerning patterns). Returns knowledge filtered by topic — pass specific topics for focused context.

## Meta — Media

### `xylo_bulk_create_upload_urls`

Create upload URLs so the client can stream images/videos directly into the Xylo dashboard (staging only — this does NOT upload to Meta). Handles ONE file or MANY: for a single file, pass a one-element `files` array; for many, it collapses what would be dozens of round trips into 1. Works in Claude Code, Claude Desktop, and claude.ai web (any environment that can make an HTTP PUT). WORKFLOW: (1) Call this tool with ad_account_id + an array of {filename, mime_type, group?}. (2) PUT each file's bytes to its returned upload_url (Bash + curl, or any HTTP client). The Content-Type header MUST match the registered mime_type. (3) Each PUT response is the registered media asset (asset_id, blob_url, dimensions, group_id, etc.), staged in the Xylo dashboard (not yet on Meta). (4) Optional: call xylo_list_uploaded_media to confirm groupings, then meta_finalize_media to push to Meta. PRE-GROUPING: pass an optional `group` label on each file to stage assets already grouped — files that share a label (2+ of them) land in the same creative set with that label as the group name, with no separate merge step. Use this to pair placement variants you have decided belong together (e.g. a 1:1 feed image and its 9:16 story version). Distinct labels = distinct groups; a label on a single file is ignored. AUTO-PAIRING: files staged WITHOUT a group label auto-pair on upload when a feed-size and a 9:16 story-size asset share a filename (placement markers like feed/story/reel/1x1/4x5/9x16/1080x1920 are ignored when comparing) or look visually identical. Explicit labels always win over auto-pairing. Caps: 100 files per call, 30MB per image, 300MB per video. Upload URLs expire in 1 hour.

### `xylo_upload_directory`

For agents with local filesystem access (Claude Code, Cursor, etc.). Upload every image/video in a local directory into the Xylo dashboard as a staged, PRE-GROUPED creative set — ready for the user to review. This stages into Xylo only; it does NOT push to Meta. Skip this tool when running in claude.ai (no filesystem) — use the Xylo dashboard upload instead. This is the efficient one-pass flow: review the creatives, decide the groupings yourself, and stage them already grouped + named so the user can open the dashboard and double-check accuracy — no separate upload-then-group-then-rename round trips. WORKFLOW (the agent executes these steps): (1) List the image/video files in `directory` (jpg, jpeg, png, mp4, mov). (2) REVIEW the images (read/open them) and decide which files are placement variants of the same     creative — e.g. a 1:1 or 4:5 feed image paired with its 9:16 story/reel version. Give each set a     short descriptive name based on the creative content (not the placement sizes). (3) Call this tool with that plan: `groups` (each {group_name, files}) plus any leftover `ungrouped`     files. This tool calls Xylo and returns the per-file upload URLs (annotated with their group). (4) PUT each file to its returned upload_url in parallel (Bash + curl or any HTTP client).     Content-Type must match mime_type. Each PUT response is the registered, already-grouped asset. (5) Tell the user the creatives are staged and grouped in the Xylo dashboard under this ad account,     and to review the groupings there. (6) Only after the user confirms, push them to Meta with meta_finalize_media. Caps: 100 files per call, 30MB per image, 300MB per video. Upload URLs expire in 1 hour.

### `xylo_list_uploaded_media`

List media assets (images and videos) staged in the Xylo dashboard for an ad account. Assets are kept for 24h after upload. By default returns metadata + preview URLs (small payload). Set previews="inline" to receive compressed image previews inside MCP image blocks — useful when you need to visually inspect a small batch. Use the `ids` filter to recover hashes after a dropped finalize stream. Each asset has two usable references: (1) `meta_ref` — the image_hash (images) or video_id (videos) for meta_create_ad. Null until the asset has been pushed to Meta with meta_finalize_media. (2) `preview_url` — a direct Vercel Blob CDN URL, fetchable by Meta's Graph API scraper. When a user says "use the creative I uploaded", "group my uploaded images", or "create ads with my uploaded assets," call this tool first. Assets staged together with a group (e.g. via xylo_upload_directory or the dashboard) appear grouped here. To group images that belong together, use xylo_group_media. To name a group, use xylo_rename_media_group.

### `xylo_group_media`

Group already-staged media assets into a creative set inside the Xylo dashboard. Use this after viewing uploaded images with xylo_list_uploaded_media to pair placement variants together (e.g., a 1:1 feed image with its 9:16 story version) when they were not pre-grouped at upload time. Pass the asset IDs of images that are the same creative in different sizes. Requires at least 2 asset IDs. All assets must belong to the same ad account. This only regroups assets staged in Xylo — it does not touch Meta. After grouping, tell the user they can review the groupings in the Xylo dashboard under their ad account tab. Then ask if they would like you to send the assets to Meta now so they can be used in ads (via meta_finalize_media), or if they want to review and adjust first.

### `xylo_rename_media_group`

Rename a creative set / media group staged in the Xylo dashboard. Use this after grouping assets with xylo_group_media to give the group a descriptive name based on the creative content (e.g., "Summer Sale - Pool Service", "Brand Awareness - Clock Ad"). The group_id is returned by xylo_group_media or visible in xylo_list_uploaded_media results.

### `meta_finalize_media`

Push staged creative assets (images and videos) from the Xylo dashboard to Meta in a single call. THIS is the step that actually uploads to the live Meta ad account — everything before it (xylo_bulk_create_upload_urls, xylo_upload_directory) only stages into Xylo. It is the batch equivalent of pressing "Send to Meta" in the dashboard — use it whenever the user has staged multiple images or videos and you need to push them to the Meta ad account so they can be used in ads. ALWAYS prefer this over calling meta_upload_creative_image / meta_upload_ad_video in a loop: it sends 10, 20, 50+ assets at once instead of one-by-one (much faster, fewer rate-limit risks). Call this after the user confirms their groupings are correct. You can finalize all unfinalized assets (default) or specific ones by ID. Assets already finalized (meta_ref is not null) are skipped automatically. Returns each asset's meta_ref (image_hash for images, video_id for videos) — pass these directly into meta_create_ad.

## Meta — Budget Schedules

### `meta_list_budget_schedules`

List budget schedules (high-demand periods) attached to a Meta campaign or ad set. Returns id, time_start, time_end (ISO 8601), budget_value, and budget_value_type (ABSOLUTE = fixed spend, MULTIPLIER = percentage of baseline budget).

### `meta_create_budget_schedule`

Schedule a budget change for a fixed window on a Meta campaign or ad set. Use ABSOLUTE to set a new daily/lifetime budget for the window (value in minor currency units, e.g. cents). Use MULTIPLIER to scale the existing budget (value is percent: 150 = 1.5x, 300 = 3.0x, range 100–1000). Returns the new schedule id. Example: promote Black Friday by setting MULTIPLIER=200 from 2026-11-27 to 2026-11-30.

### `meta_delete_budget_schedule`

Delete a budget schedule by ID.

## Meta — Ad Rules

### `meta_ad_rule`

Create and manage Meta AUTOMATED RULES (the Ad Rules Engine). Use this to set up rules that watch ad objects on a schedule and act automatically. COMMON USE CASES: auto-pause underperforming ads/ad sets when CPA is too high or ROAS too low, auto-scale (increase) budget when ROAS is good, dayparting / scheduling (only run certain hours/days), get an alert or notification when spend/CPA crosses a threshold, rotate ads, rebalance budget across ad sets toward the best performers, or ping a webhook on a trigger. Set `action`: "list" (all rules), "get" (one rule by rule_id), "create", "update", "delete", "enable"/"disable" (turn a rule on/off — maps to status ENABLED/DISABLED), "history" (execution log for ONE rule — rule_id REQUIRED; Meta has no account-level history), "active" (rules currently active on specific objects — pass selected_ids), or "count_by_type" (rule counts grouped by type — optional statuses filter). SAFETY: a newly created rule defaults to DISABLED so it never acts on live spend before you review it — turn it on afterward with action "enable" (or status ENABLED). TWO ways to define a rule on create/update: (A) ERGONOMIC (schedule rules, the common case): `name`; `applies_to` {level: AD|ADSET|CAMPAIGN, ids?} (REQUIRED when not passing a raw evaluation_spec — Meta needs entity_type or id on every rule); `conditions` [{metric, operator, value}] + `time_window` (a time_preset like LAST_7D — REQUIRED whenever conditions are present); `rule_action` {type, change?, rebalance?, ...}; and `schedule` (DAILY | HOURLY | SEMI_HOURLY | a custom array). (B) RAW escape hatch for FULL parity (incl. trigger rules + advanced/aggregate/formula filters): pass `evaluation_spec`, `execution_spec`, and/or `schedule_spec` as Meta-shaped objects — a raw spec is used VERBATIM and OVERRIDES the ergonomic params. UNITS: money in `conditions` thresholds and in `rule_action.change.amount` (when unit=ACCOUNT_CURRENCY) is in the currency BASE UNIT — CENTS for USD (2000 = $20). Values pass through unchanged to match Meta. NOTE: rules that PAUSE objects cannot filter on cost metrics (Meta error 2703). No manual run / dry-run exists in the Meta API.

### `meta_value_rule_set`

Create and manage Meta VALUE RULES (value rule sets) — bid multipliers that pay more (or less) for specific audiences, attached at the ad-set level. COMMON USE CASES: bid a multiplier by age, gender, location, placement, device, or OS; pay more for high-value audiences / segments worth more to you; lifetime-value (LTV) bidding; raise bids for your best converting locations or placements and lower them elsewhere. Set `action`: "list" (all value rule sets), "get" (one by value_rule_set_id), "create", "update" (full read-modify-write — see the rule id notes), "delete", "attach" (apply a value rule set to an ad set), or "detach" (remove value rules from an ad set). On create/update pass `name` + `rules` — each rule is {name, adjust_sign (INCREASE/DECREASE), adjust_value (percent: INCREASE 1–1000, DECREASE 1–90), criterias:[{criteria_type, operator:"CONTAINS", criteria_values, criteria_value_types}]}. The wire key is "criterias" (Meta's spelling). LIMITS: 6 rule sets/account, 10 rules/set, 4 criteria/rule; first-matching-rule wins (array order = priority). attach needs adset_id + value_rule_set_id; detach needs adset_id only. ELIGIBILITY: ad sets on auto-bid (LOWEST_COST_WITHOUT_CAP) or COST_CAP.

## Meta — Pages

### `meta_list_pages`

List Facebook Pages the authenticated user manages. Call this FIRST for any prompt like "show me my Facebook Pages", "what Pages do I manage", "list my Pages", etc. NO ad_account_id is required for the default user-listing path — just call it directly. Optional `query` filters by name or id (case-insensitive substring). Set source=ad_account + ad_account_id to list Business Manager pages promotable by a specific ad account (promote_pages endpoint) — use this when you need the broader BM scope rather than the user-owned set. Keywords: list Facebook Pages, my Pages, Pages I manage, Page discovery, admin Pages.

## Meta — Lead Gen

### `meta_list_lead_forms`

List Lead Gen forms attached to a Facebook Page. Returns each form's id, name, status (ACTIVE, PAUSED, DELETED, ARCHIVED), and created_time. Use the returned form id with meta_get_lead_submissions to retrieve the actual leads (names, emails, phone numbers, etc.) submitted to that form. Requires leads_retrieval permission on the Meta access token and the Page token tied to the Page that owns the form.

### `meta_get_lead_submissions`

Retrieve submitted leads for a specific Lead Gen form. Returns each lead's id, created_time, and a fields object mapping question names to answers (e.g. { "full_name": "Jane Doe", "email": "jane@example.com", "phone_number": "+15551234" }). Use meta_list_lead_forms first to find the form_id. Requires leads_retrieval permission on the Meta token.

## Meta — Catalogs

### `meta_list_catalogs`

List Meta product catalogs accessible to the ad account. Catalogs power Advantage+ Catalog Ads (formerly Dynamic Ads), Shop ads, and Collection ads. Returns catalog id, name, and product_count. Tries the ad account's owned_product_catalogs first, then falls back to catalogs owned by the user's connected Business Manager accounts. Returns an empty list if no catalogs are accessible.

### `meta_list_catalog_products`

List or look up products in a Meta product catalog. Returns up to `limit` products with id, retailer_id, name, price, currency, image_url, url, and availability. Use this to inspect what's in a catalog before targeting it in an Advantage+ Catalog Ads campaign, or to verify products are syncing correctly from your feed. PRODUCT-LEVEL INSIGHTS JOIN: pass `retailer_ids` to resolve the values returned by the `product_id` insights breakdown (meta_get_campaign_insights etc. with breakdowns="product_id") to full product details — the breakdown reports the catalog `retailer_id` (feed/SKU ID), not the Facebook item id. Raw breakdown values like "45015356375236, Some Product Name" can be passed as-is; the ID before the first comma is extracted automatically. This is how you find WHICH PRODUCTS get the most spend/purchases in catalog ads.

### `meta_list_product_sets`

List product sets within a Meta product catalog. USE THIS when the user wants to enable product extensions on an ad and pin a specific product set (e.g. "show NEW ARRIVALS underneath this ad"). Returns id, name, product_count, and filter for each set. Pass the chosen set's id as `creative.product_set_id` in meta_create_ad along with `creative.product_extensions: true`. If the user just wants product extensions enabled and is happy to let Meta auto-match the catalog from the ad's URL, skip this and just set product_extensions=true with no product_set_id.

## Meta — Ad Integrity

### `meta_check_ad_integrity`

Run an integrity check on all active ads in the account. ENTERPRISE TIER ONLY, and only works on accounts that have been opted into daily sync. BEFORE CALLING: check `meta_list_accounts` — each account has `warehouse_enabled: true|false`. Only call this on accounts where warehouse_enabled is true. If you call it on a non-enabled account, the response body will be `{ available: false, reason, message, alternatives }` — do not retry, just tell the user the account is not enabled for integrity checks. Finds: ads linking to domains not matching the business website or not approved, ads using a Facebook page or Instagram account that has not been used before, and ads with no destination URL. Designed for scheduled daily runs — safe to call once per day from an agent cron. Returns a list of anomalies with flag types (unknown_domain, unknown_page, unknown_instagram_account, no_destination_url). Reads from the local snapshot that the daily bulk pull populates. If the snapshot is stale (no recent sync), ask the user to check account_sync_state.

## AI & Cross-Platform

### `audit_campaign`

Audit one or more ad campaigns against industry benchmarks. Analyzes CTR, CPC (all), CPM, ROAS, conversions, and spend patterns. Returns a score (0-100) and structured findings with severity levels (critical, warning, info, good) and actionable recommendations. The audit accounts for the "Breakdown Effect" — Meta's Advantage+ system optimizes for marginal efficiency (cost of the next conversion), not average efficiency. Higher CPM/CPC with strong ROAS is expected behavior, not a problem. Recommendations are framed as testable hypotheses, not directives. This audit accounts for learning phase status, bid strategy context, and the breakdown effect. For a more comprehensive structural audit, use morpheus_audit instead. For deep understanding of the mechanics behind findings, call meta_ads_knowledge.

### `optimize_budget`

Get budget optimization recommendations for your campaigns. Analyzes ROAS, CPA, and CTR efficiency across campaigns and suggests how to redistribute budget. IMPORTANT: If campaigns use Advantage+ Campaign Budget (CBO), Meta already optimizes budget allocation using marginal efficiency. These recommendations are based on average metrics and may differ from Meta's ML optimization. Test budget changes incrementally (10-20% at a time) rather than making large shifts. Bid strategy matters: cost cap campaigns throttle delivery when CPA exceeds target; highest volume campaigns maximize spend regardless. For pacing and bid strategy context, call meta_ads_knowledge.

### `generate_report`

Generate a comprehensive campaign performance report. Includes executive summary, key metrics, performance assessment against benchmarks, top and bottom performers, and recommendations framed as testable hypotheses. Defaults to the last 30 days. If the date range includes today, the report notes that today's data is partial. Great for weekly or monthly performance reviews.

### `cross_platform_insights`

Get cross-platform performance insights combining data from Meta, Google, and/or TikTok Ads into a unified view. Normalizes metrics across platforms for side-by-side comparison. Returns per-platform totals and a grand total. Requires appropriate platform headers.

### `morpheus_audit`

Run a comprehensive Morpheus Media RMP (Rating & Methodology Platform) audit on a Meta or Google Ads account. This goes far beyond basic benchmarking — it checks campaign structure, retargeting configuration, creative health, keyword organization, bidding strategy, conversion tracking, audience strategy, and more against Morpheus Media's best practices from 13+ client audits. Returns an overall score (0-100), category scores, and specific findings with severity levels and actionable recommendations. For Meta: checks prospecting consolidation, retargeting ADV+ settings, audience overlap, creative fatigue (>90 days), CTR decline trends, bidding optimization (max value vs max conversions), attribution settings, pixel health, and more. For Google: checks branded campaign separation, budget allocation, keyword count per ad group (<7 best practice), branded queries in non-branded campaigns, PMax segmentation (80/20 rule), broad match in branded campaigns, conversion action duplicates, RSA completeness, and remarketing presence. IMPORTANT: For Meta audits, pass the ad_account_id. For Google audits, pass the google_customer_id. Meta audits now include learning phase detection, auction overlap analysis, bid strategy interpretation, ad relevance diagnostics, and performance fluctuation analysis. Findings are contextualized — ad sets in learning phase won't generate false performance warnings. For the full Meta ads knowledge framework, call meta_ads_knowledge.

## Warehouse

### `query_ad_warehouse`

Query the local Xylo data warehouse — the daily snapshot of campaigns, ad sets, ads, daily insights, and URL history for the current ad account. ENTERPRISE TIER ONLY, and only for accounts that have been opted into daily sync. BEFORE CALLING: check `meta_list_accounts` — each account has `warehouse_enabled: true|false` and `last_synced_at`. Only call this tool on accounts where warehouse_enabled is true. For other accounts (free plan, or enterprise but not sync-enabled), use live Meta tools (meta_list_campaigns, meta_list_adsets, meta_list_ads, meta_get_ad_insights, etc.) instead. If you call this on a non-enabled account, the response body will be `{ available: false, reason, message, alternatives }` instead of data — pivot to live Meta tools in that case. Much faster than live Meta calls when you need to slice/dice operational data (e.g. "top 20 ads by spend last 14 days", "every ad linking to domain X", "campaigns active but with zero spend today"). Supports filters (eq/in/gte/lte/gt/lt/like), ordering, limit up to 5000 rows, and offset-based pagination. Every query is automatically scoped to the current ad account. For the list of tables and columns, call get_warehouse_schema first. INSIGHTS_DAILY SCHEMA: all scalar metrics are top-level typed columns — filter and sort them natively. Common columns: level, entity_id, date_start, campaign_id, adset_id, ad_name, spend, impressions, clicks, ctr, cpc, cpm, reach, purchases, purchase_value, leads, roas, cost_per_purchase, cost_per_lead, link_clicks, landing_page_views. "entity_id" is the campaign/adset/ad id (depending on level). EXAMPLES: "top 50 ads by spend last 30d" → table: insights_daily, filters: [{level eq ad}, {date_start gte 2026-03-25}], order: {column: spend, desc: true}, limit: 50. "every ad in campaign X" → table: insights_daily, filters: [{level eq ad}, {campaign_id eq 123...}]. "every ad that delivered in the last 30 days (any status, including paused/deleted)" → table: insights_daily, filters: [{level eq ad}, {date_start gte 2026-03-25}, {spend gt 0}] — `insights_daily` has rows for every ad that ran, regardless of current status. To get creative metadata (page_id, destination_urls, creative_id) for those ads, query ad_snapshots — it now retains rows for any ad that delivered within the last 90 days, with still_operational=true|false telling you whether the ad is currently active. The `metrics` JSONB column holds the long-tail (full actions array with every action_type, action_values array, breakdown_values). It is EXCLUDED from default select because it is large (~2-5KB/row). Opt in with select including "metrics" only when you actually need raw action data.

### `get_warehouse_schema`

Get the schema (tables + key columns) of the local Xylo data warehouse (refreshed daily). Enterprise tier only, and only for warehouse-enabled ad accounts. Before calling, verify the current ad account has `warehouse_enabled: true` in the meta_list_accounts response. Returns `{ available: false, reason, message }` if the account is not enabled.

## Feedback

### `send_feedback`

Send feedback, bug reports, or issue reports about Xylo to the Xylo team. CALL THIS TOOL WHENEVER: a Xylo MCP tool returns an unexpected error, returns wrong/incomplete data, or you find yourself stuck or repeatedly retrying because Xylo behavior is unclear or broken; an action the user asked for cannot be completed because of a Xylo-side problem; the user explicitly says "report this", "send feedback", "this is broken", "tell the Xylo team", "file a bug", "this isn't working", or asks you to surface a complaint, suggestion, or pain point. Also call proactively at the end of a frustrating session to summarize what went wrong, so the Xylo team can fix it. Reports are emailed to the support team with the full context you provide. Be detailed: include the exact error messages from previous tool responses, the tools you tried, what the user was trying to accomplish, and any relevant IDs (campaign IDs, ad account IDs, etc.). You do NOT need user confirmation to send feedback when issues happen — just send it.

## Account Brief / Memory

### `read_account_brief`

Read the saved memory / "account brief" for one ad account — the durable context you need to work the way this user expects. CALL THIS AT THE START of any task on an ad account, BEFORE creating or editing campaigns, ad sets, ads, budgets, or targeting, so you follow the account's established conventions instead of asking the user to repeat things they have already told you. Returns: (1) `brief` — the user's freeform description of the business/brand, and (2) `memory` — structured remembered facts keyed by a short slug, each { category, value, source, updated_at }. Remembered facts can include: naming conventions for campaigns / ad sets / ads, default budgets for new campaigns and ad sets, preferred geo targeting, audience and targeting preferences, frequently used product sets or catalogs, which existing campaigns are which (and where new ads usually go), brand voice, ad copy preferences, creative preferences, and anything else this account's assistant was told before. Works for Meta, Google, and TikTok ad accounts. Read-only; no side effects. If nothing has been saved yet, `memory` is {} — that just means this account has no prior context, so gather it from the user and save it with update_account_brief.

### `update_account_brief`

Save durable context ("memory" / account brief) for one ad account so the user never has to repeat it. CALL THIS WHENEVER the user reveals a lasting preference or fact about the account or its brand — for example: "always name campaigns like PROSPECTING_US_[date]", "new prospecting ad sets start at $50/day", "we mostly target US and Canada", "our brand voice is playful, no corporate speak", "put new ads in the Evergreen campaign", "our default product set is Bestsellers". Store ONE fact per `remember` entry with a short stable `key` in lowercase_snake_case (e.g. `naming_convention`, `default_daily_budget`, `primary_geo`, `audience_preferences`, `brand_voice`, `where_new_ads_go`), so re-saving the same key UPDATES that fact instead of creating a duplicate. Optionally set `category` to one of: naming, budgets, geo, audiences, targeting, product_sets, campaign_map, brand_voice, copy, creative, other (defaults to other). Use `forget` to drop keys that are no longer true. This does NOT overwrite the user's freeform brief — it only updates the structured memory, and it merges with whatever is already saved. You do not need to ask permission to save a fact the user clearly stated. Do NOT store secrets, access tokens, or personal data. Works for Meta, Google, and TikTok. Returns the updated brief + memory, plus `warnings` listing any entries that were skipped (bad key, empty/too-long value, or limits reached).

## Google — Campaigns

### `google_list_accounts`

List or look up Google Ads accounts (customers) connected to the Xylo organization. USE THIS FIRST for Google Ads: it is the no-argument discovery entry point — call it with no args to see every Google Ads account you have access to, with their customer_id values. USE THIS ANY TIME the user refers to a Google account by BRAND NAME (e.g. "Acme", "our client", "for <brand>") — pass the brand as `query` to resolve it to a customer_id. Every other Google Ads tool (campaigns, ad groups, ads, keywords, insights, assets, conversions, etc.) requires a customer_id — resolve it here first. This is the Google equivalent of meta_list_accounts. Returns customer_id, manager_customer_id, name, status, billing_status, and `activated` (whether the account is activated on your plan). If `activated` is false, the account must be activated in the dashboard at https://xyloapi.dev/dashboard before campaign/insight tools will return data. Keywords: list Google accounts, find Google Ads account, resolve brand to customer id, which Google account is <brand>, discover Google Ads customers, Google Ads customer id, customer id resolver, Google account discovery, MCC, account for brand.

### `google_list_campaigns`

List Google Ads campaigns for the current customer account. Supports filtering by status (ENABLED, PAUSED, REMOVED) and pagination. Returns campaign ID, name, status, type, budgets, and schedule. Note: pagination is not currently supported — all matching results up to the limit are returned in one response.

### `google_get_campaign`

Get details of a specific Google Ads campaign by ID. Returns name, status, campaign type, daily budget, start/end dates, and bidding strategy.

### `google_create_campaign`

Create a new Google Ads campaign. The campaign defaults to PAUSED status for safety. You must specify a name and campaign_type. Valid campaign types: SEARCH, DISPLAY, SHOPPING, PERFORMANCE_MAX, VIDEO.

### `google_update_campaign`

Update an existing Google Ads campaign. You can change the name, status, budget, or schedule. Only the fields you provide will be updated.

### `google_pause_campaign`

Pause an active Google Ads campaign. This immediately stops ad delivery and spend. The campaign can be resumed later with google_resume_campaign.

### `google_resume_campaign`

Resume a paused Google Ads campaign. This reactivates ad delivery and spending. Make sure the campaign has ad groups and ads before resuming.

### `google_delete_campaign`

Delete a Google Ads campaign. This sets the campaign status to REMOVED. All ad groups and ads under this campaign will also stop delivering. WARNING: This cannot be undone.

## Google — Ad Groups

### `google_list_ad_groups`

List Google Ads ad groups within a specific campaign. Ad groups organize ads and keywords under a campaign. Returns ad group ID, name, status, CPC bid, and campaign association. Note: pagination is not currently supported — all matching results up to the limit are returned in one response.

### `google_get_ad_group`

Get details of a specific Google Ads ad group by ID. Returns name, status, CPC bid, campaign ID, and ad group type.

### `google_create_ad_group`

Create a new Google Ads ad group within a campaign. The ad group defaults to PAUSED status for safety. You must specify a name. Optionally set a CPC bid.

### `google_update_ad_group`

Update an existing Google Ads ad group. You can change the name, status, or CPC bid. Only the fields you provide will be updated.

### `google_pause_ad_group`

Pause an active Google Ads ad group. This stops all ads and keywords in the ad group from delivering. The ad group can be resumed later with google_resume_ad_group.

### `google_resume_ad_group`

Resume a paused Google Ads ad group. This reactivates ad delivery for all enabled ads and keywords in the ad group.

## Google — Ads

### `google_list_ads`

List Google Ads within a specific ad group. Returns ad ID, name, status, type, headlines, descriptions, and final URLs. Supports filtering by status and pagination. Note: pagination is not currently supported — all matching results up to the limit are returned in one response.

### `google_get_ad`

Get details of a specific Google Ads ad by ID. Returns the ad name, status, type, headlines, descriptions, final URLs, and creation time.

### `google_create_ad`

Create a new Google Ads ad within an ad group. The ad defaults to PAUSED status for safety. You must provide a name, type, at least one headline, at least one description, and at least one final URL. Valid ad types: RESPONSIVE_SEARCH_AD, RESPONSIVE_DISPLAY_AD.

### `google_update_ad`

Update an existing Google Ads ad. Per Google Ads policy, only the ad status can be changed. To change ad content (headlines, descriptions, URLs), create a new ad and remove the old one.

### `google_pause_ad`

Pause an active Google Ads ad. This immediately stops the ad from delivering. The ad can be resumed later with google_resume_ad.

### `google_resume_ad`

Resume a paused Google Ads ad. This reactivates ad delivery. Make sure the parent ad group and campaign are also enabled before resuming.

### `google_delete_ad`

Delete a Google Ads ad. This sets the ad status to REMOVED and stops delivery permanently. WARNING: This cannot be undone. Consider pausing the ad instead if you might want it later.

## Google — Keywords

### `google_list_keywords`

List keywords within a specific Google Ads ad group. Keywords trigger ads when users search for matching terms. Returns keyword text, match type, status, CPC bid, and quality score. Note: pagination is not currently supported — all matching results up to the limit are returned in one response.

### `google_add_keywords`

Add one or more keywords to a Google Ads ad group. Each keyword requires text and match_type. Valid match types: BROAD, PHRASE, EXACT. You can add multiple keywords at once by providing an array. Keywords are created in ENABLED status by default.

### `google_update_keyword`

Update an existing keyword in a Google Ads ad group. You can change the status or CPC bid. Only the fields you provide will be updated.

### `google_remove_keyword`

Remove a keyword from a Google Ads ad group. This permanently deletes the keyword. WARNING: This cannot be undone. Consider pausing the keyword instead if you might want it later.

## Google — Search Terms

### `google_search_term_report`

Get a search term report showing actual queries that triggered your Google Ads. Returns search terms with impressions, clicks, cost, conversions, and which campaign/ad group matched.

### `google_list_negative_keywords`

List negative keywords for a Google Ads campaign. These are search terms that prevent your ads from showing.

### `google_add_negative_keywords`

Add negative keywords to a Google Ads campaign. Prevents ads from showing for these search terms. Supports bulk add — provide an array of keywords with text and match type.

### `google_remove_negative_keyword`

Remove a negative keyword from a Google Ads campaign. This allows ads to show for that search term again.

### `google_list_negative_keyword_lists`

List shared negative keyword lists in the Google Ads account. These lists can be linked to multiple campaigns.

### `google_create_negative_keyword_list`

Create a new shared negative keyword list. After creating, add keywords to it and link it to campaigns.

### `google_delete_negative_keyword_list`

Delete a shared negative keyword list. This unlinks it from all campaigns. WARNING: Cannot be undone.

### `google_add_keywords_to_list`

Add negative keywords to a shared negative keyword list. Supports bulk add.

### `google_remove_keyword_from_list`

Remove a keyword from a shared negative keyword list.

### `google_link_negative_list_to_campaign`

Link a shared negative keyword list to a campaign. All keywords in the list will apply to the campaign.

## Google — Insights

### `google_get_campaign_insights`

Get performance insights at the campaign level from Google Ads. Returns metrics like spend, impressions, clicks, CTR, CPC, CPM, conversions, cost per conversion, and conversion rate. Can filter by a specific campaign ID or get all campaigns. Defaults to the last 7 days. Note: ctr, cpc, conversion_rate, and cost_per_conversion are computed server-side. Response includes: conversions (primary), conversions_value (revenue), all_conversions (all types incl. cross-device), all_conversions_value, and view_through_conversions. To get per-conversion-action breakdown (e.g., cost per lead vs cost per purchase), use breakdowns="conversion_action" — each row will include conversion_action_name and conversion_action_category. Conversion lag: Google attributes conversions to the click date, so conversion/ROAS/CPA figures for the last ~7 days are incomplete and keep rising over the following days — do not make pause/scale decisions on them (cost, clicks, impressions are unaffected).

### `google_get_ad_group_insights`

Get performance insights at the ad group level from Google Ads. Returns metrics broken down by ad group. Can filter by campaign_id or ad_group_id. Defaults to the last 7 days. Note: ctr, cpc, conversion_rate, and cost_per_conversion are computed server-side. Response includes: conversions (primary), conversions_value (revenue), all_conversions (all types incl. cross-device), all_conversions_value, and view_through_conversions. To get per-conversion-action breakdown (e.g., cost per lead vs cost per purchase), use breakdowns="conversion_action" — each row will include conversion_action_name and conversion_action_category. Conversion lag: Google attributes conversions to the click date, so conversion/ROAS/CPA figures for the last ~7 days are incomplete and keep rising over the following days — do not make pause/scale decisions on them (cost, clicks, impressions are unaffected).

### `google_get_ad_insights`

Get performance insights at the individual ad level from Google Ads. Returns detailed metrics for each ad. Can filter by campaign_id, ad_group_id, or ad_id. Defaults to the last 7 days. Note: ctr, cpc, conversion_rate, and cost_per_conversion are computed server-side. Response includes: conversions (primary), conversions_value (revenue), all_conversions (all types incl. cross-device), all_conversions_value, and view_through_conversions. To get per-conversion-action breakdown (e.g., cost per lead vs cost per purchase), use breakdowns="conversion_action" — each row will include conversion_action_name and conversion_action_category. Conversion lag: Google attributes conversions to the click date, so conversion/ROAS/CPA figures for the last ~7 days are incomplete and keep rising over the following days — do not make pause/scale decisions on them (cost, clicks, impressions are unaffected).

## Google — Audiences

### `google_list_audiences`

List Google Ads remarketing audiences (user lists) for the current customer account. Returns audience ID, name, type, member count, status, and creation time.

### `google_get_audience`

Get details of a specific Google Ads remarketing audience by ID. Returns the audience name, type, member count, status, and creation time.

### `google_create_audience`

Create a new Google Ads remarketing audience / user list for retargeting and customer match. For a website-visitor remarketing list (source RULE_BASED, the default) provide rule_url_contains — it matches people who visited a page whose URL contains that text. For a customer match list (source CRM) members are uploaded separately with google_upload_user_list_members.

### `google_delete_audience`

Delete a Google Ads remarketing audience. This closes the audience membership — no new users will be added. Existing members remain until their membership expires. WARNING: This cannot be undone.

## Google — Conversions

### `google_list_conversion_actions`

List Google Ads conversion actions for the current customer account. Returns conversion action ID, name, status, type, category, counting type, lookback windows, and value settings.

### `google_get_conversion_action`

Get details of a specific Google Ads conversion action by ID. Returns name, type, category, counting type, lookback windows, value settings, and attribution model.

### `google_create_conversion_action`

Create a new Google Ads conversion action. You must specify a name and type. Valid types: WEBPAGE (website conversions), UPLOAD_CLICKS (offline click conversions), UPLOAD_CALLS (offline call conversions), AD_CALL (calls from ads), WEBSITE_CALL (calls from website). The type cannot be changed after creation.

### `google_update_conversion_action`

Update an existing Google Ads conversion action. You can change name, status, category, counting type, lookback windows, and value settings. Type cannot be changed.

### `google_delete_conversion_action`

Delete a Google Ads conversion action. This sets the status to REMOVED. Conversions will no longer be recorded for this action. WARNING: Cannot be undone.

### `google_upload_offline_conversions`

Upload offline click conversions to Google Ads. Each conversion requires a gclid (Google Click ID), conversion action ID, date/time, value, and currency. Use this to attribute offline sales, phone orders, or CRM events back to Google Ads clicks.

## Google — Assets

### `google_list_assets`

List Google Ads assets (ad extensions) for the current account. Filter by type to see sitelinks, callouts, structured snippets, call extensions, or image assets.

### `google_create_asset`

Create a new Google Ads asset (ad extension). Provide the type and type-specific fields. SITELINK: link_text + final_urls. CALLOUT: callout_text. STRUCTURED_SNIPPET: header + values. CALL: country_code + phone_number. IMAGE: name + image_data (base64).

### `google_delete_asset`

Delete a Google Ads asset. This removes the asset and all its links. WARNING: Cannot be undone.

### `google_list_campaign_assets`

List assets linked to a specific Google Ads campaign. Returns asset IDs, field types, and link status. Filter by field_type to see only sitelinks, callouts, etc.

### `google_link_campaign_asset`

Link an existing asset to a Google Ads campaign. The asset must already exist — use google_create_asset first, then link it. Or use google_create_and_link_asset for both. Note: for IMAGE assets, use field_type AD_IMAGE (not IMAGE) when linking.

### `google_unlink_campaign_asset`

Remove an asset link from a Google Ads campaign. The asset itself is not deleted — only the association with the campaign is removed.

### `google_create_and_link_asset`

Create a new asset and immediately link it to a campaign in one step. Combines google_create_asset + google_link_campaign_asset. Provide the campaign_id and all asset fields. The field_type is auto-determined from the asset type — IMAGE assets are automatically linked as AD_IMAGE.

## Google — Budgets

### `google_list_budgets`

List Google Ads campaign budgets for the current account. Returns budget ID, name, amount (in dollars), delivery method, status, and how many campaigns reference each budget. Note: pagination is not currently supported — all matching results up to the limit are returned in one response.

### `google_get_budget`

Get details of a specific Google Ads campaign budget by ID. Returns the budget name, amount in dollars, delivery method, status, and reference count.

### `google_create_budget`

Create a new standalone Google Ads campaign budget. The amount is in dollars (e.g., 50 = $50/day). Budgets can be shared across multiple campaigns.

### `google_update_budget`

Update an existing Google Ads campaign budget. You can change the name or amount. Changes apply to all campaigns using this budget.

### `google_delete_budget`

Delete a Google Ads campaign budget. The budget must not be referenced by any campaigns. WARNING: This cannot be undone.

## Google — Remarketing

### `google_list_user_lists`

List Google Ads user lists (audiences) for remarketing and customer match. Returns list ID, name, type, search audience size, membership status, and match rate.

### `google_get_user_list`

Get details of a specific Google Ads user list by ID. Returns list name, type, audience size, membership status, and match rate percentage.

### `google_create_user_list`

Create a new Google Ads user list for customer match. Provide a name, optional description, and membership lifespan. After creation, upload members with google_upload_user_list_members.

### `google_delete_user_list`

Close a Google Ads user list. This sets the membership status to CLOSED so no new users can be added. Existing members will expire based on the membership lifespan.

### `google_upload_user_list_members`

Upload members to a Google Ads user list for customer match. Members must be pre-hashed using SHA-256 (lowercase, trimmed). Returns the number of received operations.

### `google_remove_user_list_members`

Remove members from a Google Ads user list. Members must be identified using the same hashed format as they were uploaded with.

## Google — Reporting

### `google_get_change_history`

Get the change history for a Google Ads account. Shows who made what changes and when. Requires a date range. Optionally filter by resource type (CAMPAIGN, AD_GROUP, AD, etc.).

### `google_list_recommendations`

List Google Ads optimization recommendations for the account. Recommendations suggest improvements to campaigns, budgets, keywords, and ads. Filter by type for specific suggestions.

### `google_apply_recommendation`

Apply a Google Ads recommendation. This implements the suggested change (e.g., adding a keyword, adjusting a budget). The recommendation is permanently consumed after applying.

### `google_dismiss_recommendation`

Dismiss a Google Ads recommendation. This hides the recommendation from future queries. Dismissed recommendations may reappear if conditions change.

### `google_get_quality_scores`

Get keyword quality scores for an ad group. Returns quality score (1-10), creative quality, post-click experience quality, and expected CTR for each keyword.

## Google — Performance Max

### `google_list_asset_groups`

List asset groups for a Google Ads Performance Max campaign. Asset groups contain the targeting signals, assets, and final URLs for PMax campaigns. Requires a campaign_id.

### `google_get_asset_group`

Get details of a specific Google Ads asset group by ID. Returns name, status, campaign, final URLs, and display URL paths.

### `google_create_asset_group`

Create a new asset group for a Performance Max campaign. Requires a campaign_id, name, and at least one final URL. Defaults to PAUSED status.

### `google_update_asset_group`

Update an existing Google Ads asset group. You can change the name, status, final URLs, or display URL paths.

### `google_delete_asset_group`

Delete a Google Ads asset group. This sets the status to REMOVED. WARNING: This cannot be undone.

### `google_list_asset_group_assets`

List assets linked to a specific asset group. Returns asset IDs, field types (HEADLINE, DESCRIPTION, MARKETING_IMAGE, etc.), and link status.

### `google_link_asset_to_group`

Link an existing asset to a Performance Max asset group. The asset must already exist. Use google_create_asset to create one first, then link it here.

### `google_unlink_asset_from_group`

Remove (unlink) an asset from a Performance Max asset group. The asset itself is not deleted — only the association with the asset group is removed. You must pass the same field_type the asset was linked under, since the same asset can be linked under multiple field types.

## Google — Experiments

### `google_list_experiments`

List Google Ads experiments (A/B tests) for the current account. Returns experiment ID, name, status, type, date range, and associated campaign.

### `google_get_experiment`

Get details of a specific Google Ads experiment by ID. Returns name, description, status, type, goals, date range, and traffic split.

### `google_create_experiment`

Create and start a new Google Ads experiment (A/B test) for a campaign. This creates the experiment, sets up the control arm (the base campaign) and a treatment arm with the traffic split, then schedules (starts) it. Specify a campaign_id, name, traffic split, and optional date range.

### `google_update_experiment`

Update an existing Google Ads experiment. You can change the name, description, or dates. Only experiments in SETUP status can be fully edited.

### `google_delete_experiment`

Delete a Google Ads experiment. This removes the experiment and its associated draft campaign. WARNING: This cannot be undone.

### `google_promote_experiment`

Promote a Google Ads experiment to replace the original campaign. This makes the experiment arm the new primary campaign and removes the original. WARNING: This cannot be undone.

### `google_end_experiment`

End a running Google Ads experiment. This stops the experiment and reverts all traffic to the original campaign. Use this if the experiment is not performing well.

## TikTok — Campaigns

### `tiktok_list_campaigns`

List TikTok Ads campaigns for the current advertiser account. Supports filtering by status (ENABLE, DISABLE, DELETE) and pagination. Returns campaign ID, name, status, objective, budget, and budget mode.

### `tiktok_get_campaign`

Get details of a specific TikTok Ads campaign by ID. Returns name, status, objective, budget, budget mode, and creation time.

### `tiktok_create_campaign`

Create a new TikTok Ads campaign. You must specify a name and objective. Valid objectives: REACH, TRAFFIC, VIDEO_VIEWS, LEAD_GENERATION, CONVERSIONS, APP_INSTALL, PRODUCT_SALES. The campaign defaults to DISABLE status for safety.

### `tiktok_update_campaign`

Update an existing TikTok Ads campaign. You can change the name, status, budget, or budget mode. Only the fields you provide will be updated.

### `tiktok_pause_campaign`

Pause an active TikTok Ads campaign. This immediately stops ad delivery and spend. The campaign can be resumed later with tiktok_resume_campaign.

### `tiktok_resume_campaign`

Resume a paused TikTok Ads campaign. This reactivates ad delivery and spending. Make sure the campaign has ad groups and ads before resuming.

## TikTok — Ad Groups

### `tiktok_list_ad_groups`

List TikTok Ads ad groups within a specific campaign. Ad groups control targeting, budget, schedule, and optimization for a group of ads. Returns ad group ID, name, status, optimization goal, budget, and schedule.

### `tiktok_get_ad_group`

Get details of a specific TikTok Ads ad group by ID. Returns name, status, campaign ID, optimization goal, placement type, budget, bid price, and schedule.

### `tiktok_create_ad_group`

Create a new TikTok Ads ad group within a campaign. You must specify a name and optimization_goal (CLICK, IMPRESSION, REACH, CONVERSION, INSTALL, VIDEO_VIEW, LEAD_GENERATION). The ad group defaults to DISABLE status for safety.

### `tiktok_update_ad_group`

Update an existing TikTok Ads ad group. You can change the name, status, budget, bid price, or schedule. Only the fields you provide will be updated.

### `tiktok_delete_ad_group`

Delete a TikTok Ads ad group. This sets the ad group status to DELETE and stops all ads within it from delivering. WARNING: This cannot be easily undone.

## TikTok — Insights

### `tiktok_get_campaign_insights`

Get performance insights at the campaign level for standard (auction) TikTok Ads campaigns. Returns metrics like spend, impressions, clicks, CTR, CPC, CPM, conversions, and cost per conversion. Can filter by a specific campaign ID or get all campaigns. Defaults to the last 7 days. NOTE: This does NOT work for GMV Max campaigns — for GMV Max performance use tiktok_get_gmv_max_report instead.

### `tiktok_get_ad_group_insights`

Get performance insights at the ad group level from TikTok Ads. Returns metrics broken down by ad group. Can filter by campaign_id or ad_group_id. Defaults to the last 7 days.

### `tiktok_get_ad_insights`

Get performance insights at the individual ad level from TikTok Ads. Returns detailed metrics for each ad. Can filter by campaign_id, ad_group_id, or ad_id. Defaults to the last 7 days.

## TikTok — Marketing (accounts, creatives, GMV Max, Smart+, audiences, more)

### `tiktok_list_accounts`

List or look up TikTok Ads advertiser accounts connected to the Xylo organization. USE THIS FIRST for TikTok: it is the no-argument discovery entry point — call it with no args to see every TikTok ad account you have access to, with their advertiser_id values. USE THIS ANY TIME the user refers to a TikTok account by BRAND NAME (e.g. "Lambs&Ivy", "our client", "for <brand>") — pass the brand as `query` to resolve it to an advertiser_id. Every other TikTok tool (campaigns, ad groups, ads, insights, GMV Max, business centers, account info) requires an advertiser_id — resolve it here first. This is the TikTok equivalent of meta_list_accounts. Returns advertiser_id, name, status, billing_status, and `activated` (whether the account is activated on your plan). If `activated` is false, the account must be activated in the dashboard at https://xyloapi.dev/dashboard before campaign/insight tools will return data. Keywords: list TikTok accounts, find TikTok account, resolve brand to advertiser id, which TikTok account is <brand>, discover TikTok advertisers, TikTok ad account id, advertiser id resolver, TikTok account discovery, GMV Max account, account for brand.

### `tiktok_get_account_info`

Get TikTok Ads advertiser account info: name, currency, timezone, country, industry, balance. Use to check account details, currency, or status before creating campaigns. Resolve the advertiser_id first with tiktok_list_accounts.

### `tiktok_get_account_balance`

Get the current ad account balance for a TikTok advertiser. Returns balance and currency. Useful for checking funds before campaign launch.

### `tiktok_list_account_transactions`

List the TikTok ad account billing transactions over a date range. Useful for finance / accounting / spend reconciliation.

### `tiktok_list_business_centers`

List TikTok Business Centers that the authenticated app/user can access. A Business Center holds multiple ad accounts, pixels, catalogs, and identities.

### `tiktok_list_business_center_advertisers`

List all TikTok advertisers (ad accounts) inside a given TikTok Business Center.

### `tiktok_list_business_center_assets`

List assets in a TikTok Business Center. Filter by `type`: ADVERTISER (ad accounts), CATALOG, DOMAIN, LEAD, PIXEL, STOREFRONT, TIKTOK_SHOP, or TT_ACCOUNT (TikTok account / identity).

### `tiktok_list_identities`

List TikTok identities used for Spark Ads (TT_USER for own/auth'd TikTok users, BC_AUTH_TT for BC-owned accounts). Spark Ads need an identity_id; this is how you find one. CUSTOMIZED_USER is being phased out.

### `tiktok_authorize_tt_user`

Authorize a TikTok user (TT_USER identity) for Spark Ads via an auth_code obtained from the in-app TikTok auth flow. Use this so the advertiser can promote a creator's organic post as a Spark Ad.

### `tiktok_list_spark_posts`

List TikTok posts (item_ids) authorized for Spark Ads under a given identity. Use these item_ids when creating a Spark Ad creative.

### `tiktok_upload_video`

Upload a video to the TikTok Ad account library. Either pass video_url (TikTok will pull) or file_b64 + file_name for inline upload. Returns video_id used in ad creative.

### `tiktok_upload_image`

Upload an image creative to the TikTok Ad account library. Pass image_url or file_b64 + file_name.

### `tiktok_list_videos`

List videos in the TikTok Ad account library. Filter by video_ids or material_ids.

### `tiktok_list_images`

List images in the TikTok Ad account library.

### `tiktok_get_video_status`

Get transcoding status of one or more uploaded TikTok ad videos. Videos must finish processing before being usable in ad creatives.

### `tiktok_get_ad_media`

Fetch the downloadable creative media URLs (video and/or images) behind a TikTok ad. Download the ad video, get a video download link, save the ad creative, view the ad image, see what video an ad is running, export ad creatives. Works for regular ads built from uploaded library videos (returns preview_url, valid ~6 hours), Spark Ads built from TikTok posts (resolves the post via its identity; URL valid ~1 hour; photo posts return carousel image URLs), and image/carousel ads (temporary image URLs). Returns { ad_id, ad_format, media_source, assets: [{ type, source, download_url, download_url_expires_in, cover_url, width, height, duration_seconds, unavailable_reason }] }. IMPORTANT: download_url values are TEMPORARY signed links — download immediately, never store them. LIMITS: Spark posts owned by TikTok-Shop (TTS_TT) identities cannot be resolved here — for GMV Max campaign videos use tiktok_get_gmv_max_campaign_media instead. Keywords: download TikTok ad video, ad video download link, get ad creative file, export TikTok creative, save ad video, spark ad video url, ad media, creative download.

### `tiktok_get_video_covers`

Get auto-generated cover thumbnails for a TikTok ad video. Returns multiple cover URIs to choose from.

### `tiktok_list_smart_plus_campaigns`

List TikTok Smart+ auto-optimized campaigns (the Advantage+ / PMax equivalent). Smart+ replaced SPC after 2026-03-31.

### `tiktok_create_smart_plus_campaign`

Create a TikTok Smart+ (auto-optimized) campaign. Objectives: WEB_CONVERSIONS, APP_PROMOTION, LEAD_GENERATION.

### `tiktok_update_smart_plus_campaign`

Update a TikTok Smart+ campaign — change name, status, or budget.

### `tiktok_create_smart_plus_ad_group`

Create a Smart+ ad group. Requires optimization_goal, promotion_type, and billing_event; targeting (locations, age, gender, languages, OS) is nested into the required targeting_spec automatically. schedule_type defaults to SCHEDULE_FROM_NOW.

### `tiktok_create_smart_plus_ad`

Create a Smart+ ad. Requires ad_name. Creative is supplied via list-shaped fields: creative_list ([{creative_info}]), ad_text_list ([{ad_text}]), call_to_action_list ([{call_to_action}]), page_list ([{page_id}]), landing_page_url_list ([{landing_page_url}]). There is no flat "creatives" field.

### `tiktok_get_smart_plus_material_report`

Get Smart+ creative-level material performance report (which video/asset is driving spend, impressions, conversions). report_type=overview (aggregate, default) or breakdown (time-series, requires start_date+end_date). dimensions is required.

### `tiktok_list_gmv_max_campaigns`

List TikTok GMV Max campaigns — automated TikTok Shop campaigns that bid toward an ROI (ROAS) target to maximize gross merchandise value (GMV). Filter by promotion_types (PRODUCT_GMV_MAX, LIVE_GMV_MAX).

### `tiktok_get_gmv_max_campaign`

Get a single TikTok GMV Max campaign by ID, including its status, objective, and ROI protection status.

### `tiktok_get_gmv_max_bid_recommendation`

Get TikTok's recommended ROI target (ROAS) and daily budget for a GMV Max campaign on a TikTok Shop. Use these values as starting points when creating a GMV Max campaign.

### `tiktok_create_gmv_max_campaign`

Create a TikTok GMV Max campaign (automated TikTok Shop campaign). Requires a TikTok Shop ID, the Business Center that owns the shop, an ROI target (roas_bid), and a daily budget. shopping_ads_type is PRODUCT or LIVE.

### `tiktok_update_gmv_max_campaign`

Update a TikTok GMV Max campaign: change the ROI target (roas_bid) or the daily budget. To enable, disable, or delete the campaign, use tiktok_update_gmv_max_campaign_status instead.

### `tiktok_update_gmv_max_campaign_status`

Enable, disable (pause), or delete a TikTok GMV Max campaign.

### `tiktok_get_gmv_max_report`

Run a TikTok GMV Max campaign performance report (insights) — gross revenue, ROI, cost, orders, and (at the creative/post level) impressions, clicks, click rates, conversion rate, and video-view rates for GMV Max campaigns. This is the ONLY way to get GMV Max performance data; the standard tiktok_get_campaign_insights does not work for GMV Max campaigns. BREAK DOWN BY CREATIVE/POST: set dimensions to "item_id" (item_id is the TikTok POST ID, i.e. the video/creative). NOTE: a creative-level (item_id) report REQUIRES both the campaign_ids and item_group_ids filters — TikTok rejects it otherwise. Get a campaign_id from tiktok_list_gmv_max_campaigns and item_group_id (product SPU) values from tiktok_list_gmv_max_posts (spu_id_list) or a product-level report. BREAK DOWN BY CREATOR: there is no standalone creator dimension, but every creative-level (item_id) row carries tt_account_name (the creator's DISPLAY NAME — a label like "mechanic", NOT the @handle) + tt_account_profile_image_url — request item_id with tt_account_name + gross_revenue/orders and group by tt_account_name to reproduce the Ads Manager "Creators" tab. IMPORTANT: tt_account_name and title are ATTRIBUTE fields that TikTok only returns when you scope to a SINGLE campaign_ids value AND a SINGLE item_group_ids value; pass multiple campaign or product IDs and they come back as "-". To get a clickable creator profile_url (the real @handle), use tiktok_list_gmv_max_creators (the report does not expose @handles). For the roster of available posts/creators incl. authorization status, use tiktok_list_gmv_max_posts and tiktok_list_gmv_max_creators. BREAK DOWN BY PRODUCT: set dimensions to "item_group_id" (the product SPU) AND include product_name (and optionally product_image_url) in metrics to get the human-readable product TITLE + thumbnail alongside cost/orders/gross_revenue/roi. Omit product_name and you only get the bare numeric SPU ID — so ALWAYS request product_name for a product breakdown. This works straight from the ads API; no TikTok Shop connection is required. A product-level (item_group_id) report REQUIRES a campaign_ids filter (TikTok rejects "item level" reports with no campaign ID — pass a campaign_id from tiktok_list_gmv_max_campaigns); do NOT also put campaign_id in dimensions (product_name is an attribute metric incompatible with the campaign_id dimension). store_ids comes from tiktok_list_gmv_max_campaigns (each campaign includes its store_id) or tiktok_list_gmv_max_stores. Keywords: GMV Max report, GMV Max insights, by creator, by creative, by post, by video, per-creator gross revenue, per-post performance, creative breakdown, top creators, top posts, which creator drove sales.

### `tiktok_list_gmv_max_stores`

List the TikTok Shops an ad account can use for GMV Max campaigns. Returns each shop's store_id, store_authorized_bc_id, store_name, store_status, and is_gmv_max_available. USE THIS FIRST to obtain the store_id + store_authorized_bc_id required by tiktok_list_gmv_max_posts and tiktok_list_gmv_max_creators. Keywords: GMV Max shops, TikTok Shops for GMV Max, store id, business center id for shop, which shops support GMV Max, GMV Max store list.

### `tiktok_list_gmv_max_creators`

List the creators/identities (TikTok accounts) associated with a TikTok Shop and whether each is available for Product or LIVE GMV Max campaigns. This is the roster behind the "Creators" tab of Ads Manager GMV Max creative reporting: user_name (the real @handle), display_name (label), profile_image, identity_id, identity_type, product_gmv_max_available, live_gmv_max_available, is_running_custom_shop_ads, unavailable_reason. When TikTok returns a user_name, Xylo adds a derived profile_url (https://www.tiktok.com/@<user_name>) — this is the only GMV Max endpoint that exposes the actual @handle (present for BC-authorized creators; the shop's own TTS_TT identity has no user_name). The report and posts tools only carry display names, not @handles, so a profile link can't be built there. Get store_id + store_authorized_bc_id from tiktok_list_gmv_max_stores. NOTE: for per-creator GROSS REVENUE / performance, use tiktok_get_gmv_max_report grouped by item_id with tt_account_name — this tool lists who the creators are, not their sales. Keywords: GMV Max creators, list creators, creator identities, which creators, authorized creators, TikTok accounts for GMV Max, creator availability, identity list, creator profile link, tiktok profile url.

### `tiktok_list_gmv_max_posts`

List the TikTok posts (videos) available to a Product GMV Max campaign for a given TikTok Shop — the roster behind the "Posts" tab of Ads Manager GMV Max creative reporting. Each post returns item_id (post ID), text (caption), associated product spu_id_list, can_change_anchor, and creator identity_info (display_name + profile_image). Sort by GMV, post time, views, likes, CTR, or product clicks. Get store_id + store_authorized_bc_id from tiktok_list_gmv_max_stores. IMPORTANT: TikTok returns an EMPTY list unless you scope creators — pass identity_list (from tiktok_list_gmv_max_creators) and/or set need_auth_code_video="true". For per-post GROSS REVENUE / performance, use tiktok_get_gmv_max_report grouped by item_id. Keywords: GMV Max posts, GMV Max videos, available posts, authorized videos, posts by creator, sort posts by GMV, top videos, which videos.

### `tiktok_get_gmv_max_campaign_media`

Fetch downloadable VIDEO URLs for the TikTok posts a GMV Max campaign is running — download GMV Max ad videos, get video download links for GMV Max creatives, save/export the videos a GMV Max campaign uses, see which creator videos a campaign ran. HOW IT WORKS: resolves which posts the campaign actually delivered via the GMV Max creative report, then fetches each post's temporary video URL from the GMV Max posts roster. Each post returns: item_id, caption, creator_display_name, video_id, download_url (signed URL, valid ~6 HOURS — download immediately, never store), cover_url (~24h), width/height/duration, plus cost / gross_revenue / creative_delivery_status from the report. Posts that are removed, private, rejected, or no longer authorized come back with download_url null and an unavailable_reason. Defaults to the last 365 days of delivery; pass start_date/end_date to narrow, or item_ids (comma-separated post IDs, e.g. from tiktok_get_gmv_max_report) to skip the report step and resolve specific posts directly. FALLBACK: when posts come back with download_url null + an unavailable_reason (common — the ads API only serves posts whose creator identity grants roster access), each post still includes its public post_url and item_id: feed those to tiktok_download_post_video, which downloads the public post and returns a STABLE re-hosted URL. For regular (non-GMV-Max) ads use tiktok_get_ad_media. Keywords: download GMV Max video, GMV Max ad video download link, GMV Max creative download, export GMV Max posts, save campaign videos, TikTok Shop campaign videos, creator video download, GMV Max media.

### `tiktok_download_post_video`

Download the video of any PUBLIC TikTok post and get back a STABLE re-hosted download URL (no expiry, shareable). Download a TikTok video, save a TikTok post, get an mp4 of a TikTok video, download creator videos, grab the video behind a TikTok link. Pass `post` as either a TikTok post ID (e.g. "7594956518185782559") or any tiktok.com post URL. Xylo fetches the public post page server-side, downloads the video file, re-hosts it, and returns { download_url, post_url, author_handle, caption, duration_seconds, width, height, size_bytes, cover_url, cached }. Results are cached per post — repeat calls are instant. USE THIS AS THE FALLBACK when tiktok_get_ad_media or tiktok_get_gmv_max_campaign_media return download_url null with an unavailable_reason (GMV Max creator/affiliate posts usually are NOT accessible via the ads API — this tool is the reliable path for them; take item_id or post_url from those responses). Only works for public posts; private/deleted posts return a clear error. One post per call — loop for batches and expect a few seconds per video. Keywords: download TikTok video, TikTok video downloader, save TikTok post video, GMV Max video download, creator post mp4, download video from TikTok link, export TikTok post.

### `tiktok_list_catalogs`

List TikTok product catalogs in a Business Center. Catalogs power Dynamic Showcase Ads (DSA).

### `tiktok_create_catalog`

Create a TikTok product catalog for Dynamic Showcase Ads (DSA). catalog_type: AUTO, DESTINATION, ECOM, ENTERTAINMENT, FLIGHT, HOTEL. region_code (e.g. US) and currency (e.g. USD) are required.

### `tiktok_upload_catalog_products`

Upload/upsert products into a TikTok catalog for Dynamic Showcase Ads from a hosted feed file. TikTok pulls products from a CSV/feed URL — inline product arrays are not supported. Pass file_url (the publicly reachable feed) and optional update_mode (INCREMENTAL default, or OVERWRITE).

### `tiktok_list_catalog_products`

List products inside a TikTok catalog.

### `tiktok_create_product_set`

Create a Product Set inside a TikTok catalog (used to scope which products a DSA ad group promotes). Pass a filter spec.

### `tiktok_create_catalog_feed`

Hook a TikTok catalog up to an auto-updating data feed (CSV/XML/JSON hosted at source_uri). TikTok pulls updates on the schedule you set. feed_name and update_mode are required.

### `tiktok_list_pixels`

List TikTok pixels for the advertiser. Used for web conversion tracking + Events API.

### `tiktok_create_pixel`

Create a new TikTok pixel for website conversion tracking.

### `tiktok_track_conversion_event`

Send conversion events to TikTok via the Events API 2.0 (server-side tracking, the TikTok equivalent of Meta Conversions API / CAPI). Pass `event_source`: web | app | offline | crm and an `events` array. PII (email/phone) is automatically SHA-256 hashed.

### `tiktok_list_pixel_events`

List the events configured on a TikTok pixel (Purchase, AddToCart, etc.) and their match-rate stats.

### `tiktok_create_pixel_event`

Register a custom event on a TikTok pixel (for example mapping a non-standard event_name to an optimization_event).

### `tiktok_create_customer_file_audience`

Create a TikTok custom audience from a CRM file — pass an array of emails, phones, IDFAs, or GAIDs. Xylo SHA-256 hashes them automatically (set already_hashed=true to skip hashing).

### `tiktok_append_customer_file_audience`

Append more identifiers to an existing TikTok customer-file audience. Same hashing rules as creation.

### `tiktok_create_engagement_audience`

Create a TikTok engagement audience — users who watched videos or visited the profile for a given identity. Great for retargeting organic engagement.

### `tiktok_create_lookalike_audience`

Create a TikTok lookalike audience from a source audience. type: NARROW (most similar, smallest), BALANCED, or BROAD (largest reach).

### `tiktok_create_lead_audience`

Build a TikTok audience from users who submitted one or more Lead Forms (Instant Forms).

### `tiktok_list_saved_audiences`

List TikTok saved audiences (targeting templates) for the advertiser.

### `tiktok_share_audience`

Share a TikTok custom audience with other advertiser accounts (e.g. across agency-managed accounts).

### `tiktok_search_interests`

Search TikTok interest keywords for targeting. Returns interest_keyword_ids you can drop into an ad group's targeting.

### `tiktok_list_interest_categories`

List TikTok interest categories (broader than keywords) for targeting.

### `tiktok_list_behaviors`

List TikTok behavioral/action-category targeting categories (video-related and creator-related interactions).

### `tiktok_search_hashtags`

Search TikTok hashtags for targeting (hashtag-based behavioral targeting). Pass 1-10 keywords.

### `tiktok_search_locations`

Search TikTok geo locations for targeting — countries, states, cities, ZIPs, DMAs. Returns location_ids.

### `tiktok_estimate_audience_size`

Estimate TikTok audience reach for a targeting spec. Pass a full targeting object (locations, age, interests, etc.) to see the projected daily reach and impressions before launching.

### `tiktok_get_bid_recommendation`

Get a TikTok suggested bid for an ad group based on objective and target locations. The suggestion is only valid for Cost Cap bidding (bid_type=BID_TYPE_CUSTOM).

### `tiktok_create_async_report`

Create an async TikTok report task (for large/wide queries that would time out as sync). Poll with tiktok_check_async_report; download with tiktok_download_async_report.

### `tiktok_check_async_report`

Poll the status of an async TikTok report task. status: QUEUING, PROCESSING, SUCCESS, FAILED.

### `tiktok_download_async_report`

Get the download URL for a completed async TikTok report (CSV).

### `tiktok_get_integrated_report`

Get a synchronous TikTok integrated report with custom dimensions/metrics. Use when async overhead isn't needed.

### `tiktok_bulk_update_campaign_status`

Bulk pause / enable / delete TikTok campaigns by ID. TikTok caps each status request at 20 IDs; larger lists are automatically chunked into batches of 20, and a failed chunk does not stop the rest. RESPONSE: { success, requested, updated, updated_ids, failed: [{id, error}] } — report the counts; anything in failed[] definitively did not change (safe to re-run for just those ids, status changes are idempotent).

### `tiktok_bulk_update_ad_group_status`

Bulk pause / enable / delete TikTok ad groups by ID. TikTok caps each status request at 20 IDs; larger lists are automatically chunked into batches of 20, and a failed chunk does not stop the rest. RESPONSE: { success, requested, updated, updated_ids, failed: [{id, error}] } — report the counts; anything in failed[] definitively did not change (safe to re-run for just those ids, status changes are idempotent).

### `tiktok_bulk_update_ad_status`

Bulk pause / enable / delete TikTok ads by ID. TikTok caps each status request at 20 IDs; larger lists are automatically chunked into batches of 20, and a failed chunk does not stop the rest. RESPONSE: { success, requested, updated, updated_ids, failed: [{id, error}] } — report the counts; anything in failed[] definitively did not change (safe to re-run for just those ids, status changes are idempotent).

### `tiktok_bulk_create_ads`

Bulk create up to ~20 TikTok ads inside one ad group in a single API call. Each creative entry typically has: ad_name, ad_format, identity_type, identity_id, video_id (or image_ids[]), ad_text, call_to_action, landing_page_url. Which fields are required depends on ad_format (TikTok enforces this server-side), so this schema stays permissive.

### `tiktok_bulk_update_campaigns`

Bulk update fields on many TikTok campaigns. Each entry needs campaign_id; settable fields include campaign_name, budget. Pass operation_status (ENABLE/DISABLE/DELETE) to also change status — it is routed to the dedicated status endpoint. Each campaign is updated individually; the response reports per-campaign success/failure.

### `tiktok_bulk_update_ad_groups`

Bulk update many TikTok ad groups (budgets, bids, schedules, targeting). Each entry needs adgroup_id. Pass operation_status (ENABLE/DISABLE/DELETE) to also change status — it is routed to the dedicated status endpoint. (budget_mode is not an updatable field and is ignored.) Each ad group is updated individually; the response reports per-ad-group success/failure.

### `tiktok_list_ad_comments`

List comments on the advertiser's own TikTok video ads, scoped to one ad group over a time window. Filter by comment_status (ALL/HIDDEN/PUBLIC) and comment_type (ALL/COMMENT/REPLY). Use to monitor reactions and reply.

### `tiktok_reply_to_ad_comment`

Reply to a first-level comment on a TikTok ad (only on the advertiser's own ads). Requires the originating ad_id + tiktok_item_id and the identity that owns the ad.

### `tiktok_hide_ad_comment`

Hide a TikTok ad comment (commenter sees their comment, others don't).

### `tiktok_delete_ad_comment`

Delete a comment on a TikTok ad. Requires the originating ad_id + tiktok_item_id and the identity that owns the ad.

### `tiktok_like_ad_comment`

Like a comment on the advertiser's TikTok ad (boosts perceived engagement).

### `tiktok_update_comment_settings`

Configure TikTok ad comment moderation: enable/disable comments globally, set auto-hide keywords, block specific user IDs.

### `tiktok_list_lead_forms`

List TikTok Instant Forms (Lead Generation forms) attached to the advertiser. No CRUD — forms are created in TikTok Ads Manager UI.

### `tiktok_list_lead_submissions`

Fetch submissions for a TikTok Instant Form (Lead Gen). Production deployments should subscribe to the LEAD webhook for real-time delivery.

### `tiktok_download_leads_csv`

Generate a CSV download URL for all submissions of a TikTok Instant Form within a time range.

### `tiktok_search_tcm_creators`

Search creators in the TikTok Creator Marketplace (TTCM) by keyword, follower range, age, gender, location, category. Used to find influencers for branded content campaigns.

### `tiktok_invite_tcm_creator`

Invite TikTok creators to a TTCM (Creator Marketplace) campaign for branded content. Pass campaign_id, creator_ids, optional budget/message.

### `tiktok_list_tcm_orders`

List TTCM influencer collaboration orders / contracts for a TikTok advertiser.

### `tiktok_approve_tcm_draft`

Approve a TTCM creator's draft video for a branded content order.

### `tiktok_reject_tcm_draft`

Reject a TTCM creator's draft video with required feedback.

## Creative Analysis

### `meta_analyze_ad_creative`

Analyze a Meta ad creative (video or image) AND explain it against real delivery data. Returns ~30 structured fields — creative content, hook breakdown, production fingerprint, timing markers (videos), psychology (awareness level, life-force-8, mindstate × regulatory focus, dominant emotion), conversion architecture — PLUS:
1. retention_analysis (videos, by ad_id): pulls hook rate, hold rate, and the 25/50/75/100% drop-off curve and maps WHERE viewers leave to WHAT is on screen, with a concrete retention fix. Explains why people scroll past or stop watching.
2. A KPI-anchored verdict: reads what the ad set OPTIMIZES for (leads, purchases, …) and judges the ad on its cost-per-result vs the account average — not on completion rate or ROAS.
3. interpretation: how to read the result + what to tell the user, plus factual callouts (missing CTA, no captions, weak product visibility, etc.).
Accepts ad_id (preferred — unlocks retention + KPI) or creative_id. Carousels analyze one card via card_index. MULTI-MEDIA ADS (Meta "multi-media": up to 10 images+videos in ONE ad): each call analyzes ONE asset, scored on its OWN delivery (per-media spend/CTR/CPA via the video_asset breakdown) — never the ad`s blended numbers. Defaults to the primary asset; pass media_index (0-based) to analyze another. The response`s multi_media block lists every asset (media_total + media_roster) so you can loop them, and carries ad_level_retention — the ad`s blended 25–100% curve, which Meta does NOT break out per media (only 3-sec plays do). To compare the assets of one multi-media ad, call once per media_index and diff the per-media verdicts.
Use when the user asks: analyze this ad, what makes this creative work, why are people scrolling past, where do viewers drop off, why is this ad winning/losing, should I scale this creative.

FOLLOW the interpretation block in the response: with several winning ads analyzed, diff their structured fields to surface the shared "winning DNA"; for new ideas, hold that DNA constant and vary one element at a time with real crafted copy.

Bulk-analysis guidance: Before fanning this tool out across many creatives in
an account, sort by spend descending and filter to roughly the top 90% of
cumulative spend (the Pareto set). Analyzing low-spend ads ($5–$20 lifetime)
wastes inference cost and produces non-actionable diagnoses because volume is
below the confidence floor (5,000 impressions / $30 spend). For a 200-ad
account this typically reduces analyses to 20–40 ads while covering 90%+ of
the account's actual ROI surface.

Can't see: DPA/Catalog ads (no single creative → UNSUPPORTED_ASSET_TYPE), comment sentiment, or audience saturation root cause. For bucket-style "is it fatigued / kill or scale" diagnosis, use meta_diagnose_ad_creative.

To turn this analysis into actual variations or net-new concepts, call creative_frameworks (the expert creative brain — hooks, scripting, copywriting, metadeception, open-loop retention) and apply it to craft real, original creative. Never delegatethe writing to platform text generation.

### `google_analyze_ad_creative`

Analyze a Google Ads asset (image or YouTube video) and return ~30 structured fields. Accepts an asset resource_name (customers/{cust}/assets/{id}). YouTube videos are ingested natively by Gemini (no byte download). Image assets are downloaded and cached. Same field schema as meta_analyze_ad_creative.

Bulk-analysis guidance: Before fanning this tool out across many creatives in
an account, sort by spend descending and filter to roughly the top 90% of
cumulative spend (the Pareto set). Analyzing low-spend ads ($5–$20 lifetime)
wastes inference cost and produces non-actionable diagnoses because volume is
below the confidence floor (5,000 impressions / $30 spend). For a 200-ad
account this typically reduces analyses to 20–40 ads while covering 90%+ of
the account's actual ROI surface.

What this analysis can't see:
- DPA / Catalog ads don't have a single analyzable creative — performance is
  feed-driven. This tool returns UNSUPPORTED_ASSET_TYPE for those.
- A/B variant comparison: describes a single creative; does not compare
  variants. Call the tool on each ad and diff the structured fields.
- Comment sentiment, share patterns, save behavior: not included.
- Frequency, fatigue, or delivery signals: not included. Use the corresponding
  {platform}_diagnose_ad_creative tool for those (ships separately).

To turn this analysis into actual variations or net-new concepts, call creative_frameworks (the expert creative brain — hooks, scripting, copywriting, metadeception, open-loop retention) and apply it to craft real, original creative. Never delegatethe writing to platform text generation.

### `tiktok_analyze_ad_creative`

Analyze a TikTok ad video and return ~30 structured fields. Accepts either ad_id or video_id. TikTok single-image creatives are not supported in v1 (returns UNSUPPORTED_ASSET_TYPE). Same field schema as meta_analyze_ad_creative.

Bulk-analysis guidance: Before fanning this tool out across many creatives in
an account, sort by spend descending and filter to roughly the top 90% of
cumulative spend (the Pareto set). Analyzing low-spend ads ($5–$20 lifetime)
wastes inference cost and produces non-actionable diagnoses because volume is
below the confidence floor (5,000 impressions / $30 spend). For a 200-ad
account this typically reduces analyses to 20–40 ads while covering 90%+ of
the account's actual ROI surface.

What this analysis can't see:
- DPA / Catalog ads don't have a single analyzable creative — performance is
  feed-driven. This tool returns UNSUPPORTED_ASSET_TYPE for those.
- A/B variant comparison: describes a single creative; does not compare
  variants. Call the tool on each ad and diff the structured fields.
- Comment sentiment, share patterns, save behavior: not included.
- Frequency, fatigue, or delivery signals: not included. Use the corresponding
  {platform}_diagnose_ad_creative tool for those (ships separately).

To turn this analysis into actual variations or net-new concepts, call creative_frameworks (the expert creative brain — hooks, scripting, copywriting, metadeception, open-loop retention) and apply it to craft real, original creative. Never delegatethe writing to platform text generation.

### `xylo_analyze_creative_url`

Analyze any image or video URL — Meta, Google, TikTok, or arbitrary third-party. Use this when you already have a direct asset URL and want to skip platform API lookup. Returns the same ~30-field schema as the platform-specific analyze tools. URL is SSRF-validated (no localhost, no private IPs, no file://).

Bulk-analysis guidance: Before fanning this tool out across many creatives in
an account, sort by spend descending and filter to roughly the top 90% of
cumulative spend (the Pareto set). Analyzing low-spend ads ($5–$20 lifetime)
wastes inference cost and produces non-actionable diagnoses because volume is
below the confidence floor (5,000 impressions / $30 spend). For a 200-ad
account this typically reduces analyses to 20–40 ads while covering 90%+ of
the account's actual ROI surface.

What this analysis can't see:
- DPA / Catalog ads don't have a single analyzable creative — performance is
  feed-driven. This tool returns UNSUPPORTED_ASSET_TYPE for those.
- A/B variant comparison: describes a single creative; does not compare
  variants. Call the tool on each ad and diff the structured fields.
- Comment sentiment, share patterns, save behavior: not included.
- Frequency, fatigue, or delivery signals: not included. Use the corresponding
  {platform}_diagnose_ad_creative tool for those (ships separately).

To turn this analysis into actual variations or net-new concepts, call creative_frameworks (the expert creative brain — hooks, scripting, copywriting, metadeception, open-loop retention) and apply it to craft real, original creative. Never delegatethe writing to platform text generation.

## Creative Diagnostic

### `meta_diagnose_ad_creative`

Diagnose WHY a Meta ad is failing or winning. Combines structured creative analysis (hook breakdown, awareness level, psychology, conversion architecture) with delivery signals (hook_rate, hold_rate, frequency, CPM, CTR, ROAS) and returns a bucket diagnosis: wrong_audience_or_fatigue | poor_hook | good_hook_poor_hold | good_creative_weak_cta | average_everything. Each bucket comes with a headline, fix_summary, and concrete fix_actions that reference specific creative fields. Auto-bootstraps the creative analyzer on cache miss — you do not need to call meta_analyze_ad_creative first. Use this when the user asks: why is this ad failing, why is this creative working, is this ad fatigued, is the audience saturated, why is CTR low on this ad, should we kill or scale this ad, diagnose this creative, what should I fix on this ad.

Bulk-analysis guidance: Before fanning this tool out across many creatives in
an account, sort by spend descending and filter to roughly the top 90% of
cumulative spend (the Pareto set). Diagnosing low-spend ads ($5–$20 lifetime)
wastes compute and produces non-actionable diagnoses because volume is below
the confidence floor (5,000 impressions / $30 spend). For a 200-ad account
this typically reduces diagnoses to 20–40 ads while covering 90%+ of the
account's actual ROI surface.

What this diagnostic can't see:
- Audience saturation root cause (size, lookalike quality, market exhaustion).
  The wrong_audience_or_fatigue bucket flags the pattern but does not
  diagnose the underlying audience problem — use targeting tools for that.
- Attribution + multi-touch effects. ROAS is platform-attributed (single-touch,
  click-through dominant). Use cross_platform_insights or a server-side
  attribution layer for true incrementality.
- Below 1,000 impressions: returns confidence "low" (informational only —
  bucket thresholds aren't statistically stable at low volume). High-confidence
  diagnoses require ≥5,000 impressions and ≥$30 spend over the date range.
- DPA / Catalog ads: returns UNSUPPORTED_ASSET_TYPE (feed-driven, not
  creative-driven).

For the rewrite, pull the matching creative_frameworks topic: poor_hook -> "hooks";
good_hook_poor_hold -> "open_loops" + "scripting"; good_creative_weak_cta ->
"copywriting"; average_everything -> "core" + "hooks". Apply it with real crafted copy.

## Creative Frameworks

### `creative_frameworks`

Expert short-form ad-creative frameworks for GENERATING and IMPROVING creative — the companion to the analyze/diagnose tools. Call this when you need to: write ad variations, rewrite or build a hook, write a UGC / ad script, fix a salesy or "this is an ad"-feeling creative, raise watch-time / retention / hold rate, generate net-new ad concepts, sharpen ad copy, or make a creative feel native and scroll-stopping. Aggregates proven frameworks: the 8-step creative diagnostic funnel, the 3-step hook formula, viral scripting & storytelling, Harry-Dry copywriting, the Metadeception resistance system, and the Zeigarnik open-loop retention system, plus a 100-item hook template bank, checklists, worked examples, and a truthfulness guardrail. Call this AFTER meta_analyze_ad_creative / meta_diagnose_ad_creative (or the Google/TikTok/URL analyzers) when moving from "what the creative IS" to "make it better." DEFAULTS to the complete brain — pass specific topics only to scope down for bulk/cost-sensitive calls.

## Competitor Intelligence — Meta

### `competitor_meta_ads`

See what Facebook & Instagram ads a competitor (or any company) is currently running, straight from Meta's public Ad Library. Give a company domain (e.g. nike.com) or a Facebook page URL and get their live ads: ad copy, headlines, CTAs, landing pages, images and videos, and run dates. Use this for competitor research, ad spying, creative inspiration / swipe files, and to see a brand's current Meta advertising strategy. Keywords: competitor ads, spy on competitor Facebook ads, what ads is a company running, Meta ad library, Instagram ads, competitive intelligence, ad creative research.

### `competitor_meta_ad_search`

Search Meta's public Ad Library by keyword to discover ads running across many advertisers. Great for researching how a product category, offer, or angle is being advertised on Facebook & Instagram (e.g. search "webinar", "black friday", "meal kit"). Returns matching ads with copy, media, and advertiser info. Keywords: search Facebook ads by keyword, Meta ad library search, find ads by topic, category ad research, competitor angle research, ad inspiration.


## Competitor Intelligence — Google

### `competitor_google_ads`

See what Google ads a competitor (or any company) is running, from the Google Ads Transparency Center. Give a company domain (e.g. clay.com) and get their ads across Search, Display, and video — with format, creative previews, the transparency-center link, and first/last-seen dates. Use for competitor research, ad spying, and understanding a brand's Google advertising. Keywords: competitor Google ads, Google ad transparency, spy on Google ads, what Google ads is a company running, search ads research, display ads.


## Competitor Intelligence — TikTok

### `competitor_tiktok_ad_search`

Search TikTok's public ad library by keyword to discover ads and the advertisers running them. Returns matching ads with advertiser name, estimated audience, video and image creatives, and first/last-shown dates. Use each result's id with competitor_tiktok_ad_details for full targeting and creative info. Great for TikTok competitor research and creative inspiration. Keywords: TikTok ad library, search TikTok ads, competitor TikTok ads, spy on TikTok ads, TikTok creative research, trending TikTok ads.

### `competitor_tiktok_ad_details`

Get full details for a single TikTok ad by its id (from a competitor_tiktok_ad_search result): the creative (video and images), advertiser info, and targeting breakdown (age, gender, location, audience size). Use this to deep-dive a specific competitor TikTok ad. Keywords: TikTok ad details, TikTok ad targeting, competitor TikTok creative, TikTok ad breakdown.
