{
  "server": {
    "name": "AXO",
    "endpoint": "https://axoapp.ai/api/mcp",
    "transport": "streamable-http"
  },
  "note": "Public reference. Operator and diagnostic tools are available to connected agents at runtime but omitted here.",
  "toolCount": 281,
  "categories": [
    {
      "id": "analytics",
      "label": "Analytics & lift",
      "blurb": "Read performance, holdout lift, optimizer state, events, and sessions.",
      "tools": [
        "find_stalls",
        "get_ad_performance",
        "get_agent_usage",
        "get_ai_generations",
        "get_analytics",
        "get_backtest",
        "get_component_experiment_results",
        "get_component_performance",
        "get_content_experiment_results",
        "get_customer_ga4_report",
        "get_customer_gsc_report",
        "get_daypart_performance",
        "get_decision_outcomes",
        "get_events",
        "get_form_funnel",
        "get_growth_traffic_plan",
        "get_layout_experiment_results",
        "get_lift_report",
        "get_placement_report",
        "get_scorecard",
        "get_segment_performance",
        "get_sitemap_health",
        "get_tag_health",
        "get_trigger_lift",
        "get_variant_performance",
        "get_zone_exposure_report",
        "list_agent_activity",
        "list_ai_actions",
        "list_customer_analytics_connections"
      ]
    },
    {
      "id": "audience",
      "label": "Audience & segments",
      "blurb": "Query and build behavioral segments; look up, merge, and link profiles.",
      "tools": [
        "create_custom_segment",
        "create_segment",
        "delete_profile",
        "designate_optin_form",
        "explain_variant_exposure",
        "export_audience",
        "generate_audience_clusters",
        "get_profile",
        "get_segment_members",
        "get_segment_orchestration",
        "get_segment_thesis",
        "get_segments",
        "get_sessions",
        "link_identifier",
        "list_custom_segments",
        "list_dedup_candidates",
        "list_leads",
        "list_optin_forms",
        "merge_profiles",
        "mint_link_tokens",
        "preview_custom_segment",
        "recompute_custom_segment",
        "remove_optin_form",
        "search_profiles",
        "unlink_identifier",
        "update_profile",
        "update_segment"
      ]
    },
    {
      "id": "personalization",
      "label": "Placements & personalization",
      "blurb": "Author zones and variants, wire placements, and manage the render surface.",
      "tools": [
        "activate_component_placement",
        "approve_ad_theme_suggestion",
        "attach_asset_to_variant",
        "attach_campaign_component",
        "author_move",
        "clone_trigger_template",
        "close_campaign",
        "configure_product_reco",
        "create_campaign",
        "create_component_experiment",
        "create_component_placement",
        "create_content_experiment",
        "create_content_variant",
        "create_form",
        "create_layout_experiment",
        "create_offer",
        "create_page_config",
        "create_preview_share_link",
        "create_trigger",
        "create_trigger_template",
        "create_zone",
        "delete_campaign",
        "delete_component_placement",
        "delete_content_variant",
        "delete_form",
        "delete_offer",
        "delete_page_config",
        "delete_trigger",
        "delete_trigger_template",
        "delete_zone",
        "detach_campaign_component",
        "draft_content_variant",
        "duplicate_campaign",
        "end_layout_experiment",
        "get_campaign",
        "get_campaign_launch_plan",
        "get_component_experiment",
        "get_component_placement",
        "get_content_experiment",
        "get_layout_experiment",
        "get_optimizer",
        "get_trigger",
        "get_trigger_template",
        "get_zones",
        "install_lifecycle_recipe",
        "launch_campaign",
        "list_ad_theme_suggestions",
        "list_campaigns",
        "list_component_experiments",
        "list_component_placement_history",
        "list_component_placements",
        "list_content_experiments",
        "list_content_variant_revisions",
        "list_forms",
        "list_layout_experiments",
        "list_lifecycle_recipes",
        "list_lightbox_presets",
        "list_offers",
        "list_page_configs",
        "list_preview_share_links",
        "list_reviews",
        "list_trigger_fires",
        "list_trigger_templates",
        "list_triggers",
        "pause_campaign",
        "pause_component_placement",
        "place_lead_form",
        "place_offer",
        "preview_component_revision",
        "publish_content_variants",
        "reject_ad_theme_suggestion",
        "release_component_revision",
        "rename_component_placement",
        "request_review",
        "restore_component_placement",
        "restore_content_variant",
        "restore_content_variant_revision",
        "revoke_preview_share_link",
        "set_campaign_lifecycle",
        "ship_layout_winner",
        "suggest_donation_amounts",
        "suggest_placement_goals",
        "test_fire_trigger",
        "update_campaign",
        "update_component_experiment",
        "update_component_placement",
        "update_content_experiment",
        "update_content_variant",
        "update_form",
        "update_offer",
        "update_page_config",
        "update_trigger",
        "update_trigger_template",
        "update_zone"
      ]
    },
    {
      "id": "content",
      "label": "Content variants",
      "blurb": "Draft, edit, and version the content served into zones.",
      "tools": [
        "approve_component_revision",
        "approve_page",
        "approve_page_suggestion",
        "create_component",
        "create_page",
        "create_route",
        "delete_component",
        "dismiss_page_suggestion",
        "duplicate_component",
        "edit_component",
        "generate_component",
        "generate_page",
        "get_component",
        "get_content",
        "get_content_history",
        "get_page",
        "get_route_performance",
        "import_component_from_page",
        "import-claude-design-from-url",
        "list_components",
        "list_content_sources",
        "list_page_suggestions",
        "list_pages",
        "list_routes",
        "log_article",
        "publish_page",
        "rename_page",
        "retire_page",
        "save_component_revision",
        "update_component"
      ]
    },
    {
      "id": "catalog",
      "label": "Catalog & recommendations",
      "blurb": "Import products, manage the taxonomy, and configure recommendation zones.",
      "tools": [
        "get_catalog_import_status",
        "get_content_map",
        "get_site_taxonomy",
        "import_catalog",
        "list_catalog_filter_fields",
        "list_products",
        "list_reco_presets",
        "list_taxonomy_terms",
        "merge_categories",
        "reclassify_catalog_item",
        "reclassify_page",
        "rename_section"
      ]
    },
    {
      "id": "assets",
      "label": "Assets & brand",
      "blurb": "Generate images, manage the DAM, and extract brand guidelines.",
      "tools": [
        "extract_brand_guidelines",
        "generate_image",
        "get_asset",
        "import_figma_brand",
        "list_dam_connections",
        "list_image_gen_keys",
        "list_text_gen_keys",
        "register_dam_asset",
        "search_assets",
        "search_dam",
        "update_asset"
      ]
    },
    {
      "id": "integrations",
      "label": "Integrations & sync",
      "blurb": "Connect destinations, test them, and sync CRM/Salesforce data.",
      "tools": [
        "create_integration",
        "delete_integration",
        "get_integration",
        "get_integration_logs",
        "list_integrations",
        "pause_ad_campaign",
        "set_ad_campaign_budget",
        "sync_crm",
        "test_integration",
        "update_integration"
      ]
    },
    {
      "id": "site",
      "label": "Site & configuration",
      "blurb": "Site config, verticals, taxonomy, sitemap health, and page configs.",
      "tools": [
        "add_page_domain",
        "analyze_sitemap",
        "apply_vertical_pack",
        "batch_operations",
        "define_affinity_terms",
        "detect_tracking_params",
        "discover_sitemap",
        "generate_aeo_artifact",
        "generate_taxonomy",
        "get_aeo_artifact",
        "get_ai_visibility_cycle",
        "get_ai_visibility_status",
        "get_job",
        "get_page_domain",
        "get_site_allowance",
        "get_site_config",
        "get_vertical_pack",
        "list_aeo_artifacts",
        "list_aeo_findings",
        "list_jobs",
        "list_notifications",
        "list_verticals",
        "mark_notification_read",
        "remove_page_domain",
        "resolve_aeo_finding",
        "rotate_mcp_token",
        "run_aeo_audit",
        "run_ai_visibility_cycle",
        "set_publishing_mode",
        "update_site_config"
      ]
    },
    {
      "id": "crm",
      "label": "CRM & pipeline",
      "blurb": "Manage the customer-facing sales pipeline: opportunities, contacts, tasks.",
      "tools": [
        "get_crm_status",
        "get_salesforce_status",
        "get_target_account_matches",
        "import_target_accounts",
        "pipeline_acquire_claim",
        "pipeline_add_contact",
        "pipeline_add_note",
        "pipeline_add_task",
        "pipeline_begin_source_page",
        "pipeline_create_handoff",
        "pipeline_create_opportunity",
        "pipeline_get_claim",
        "pipeline_get_funnel",
        "pipeline_get_handoff",
        "pipeline_get_opportunity",
        "pipeline_get_source_page",
        "pipeline_list_assignees",
        "pipeline_list_deleted_opportunities",
        "pipeline_list_handoffs",
        "pipeline_list_opportunities",
        "pipeline_list_prospects",
        "pipeline_list_runs",
        "pipeline_list_source_checkpoints",
        "pipeline_list_tasks",
        "pipeline_log_activity",
        "pipeline_propose_funnel",
        "pipeline_release_claim",
        "pipeline_renew_claim",
        "pipeline_restore_opportunity",
        "pipeline_set_funnel",
        "pipeline_start_run",
        "pipeline_suggest_stage_move",
        "pipeline_update_handoff",
        "pipeline_update_opportunity",
        "pipeline_update_run",
        "pipeline_update_source_page",
        "pipeline_update_task",
        "trigger_salesforce_sync"
      ]
    }
  ],
  "tools": [
    {
      "name": "activate_component_placement",
      "title": "Activate component placement",
      "domain": "personalization",
      "scope": "write",
      "description": "Publish the reviewed placement version, including its binding, targeting and approved component revision. expected_version rejects intervening edits. This changes the customer site. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "add_page_domain",
      "title": "Add page domain",
      "domain": "site",
      "scope": "write",
      "description": "Start custom-domain onboarding for generated pages: the customer's subdomain (e.g. learn.brand.com) will serve published pages on THEIR domain. Returns DNS instructions: first an ownership TXT record, then a CNAME. Subdomains only (no apex). One domain per site.",
      "params": [
        {
          "name": "hostname",
          "type": "string",
          "required": true,
          "description": "Subdomain to attach, e.g. learn.your-domain.com"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "analyze_sitemap",
      "title": "Analyze sitemap",
      "domain": "site",
      "scope": "write",
      "description": "Analyze a semantic sitemap structure to optimize category mapping internally.",
      "params": [
        {
          "name": "options",
          "type": "object",
          "required": false,
          "description": "Configuration/tuning options for the analysis run"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "apply_vertical_pack",
      "title": "Apply vertical pack",
      "domain": "site",
      "scope": "write",
      "description": "Switch the current site to a vertical pack: seeds any missing segments/zones/variants and RETIRES the previous composition's pack segments (visible=false — out of the tag's scoring rules and dashboard lists; reversible, history kept, re-applying the old pack restores them). Existing customizations and archetype extensions are preserved unless archetype_extensions is passed explicitly (omit to keep, [] to clear). To ADD a pack alongside the current one instead of switching, include it in archetype_extensions rather than changing vertical. Re-applying the site's current vertical is a safe no-op refresh.",
      "params": [
        {
          "name": "vertical",
          "type": "\"ecommerce\" | \"home_services\" | \"saas\" | \"financial\" | \"healthcare\" | \"nonprofit\" | \"affiliate\" | \"content_media\" | \"education\"",
          "required": true,
          "description": ""
        },
        {
          "name": "archetype_extensions",
          "type": "string[]",
          "required": false,
          "description": "Secondary archetype ids layered on top of the primary (e.g. [\"ecommerce\"] on a content site). Omit to keep the site's current extensions; pass [] to clear."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "approve_ad_theme_suggestion",
      "title": "Approve ad theme suggestion",
      "domain": "personalization",
      "scope": "write",
      "description": "Approve a pending ad-theme suggestion into an active theme. Requires write authority and tenant approval policy permission; does not publish a content variant.",
      "params": [
        {
          "name": "suggestion_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "label",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "utm_content_value",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "approve_component_revision",
      "title": "Approve component revision",
      "domain": "content",
      "scope": "write",
      "description": "Approve a specific component revision for use by live placements. Does not itself activate any placement. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "revision_id",
          "type": "string",
          "required": true,
          "description": "revision id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "approve_page",
      "title": "Approve page",
      "domain": "content",
      "scope": "write",
      "description": "Approve the revision you reviewed. Pass revision_id from get_page; required for private drafts, including new drafts on published pages. A stale revision is refused. Approval does not publish.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page ID"
        },
        {
          "name": "revision_id",
          "type": "string",
          "required": false,
          "description": "Reviewed revision ID from get_page"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "approve_page_suggestion",
      "title": "Approve page suggestion",
      "domain": "content",
      "scope": "write",
      "description": "Accept a page suggestion: creates an UNPUBLISHED draft page from the proposed brief. Pass amendments to extend the brief. Generate and publish are separate steps.",
      "params": [
        {
          "name": "suggestion_id",
          "type": "string",
          "required": true,
          "description": "Suggestion (proposal) ID from list_page_suggestions"
        },
        {
          "name": "amendments",
          "type": "string",
          "required": false,
          "description": "Operator additions merged into the brief before generation"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "attach_asset_to_variant",
      "title": "Attach asset to variant",
      "domain": "personalization",
      "scope": "write",
      "description": "Attach a library asset to a content variant field — overwrites whatever image was on that field with the asset's blob_url. Also copies the asset's provenance (prompt, brief / reference / planner attribution, tags) into the variant's generated_metadata so the audit trail follows the image. Returns { ok, asset_id, variant_id, blob_url, field_key }. Use after search_assets surfaces an existing asset that matches what the operator wants — saves a generation call. The variant's image_url is also synced to the asset's blob_url so the runtime tag picks it up immediately.",
      "params": [
        {
          "name": "asset_id",
          "type": "string",
          "required": true,
          "description": "Asset id from search_assets (asset_<uuid> shape)."
        },
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "content_variants.id to attach to."
        },
        {
          "name": "field_key",
          "type": "string",
          "required": true,
          "description": "The payload field key on the variant to overwrite (e.g. \"image_url\", \"hero_image_url\")."
        },
        {
          "name": "alt",
          "type": "string",
          "required": false,
          "description": "Optional alt text override. When omitted, the variant's existing image_alt is preserved."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "attach_campaign_component",
      "title": "Attach campaign component",
      "domain": "personalization",
      "scope": "write",
      "description": "Attach an existing resource to a campaign's manifest by REFERENCE (never a copy). Idempotent on (campaign, kind, ref_id): a repeat call updates `required`/`ref_version` and returns created:false. The ref must exist in this site or the call 404s. Attaching a variant or trigger also sets its serving-path campaign_id when unset, and is REFUSED (409) when it already serves under another campaign - detach it there first. Live content needs confirm_serving_change. `required:false` components are reported but do not gate readiness. Any change voids an approval (response `stage_reset`).",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "kind",
          "type": "\"page\" | \"variant\" | \"trigger\" | \"offer\" | \"audience\" | \"experiment\" | \"provider_campaign\"",
          "required": true,
          "description": "What the ref_id names"
        },
        {
          "name": "ref_id",
          "type": "string",
          "required": true,
          "description": "The resource's own id: pages.id, content_variants.id, triggers.id, offers.id, custom_segments.id, a layout/component experiment id, or ad_platform_campaigns.id"
        },
        {
          "name": "required",
          "type": "boolean",
          "required": false,
          "description": "Gate readiness on this component (default true)"
        },
        {
          "name": "ref_version",
          "type": "string | null",
          "required": false,
          "description": "Optional pinned version (page revision id)"
        },
        {
          "name": "confirm_serving_change",
          "type": "boolean",
          "required": false,
          "description": "Required (true) when the variant is active or the trigger enabled: attaching puts live content under this campaign's window and audience (off the site if the campaign is not serving); detaching removes that gate so it serves to everyone. Without it the call returns 409 with details.reason = serving_change_requires_confirmation. Confirm with the operator first."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "author_move",
      "title": "Author move",
      "domain": "personalization",
      "scope": "write",
      "description": "Author a \"move object\" placement: relocate an EXISTING element on the page to a new location, WITHOUT changing its copy or injecting new content (pure rearrangement). Creates a source zone (element_selector = the element to move) + a move_object content variant whose payload carries the destination anchor + position. Use AXO skills with browser access, or the dashboard picker, to inspect the source and destination elements and obtain source_selector + dest_selector first. Target ONE segment via segment_id, OR all_visitors:true for everyone (the default — moves are usually global). CAUTION: once published this ships to the live site within ~60s. Review both selectors and the proposed placement before publishing.",
      "params": [
        {
          "name": "page_pattern",
          "type": "string",
          "required": true,
          "description": "URL pattern the move applies to (e.g. \"/\", \"/blog/*\")"
        },
        {
          "name": "source_selector",
          "type": "string",
          "required": true,
          "description": "CSS selector of the EXISTING element to relocate (the object to move)"
        },
        {
          "name": "dest_selector",
          "type": "string",
          "required": true,
          "description": "CSS selector of the destination anchor to move it next to / into"
        },
        {
          "name": "position",
          "type": "\"before\" | \"after\" | \"inside_start\" | \"inside_end\"",
          "required": false,
          "description": "Where to place the source relative to the destination. Default \"after\". (\"replace\" is intentionally not allowed for a move — it would destroy the destination.)"
        },
        {
          "name": "zone_key",
          "type": "string",
          "required": false,
          "description": "Zone identifier. Defaults to a generated \"move_*\" key."
        },
        {
          "name": "display_name",
          "type": "string",
          "required": false,
          "description": "Human-readable name for the move."
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": "Audience segment. Omit when all_visitors is true."
        },
        {
          "name": "all_visitors",
          "type": "boolean",
          "required": false,
          "description": "Apply to ALL visitors regardless of segment (default true for moves). When false, provide segment_id."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "batch_operations",
      "title": "Batch operations",
      "domain": "site",
      "scope": "write",
      "description": "Run up to 25 tool calls in a single request, in order. Use it whenever you would otherwise call the same tool many times (for example updating many content variants). Every operation is validated before any runs: if one names an unavailable tool or has invalid arguments, NOTHING runs and the errors are returned. It is not a transaction – if an operation fails while running, earlier ones stay applied and the result lists exactly which succeeded. Destructive tools and tools that issue credentials cannot be batched; call those individually. Pass dry_run: true to preview every operation without changing anything.",
      "params": [
        {
          "name": "operations",
          "type": "object[]",
          "required": true,
          "description": "The calls to make, in order. At most 25."
        },
        {
          "name": "stop_on_error",
          "type": "boolean",
          "required": false,
          "description": "Stop at the first operation that fails while running (default true). Set false to attempt every operation regardless."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "clone_trigger_template",
      "title": "Clone trigger template",
      "domain": "personalization",
      "scope": "write",
      "description": "Clone a system or site template into a new site-owned template.",
      "params": [
        {
          "name": "template_id",
          "type": "string",
          "required": true,
          "description": "Source template ID"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "Name for the clone"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "close_campaign",
      "title": "Close campaign",
      "domain": "personalization",
      "scope": "admin",
      "description": "Close a LAUNCHED or PAUSED campaign for good, as a durable job. Stops serving (status ended), pauses its running experiments (never ends them), then snapshots the OUTCOME into the campaign (get_campaign -> campaign.outcome) with every number labeled by evidence: causal (holdout lift per variant, offer and trigger; insufficient_evidence is reported as such, never as zero), observed (the goal's converting-session rate in the live window vs the same-length window before launch - not causal), and platform_attributed (ad spend and the platform's own conversions). Writes a campaign_closed row to the decision ledger. IRREVERSIBLE: a closed campaign is read-only and cannot be relaunched; use duplicate_campaign to run it again. Requires admin scope. Does not retire pages or pause ad platform campaigns (reported as needs_operator). Pass rationale to record why. Confirm with the operator first.",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "actor",
          "type": "string",
          "required": false,
          "description": "Who is closing (recorded on the job, audit trail and decision ledger)"
        },
        {
          "name": "rationale",
          "type": "string",
          "required": false,
          "description": "Why the campaign is being closed (recorded on the decision ledger)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "configure_product_reco",
      "title": "Configure product reco",
      "domain": "personalization",
      "scope": "write",
      "description": "Configure a product_reco variant: choose recommendation mode (auto / curated / mixed), retrieval strategy, catalog filters, catalog weights, and pinned products. A FILTER removes items (\"do not recommend anything with 10 or fewer in stock\"); a WEIGHT re-ranks them without hiding them (\"de-emphasise low stock\"). Reach for a weight whenever the ask is to show something less rather than not at all. For a cart slot, cart_gap prefers (or requires) items priced to close the gap to the cart's next offer threshold (free shipping, a gift, a tier). Call list_catalog_filter_fields first so field names are real. Use list_products to find product IDs.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Existing product_reco variant ID"
        },
        {
          "name": "source",
          "type": "\"auto\" | \"curated\" | \"mixed\"",
          "required": true,
          "description": "auto: Vibe Engine picks all products. curated: only show pinned product_ids. mixed: pinned products first, engine fills the rest."
        },
        {
          "name": "strategy",
          "type": "\"session_behavior\" | \"bestsellers\" | \"new_arrivals\" | \"similar\"",
          "required": false,
          "description": "Engine retrieval strategy (used when source=auto or mixed). Default session_behavior."
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": "Max products to render (default 5)."
        },
        {
          "name": "category",
          "type": "string",
          "required": false,
          "description": "Restrict candidates to this category (case-insensitive exact match)."
        },
        {
          "name": "product_ids",
          "type": "string[]",
          "required": false,
          "description": "Catalog product IDs to pin. Required when source=curated. Optional when source=mixed."
        },
        {
          "name": "filters",
          "type": "object[]",
          "required": false,
          "description": "Catalog filters, ANDed. e.g. [{\"field\":\"inventory_quantity\",\"op\":\"gt\",\"value\":10}] serves only items with more than 10 in stock. Applied to auto-ranked AND pinned items alike. Pass [] to clear."
        },
        {
          "name": "catalog_weights",
          "type": "object[]",
          "required": false,
          "description": "Catalog scoring weights. e.g. [{\"field\":\"inventory_quantity\",\"op\":\"lt\",\"value\":10,\"weight\":0.5}] de-emphasises nearly-sold-out items without hiding them. Weights re-rank auto-ranked recommendations and deliberately do NOT reorder curated pins, which are an ordering the operator authored by hand. Pass [] to clear."
        },
        {
          "name": "cart_gap",
          "type": "object | null",
          "required": false,
          "description": "Next-unlock rule for a cart slot. Thresholds come from the site's live offers (min_order_cents and each tier of a tiered offer: free shipping, gift with purchase, spend-and-save, tiers), not from this rule. At serve time the engine reads the visitor's latest cart value, finds the nearest unmet threshold among live offers placed for the visitor's segment in the cart's currency, and prefers or requires items priced at least the gap (free shipping at 150, cart 128 → items of 22 or more). The response returns the target (offer_id, offer_type, gap_amount, unlocks) and, when line.show is true, gap_line: the finished sentence the tag shows above the picks. No cart, an unknown cart value, no eligible offer or a cart past every threshold means normal ranking. Cart items are never recommended. Needs cart events with item prices (axo.setCart or data-vt-line-item markup). Does not reorder or remove curated pins. Pass null to clear; omit to leave it as it is."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_campaign",
      "title": "Create campaign",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a new campaign with a brief (objective, offer, tone, themes).",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Campaign name"
        },
        {
          "name": "brief",
          "type": "object",
          "required": false,
          "description": "Campaign brief"
        },
        {
          "name": "status",
          "type": "\"active\" | \"draft\" | \"paused\" | \"archived\"",
          "required": false,
          "description": ""
        },
        {
          "name": "goal_id",
          "type": "string | null",
          "required": false,
          "description": "Primary goal: a site_goals.id (see get_site_config goals). Must be an ACTIVE goal of this site; null clears it. Readiness requires one."
        },
        {
          "name": "owner_user_id",
          "type": "string | null",
          "required": false,
          "description": "Owner (a user id or name) responsible for the campaign; null clears it."
        },
        {
          "name": "starts_at",
          "type": "string",
          "required": false,
          "description": "ISO-8601 start date for automated scheduling"
        },
        {
          "name": "ends_at",
          "type": "string",
          "required": false,
          "description": "ISO-8601 end date for automated scheduling"
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": "Advanced segment matching rules (e.g. segment_member, audience_signals)"
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": "Evaluation priority (higher = evaluated first)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_component",
      "title": "Create component",
      "domain": "content",
      "scope": "write",
      "description": "Create a draft reusable component from a description or sanitized HTML/CSS. Does not place or publish it. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Stable key for retries of this logical mutation"
        },
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "name"
        },
        {
          "name": "brief",
          "type": "string",
          "required": false,
          "description": "brief"
        },
        {
          "name": "html",
          "type": "string",
          "required": false,
          "description": "html"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        }
      ]
    },
    {
      "name": "create_component_experiment",
      "title": "Create component experiment",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a draft test of one or more component placements against the unchanged page. With only placement_id it is the classic 50/50 test; additional_placement_ids adds further treatment arms (C, D, ...) so one visitor is drawn into exactly one of: unchanged page, treatment B, treatment C... with an equal fixed split. Every additional placement must sit on the same page pattern, anchor selector, audience and schedule as placement_id (otherwise 409 treatment_*_mismatch) and keeps its own placement row; a placement already in an open test is refused. Starting freezes each arm's revision, binding and audience; assignment is sticky per visitor across all arms. Requires migration 350 for more than one treatment (503 multi_treatment_unavailable otherwise). Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "First treatment placement id (arm B); the control is cloned from it"
        },
        {
          "name": "additional_placement_ids",
          "type": "string[]",
          "required": false,
          "description": "Further treatment placement ids (arms C, D, ...), max 4. Same page, anchor, audience and schedule as placement_id."
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "name"
        },
        {
          "name": "hypothesis",
          "type": "string",
          "required": false,
          "description": "hypothesis"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_component_placement",
      "title": "Create component placement",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a DRAFT component placement. For between, both anchors must be adjacent siblings. Preview and approve the revision before activation. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Stable key for retries of this draft placement creation"
        },
        {
          "name": "field_overrides",
          "type": "object",
          "required": false,
          "description": "Only exposed revision field keys; placement-specific text/image/link values. Empty object clears customizations."
        },
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "component_revision_id",
          "type": "string",
          "required": true,
          "description": "component revision id"
        },
        {
          "name": "position",
          "type": "\"before\" | \"after\" | \"between\" | \"inside_start\" | \"inside_end\" | \"replace\"",
          "required": true,
          "description": "position"
        },
        {
          "name": "element_selector",
          "type": "string",
          "required": true,
          "description": "element selector"
        },
        {
          "name": "second_anchor_selector",
          "type": "string",
          "required": false,
          "description": "second anchor selector"
        },
        {
          "name": "anchor_policy",
          "type": "\"required\" | \"when_present\"",
          "required": false,
          "description": "required (default) reports a missing anchor. when_present waits for a conditional primary anchor without reporting absence as an error; invalid or ambiguous anchors still fail."
        },
        {
          "name": "page_pattern",
          "type": "string",
          "required": true,
          "description": "page pattern"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "name"
        },
        {
          "name": "page_type",
          "type": "string",
          "required": false,
          "description": "page type"
        },
        {
          "name": "order",
          "type": "number",
          "required": false,
          "description": "order"
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": "priority"
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": "eligibility"
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": "segment id"
        },
        {
          "name": "starts_at",
          "type": "string",
          "required": false,
          "description": "ISO timestamp, or empty string to remove start"
        },
        {
          "name": "ends_at",
          "type": "string",
          "required": false,
          "description": "ends at"
        },
        {
          "name": "goal_id",
          "type": "string",
          "required": false,
          "description": "Built for goal: id of an active goal of this site (from get_site_config) that this placement is meant to move. The Impact page and get_zone_exposure_report group placements by it. A goal of another site is refused with goal_not_found. It is a reporting label: it never changes what visitors see, makes no new placement version and is allowed during a test. Returns placement_goal_unavailable (503) on a database that does not have the column yet."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        }
      ]
    },
    {
      "name": "create_content_experiment",
      "title": "Create content experiment",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a DRAFT fixed-split test over existing content variants in one zone that all target the same audience segment (the all-visitors segment = everyone, independent of segment ranking). include_control adds an Original arm (the page as-is, arm A) that still records exposures. Allocation is visitor-sticky and never moved by the optimizer; while running, eligible visitors always get their drawn arm. One open test per zone. Nothing serves until update_content_experiment action=start.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": true,
          "description": "Zone id"
        },
        {
          "name": "variant_ids",
          "type": "string[]",
          "required": true,
          "description": "2–5 content variant ids in that zone (same segment). Arm keys follow this order after the optional Original arm."
        },
        {
          "name": "include_control",
          "type": "boolean",
          "required": false,
          "description": "Add an Original control arm (default true)"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "Test name"
        },
        {
          "name": "hypothesis",
          "type": "string",
          "required": false,
          "description": "What you hope to improve"
        },
        {
          "name": "split",
          "type": "object | null",
          "required": false,
          "description": "{armKey: weight} on any non-negative scale, e.g. {A: 50, B: 50}. Omit for equal."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_content_variant",
      "title": "Create content variant",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a personalized content variant for a zone + segment combination. For copy_swap/hero_swap, check the zone's field_schema (get_zones) FIRST and key the payload by its payload_key/canonical_name entries — a payload sharing NO renderable key with a schema-bearing zone binds nothing and is refused with a 400 naming the accepted keys. Do not copy another variant's payload shape without checking the target zone's schema. For product_reco variants, create with an empty payload ({}) and then call configure_product_reco to set the recommendation mode and curated products.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": true,
          "description": "Zone ID"
        },
        {
          "name": "active",
          "type": "boolean",
          "required": false,
          "description": "false saves an inactive private draft for human review; required under propose-only. Omit to enable immediately when policy permits."
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": "Target segment ID (legacy mode, or mirrored from eligibility)"
        },
        {
          "name": "type",
          "type": "\"overlay\" | \"hero_swap\" | \"copy_swap\" | \"product_reco\" | \"move_object\"",
          "required": true,
          "description": "Variant type"
        },
        {
          "name": "payload",
          "type": "object",
          "required": true,
          "description": "Variant payload. For overlay/hero_swap/copy_swap: { headline, body, cta, image_url }. For product_reco: pass {} and configure via configure_product_reco. For move_object: { dest_selector, position } — but prefer author_move, which also creates the source zone."
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": "Advanced segment matching rules (e.g. segment_member, audience_signals)"
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": "Evaluation priority (higher = evaluated first)"
        },
        {
          "name": "campaign_id",
          "type": "string",
          "required": false,
          "description": "Link this variant to an orchestration campaign"
        },
        {
          "name": "destination_page_id",
          "type": "string",
          "required": false,
          "description": "Make this a ROUTE to an axo Page (migration 260): the CTA URL is then SERVER-derived (<pageUrl>?vt_route=<variantId>) — do not supply URL fields in the payload. Prefer create_route."
        },
        {
          "name": "ends_at",
          "type": "string",
          "required": false,
          "description": "Optional scheduled expiry (ISO 8601, at least 5 minutes out, within a year). The hourly watchdog sweep deactivates the variant once this passes and posts a notification. Omit for no expiry."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_custom_segment",
      "title": "Create custom segment",
      "domain": "audience",
      "scope": "write",
      "description": "Create a custom audience from the same definition used by preview_custom_segment. Preview first, then recompute_custom_segment to materialize membership.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "definition",
          "type": "object",
          "required": true,
          "description": "Custom audience definition, e.g. {predicate:{any_of:[{kind:\"event_occurred\",event_type:\"pageview\"}]}}"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_form",
      "title": "Create form",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a reusable lead-capture form template. The payload is the form design; it is copied onto each placement.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Internal template name"
        },
        {
          "name": "payload",
          "type": "object",
          "required": true,
          "description": "Form design: { title, cta_text, tcpa_text, success_text, accent_color, fields (subset of name/email/phone), calendar_enabled }"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_integration",
      "title": "Create integration",
      "domain": "integrations",
      "scope": "write",
      "description": "Create a new integration (klaviyo, braze, resend, webhook, meta_capi, google_capi, google_ads_ec, …).",
      "params": [
        {
          "name": "type",
          "type": "string",
          "required": true,
          "description": "Integration type"
        },
        {
          "name": "config",
          "type": "object",
          "required": true,
          "description": "Config (API keys, endpoints)"
        },
        {
          "name": "events",
          "type": "string[]",
          "required": false,
          "description": "Events to fire on"
        },
        {
          "name": "active",
          "type": "boolean",
          "required": false,
          "description": "Enable immediately"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_layout_experiment",
      "title": "Create layout experiment",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a page-level layout experiment as a DRAFT. SIDE EFFECTS: inserts the experiment row, one layout-role zone for the container (page_pattern + element_selector) and an INACTIVE control variant for arm \"A\" (\"Original\"); nothing serves to visitors — a draft has no alternative layouts yet and cannot run or pause. Adding fragment arms (including the AI-generated arm B), extra containers, and starting the test are dashboard actions today. element_selector must be a CSS selector for the container (max 300 chars, no <>{ }); page_pattern must start with \"/\". Requires the layout_experiments module.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Experiment name (max 120 chars), e.g. \"PDP gallery-first layout\""
        },
        {
          "name": "page_type",
          "type": "string",
          "required": true,
          "description": "Page type the test runs on (e.g. home, pdp, plp, cart, checkout, blog) — must be one of the site's known page types"
        },
        {
          "name": "page_pattern",
          "type": "string",
          "required": true,
          "description": "Path pattern the container appears on, starting with \"/\" (e.g. /products/*)"
        },
        {
          "name": "element_selector",
          "type": "string",
          "required": true,
          "description": "CSS selector for the container being tested (e.g. main.product, #hero)"
        },
        {
          "name": "hypothesis",
          "type": "string",
          "required": false,
          "description": "What the alternative layout is expected to change, and why (max 2000 chars)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_offer",
      "title": "Create offer",
      "domain": "personalization",
      "scope": "write",
      "description": "Create an offer in this site's library. It is ALWAYS created as an UNAPPROVED DRAFT: a human must ratify it in the dashboard (approve the terms, and attest the code exists and is active in their commerce platform) before it can ever serve. There is no `status` argument here and no tool on any surface that can make an offer live — that transition is dashboard-only, by construction. MONEY IS CENTS AND PERCENTS ARE BASIS POINTS, integers only: value_bp 2500 = 25% off, value_cents 1000 = $10 off. The write is REJECTED when the depth exceeds the site's max_discount_pct ceiling, or when it meets/exceeds the site's gross margin (every order at that depth loses money and no conversion lift can fix it). `code` must be a discount code the merchant's platform ALREADY honors — fulfillment_mode accepts only \"existing_code\". Returns { id, warnings }; warnings cover mirrored terms and the break-even lift this discount needs. Then place it with place_offer.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Internal offer name (max 200 chars)"
        },
        {
          "name": "offer_type",
          "type": "\"percent\" | \"fixed\" | \"free_shipping\" | \"gift\" | \"tiered\"",
          "required": true,
          "description": "percent needs value_bp. fixed needs value_cents. free_shipping/gift/tiered carry no single headline value, so the depth cap and break-even hint do not apply to them."
        },
        {
          "name": "value_bp",
          "type": "integer",
          "required": false,
          "description": "Percent discount in BASIS POINTS, 0-10000 (2500 = 25.00%). Required for offer_type=percent."
        },
        {
          "name": "value_cents",
          "type": "integer",
          "required": false,
          "description": "Fixed discount in CENTS (1000 = $10.00). Required for offer_type=fixed."
        },
        {
          "name": "currency",
          "type": "string",
          "required": false,
          "description": "3-letter ISO-4217 code (e.g. \"USD\"). Leave unset for percent / free_shipping."
        },
        {
          "name": "code",
          "type": "string",
          "required": false,
          "description": "The discount code AS IT EXISTS in the merchant's commerce platform. AXO never mints codes. Two live offers cannot claim the same code."
        },
        {
          "name": "min_order_cents",
          "type": "integer",
          "required": false,
          "description": "MIRRORED TERM (cents): minimum basket. Displayed as fine print and used to reason about a fixed offer's depth — their platform enforces it, and if the two disagree theirs wins."
        },
        {
          "name": "max_discount_cents",
          "type": "integer",
          "required": false,
          "description": "MIRRORED TERM (cents): cap on the discount. Displayed as fine print only; AXO never enforces it at checkout."
        },
        {
          "name": "max_exposures_per_visitor",
          "type": "integer",
          "required": false,
          "description": "The ONE cap AXO owns and enforces: how many times we show this offer to one visitor. There is deliberately no redemption limit — that ledger belongs to the merchant's platform."
        },
        {
          "name": "starts_at",
          "type": "string",
          "required": false,
          "description": "ISO-8601 timestamp the offer becomes servable."
        },
        {
          "name": "ends_at",
          "type": "string",
          "required": false,
          "description": "ISO-8601 timestamp the offer stops serving. Must be after starts_at. Enforced at config-build time, so the offer goes dark everywhere on the next build."
        },
        {
          "name": "stacking",
          "type": "\"exclusive\" | \"stackable\" | \"group\"",
          "required": false,
          "description": "MIRRORED TERM, rendered as fine print (\"Not combinable\"). Default \"exclusive\". Their cart decides what actually stacks."
        },
        {
          "name": "fulfillment_mode",
          "type": "\"existing_code\"",
          "required": false,
          "description": "Only \"existing_code\" is accepted: we reference a code the merchant already honors and never mint one. Minting is deliberately deferred — it is where AXO would start competing with their loyalty platform on accounting and liability."
        },
        {
          "name": "tiers",
          "type": "object[]",
          "required": false,
          "description": "For offer_type=tiered: up to 10 entries of { min_order_cents, value_bp | value_cents }."
        },
        {
          "name": "gift_sku",
          "type": "string",
          "required": false,
          "description": "For offer_type=gift: the catalog SKU the merchant adds to the cart. Required for gift offers."
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": "SegmentPredicate (same shape as a content variant's eligibility), so loyalty tier / lapse (crm_attribute, profile_attribute) and anonymous behavior (propensity, intent, affinity) feed ONE evaluator."
        },
        {
          "name": "redeem_url",
          "type": "string",
          "required": false,
          "description": "Where the offer CTA sends the visitor. Falls back to the placement's own cta_href when unset."
        },
        {
          "name": "terms_url",
          "type": "string",
          "required": false,
          "description": "Full terms page; the renderer links it as \"Terms apply\"."
        },
        {
          "name": "presentation",
          "type": "object",
          "required": false,
          "description": "Default presentation inherited by every placement: { headline, body, cta, cta_href, show_code, show_expiry }. A placement's own presentation keys win over these."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_page",
      "title": "Create page",
      "domain": "content",
      "scope": "write",
      "description": "Create a draft page from an operator-stated goal (no suggestion needed). target is the question the page must answer. Returns the draft; call generate_page next.",
      "params": [
        {
          "name": "archetype",
          "type": "\"faq\" | \"comparison\" | \"guide\" | \"landing\"",
          "required": true,
          "description": "Page archetype"
        },
        {
          "name": "target",
          "type": "string",
          "required": true,
          "description": "The question/demand signal the page answers"
        },
        {
          "name": "outline",
          "type": "string[]",
          "required": false,
          "description": "Optional section outline (defaults per archetype)"
        },
        {
          "name": "tone",
          "type": "string",
          "required": false,
          "description": "Optional tone override"
        },
        {
          "name": "slug",
          "type": "string",
          "required": false,
          "description": "Optional URL slug. May be a nested path like \"blog/spring/launch\" (max 5 segments) — folders are implicit, derived from the /s."
        },
        {
          "name": "cta",
          "type": "object",
          "required": false,
          "description": "Optional primary CTA: { label, href, fires_event? }"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_page_config",
      "title": "Create page config",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a page type configuration for sitemap analysis.",
      "params": [
        {
          "name": "page_type",
          "type": "string",
          "required": true,
          "description": "Page type (product, category, blog)"
        },
        {
          "name": "url_pattern",
          "type": "string",
          "required": true,
          "description": "URL pattern to match"
        },
        {
          "name": "selectors",
          "type": "object",
          "required": false,
          "description": "CSS selectors for page elements"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_preview_share_link",
      "title": "Create preview share link",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a link a client or stakeholder can open, without logging in, to review one audience's view of the site on the real storefront: the private working copy (view \"draft\", the default) or exactly what visitors see (view \"live\"; ordinary auto-site links show live content; placement_id plus expected_version instead freezes one saved placement on either site mode). The link carries only that audience's content. Returns the URL to send. The link expires on its own (7 days by default, 30 at most) and can be turned off with revoke_preview_share_link. Pass a site-relative path to open on a specific page and a short label so the link is recognisable in the dashboard's list.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": false,
          "description": "Component placement to freeze in this link; requires expected_version. Other content uses the published baseline."
        },
        {
          "name": "expected_version",
          "type": "integer",
          "required": false,
          "description": "Reviewed placement version from list/get_component_placement."
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": "Audience (segment id) the reviewer sees the site as. Use get_segments to find ids."
        },
        {
          "name": "view",
          "type": "\"draft\" | \"live\"",
          "required": false,
          "description": "draft (default): unpublished edits included. live: what visitors get today."
        },
        {
          "name": "path",
          "type": "string",
          "required": false,
          "description": "Site-relative page to open on, e.g. /products/widget. Default /."
        },
        {
          "name": "zone_id",
          "type": "string",
          "required": false,
          "description": "Optional zone the link is about; the zone header lists its links."
        },
        {
          "name": "label",
          "type": "string",
          "required": false,
          "description": "Optional short note shown on the review page and in the list."
        },
        {
          "name": "ttl_days",
          "type": "number",
          "required": false,
          "description": "How long the link works, in days. Default 7, max 30."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_route",
      "title": "Create route",
      "domain": "content",
      "scope": "write",
      "description": "Route the right visitor to a published axo Page: creates an entry-point variant (copy_swap/hero_swap) whose CTA the server points at the page (<pageUrl>?vt_route=<variantId> — the attribution join key; never supply URL fields yourself). Rides the full variant machinery: campaign flight window + audience AND-composition via campaign_id, eligibility/segment targeting, priority, per-visitor holdout, lift. Called WITHOUT zone_id it returns needs_zone with candidate zones — pick the one whose page_pattern covers where the audience lands (only zones whose schema carries a url field can host a route). An approved-but-unpublished destination is allowed only with active=false (the suggestion-loop shape); publishing the page re-stamps the href.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Destination axo Page ID (published; or approved when active=false)"
        },
        {
          "name": "zone_id",
          "type": "string",
          "required": false,
          "description": "Hosting zone — omit to get needs_zone candidates"
        },
        {
          "name": "type",
          "type": "\"hero_swap\" | \"copy_swap\"",
          "required": false,
          "description": "Entry variant type (default hero_swap)"
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": "Target segment (omit with eligibility for predicate targeting; omit both = everyone)"
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": "SegmentPredicate audience (alternative to segment_id)"
        },
        {
          "name": "campaign_id",
          "type": "string",
          "required": false,
          "description": "Campaign umbrella: flight window, campaign audience AND-composition, campaign priority"
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": "Serve priority among competing variants"
        },
        {
          "name": "active",
          "type": "boolean",
          "required": false,
          "description": "Default true. false = author the route dark (required for approved-but-unpublished destinations)"
        },
        {
          "name": "payload",
          "type": "object",
          "required": false,
          "description": "Entry copy (headline, cta text…) — the CTA URL itself is server-derived"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_segment",
      "title": "Create segment",
      "domain": "audience",
      "scope": "write",
      "description": "Create a new custom behavioral segment with a name, description, and signal rules.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Segment name"
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": "Behavioral description"
        },
        {
          "name": "emoji",
          "type": "string",
          "required": false,
          "description": "Emoji icon"
        },
        {
          "name": "signal_rules",
          "type": "object",
          "required": false,
          "description": "Matching signal rules"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_trigger",
      "title": "Create trigger",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a new triggered message. Requires name, mode (immediate|dormancy), watch_event, and at least one destination.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Human-readable name"
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "mode",
          "type": "\"immediate\" | \"dormancy\"",
          "required": true,
          "description": "Fire mode"
        },
        {
          "name": "watch_event",
          "type": "string",
          "required": true,
          "description": "Event type to watch"
        },
        {
          "name": "cancel_event",
          "type": "string",
          "required": false,
          "description": "Event that cancels dormancy"
        },
        {
          "name": "criteria",
          "type": "object",
          "required": false,
          "description": "Matching criteria"
        },
        {
          "name": "delay_ms",
          "type": "number",
          "required": false,
          "description": "Dormancy delay in ms"
        },
        {
          "name": "window_ms",
          "type": "number",
          "required": false,
          "description": "Cancel-event window in ms"
        },
        {
          "name": "identifier_type",
          "type": "\"email\" | \"phone\" | \"device\" | \"external_id\" | \"profile_id\"",
          "required": false,
          "description": ""
        },
        {
          "name": "destinations",
          "type": "object[]",
          "required": true,
          "description": "Destination configs"
        },
        {
          "name": "cooldown_per_profile_ms",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "global_rate_limit_per_min",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "optimization_mode",
          "type": "\"axo_bandit\" | \"external\"",
          "required": false,
          "description": "Optimization mode (axo_bandit or external)"
        },
        {
          "name": "send_window",
          "type": "object | null",
          "required": false,
          "description": "Per-trigger send window. null inherits the site setting; {\"enabled\": false} sends at any hour (for safety triggers); {\"enabled\": true, \"start_hour\": 9, \"end_hour\": 20} sets this trigger's own local hours. An enabled window needs the site trigger_timezone, and its start and end (with missing hours taken from the site) must differ. Needs migration 354."
        },
        {
          "name": "frequency_cap_exempt",
          "type": "boolean",
          "required": false,
          "description": "When true this trigger ignores the site's per-person cap and its fires do not count toward it. For safety triggers such as a declined card. Default false."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_trigger_template",
      "title": "Create trigger template",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a new site-owned payload template for triggered messages.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Template name"
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "format",
          "type": "\"json\" | \"html\" | \"form\"",
          "required": true,
          "description": "Template format"
        },
        {
          "name": "body",
          "type": "string",
          "required": true,
          "description": "Template body with mustache placeholders"
        },
        {
          "name": "example_destination",
          "type": "string",
          "required": false,
          "description": "Intended destination type"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "create_zone",
      "title": "Create zone",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a new personalization zone on the site. For lightbox zones (zone_key=\"lightbox\"), pass display_rules to set when/where/who/how-often the popup fires — including country targeting (audience.country) and day/hour windows (schedule); otherwise call list_lightbox_presets and use one of those rule objects verbatim.",
      "params": [
        {
          "name": "page_pattern",
          "type": "string",
          "required": true,
          "description": "URL pattern (e.g. \"/\", \"/product*\")"
        },
        {
          "name": "zone_key",
          "type": "string",
          "required": true,
          "description": "Zone identifier"
        },
        {
          "name": "display_name",
          "type": "string",
          "required": true,
          "description": "Human-readable name"
        },
        {
          "name": "element_selector",
          "type": "string",
          "required": false,
          "description": "CSS selector"
        },
        {
          "name": "position",
          "type": "string",
          "required": false,
          "description": "Position (before/after/replace)"
        },
        {
          "name": "goal_id",
          "type": "string",
          "required": false,
          "description": "Built for goal: id of an active goal of this site (from get_site_config) that this placement is meant to move. The Impact page and get_zone_exposure_report group placements by it. A goal of another site is refused with goal_not_found. It is a reporting label: it never changes what visitors see, makes no new placement version and is allowed during a test. Returns placement_goal_unavailable (503) on a database that does not have the column yet."
        },
        {
          "name": "display_rules",
          "type": "object",
          "required": false,
          "description": "Lightbox-only display rules (DisplayRules shape — see lib/display-rules.ts). Sub-objects: trigger {type, delay_ms?, scroll_pct?, inactivity_ms?}, page_targeting {exclude[]}, audience {visitor_type, converted, device[], country[]}, schedule {days[], hours[], tz}, frequency {cap, n_days?, cooldown_seconds?}, suppression {after_dismiss_days?, after_convert_days?, mutual_exclusion?}. audience.country takes ISO 3166-1 alpha-2 codes (\"GB\", not \"UK\" — invalid codes are DROPPED, which turns the filter off rather than erroring, so read the saved rules back to confirm). schedule.days is 0=Sunday..6=Saturday, schedule.hours is [{start,end}] with end EXCLUSIVE and end<=start wrapping past midnight, and schedule.tz is either \"visitor\" (default — each visitor's own device clock, so 11-14 is everyone's lunchtime) or an IANA zone like \"America/Chicago\" (one real-world window everyone sees at the same instant). The API sanitizes — bad fields are dropped, not 400d. Ignored on non-lightbox zones."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "define_affinity_terms",
      "title": "Define affinity terms",
      "domain": "site",
      "scope": "write",
      "description": "Create or update taxonomy terms (brand/topic/category) with the keyword + URL-pattern rules that map pages to that object. These feed per-visitor affinity scoring and the \"Interest / affinity\" segment leaf. object_key defaults to a slug of the label. Example term: { dimension:\"brand\", label:\"Titleist\", keywords:[\"titleist\"], url_patterns:[\"/titleist\"] }.",
      "params": [
        {
          "name": "terms",
          "type": "object[]",
          "required": true,
          "description": "Terms to upsert"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_campaign",
      "title": "Delete campaign",
      "domain": "personalization",
      "scope": "admin",
      "description": "Delete a campaign permanently.",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_component",
      "title": "Delete component",
      "domain": "content",
      "scope": "admin",
      "description": "Delete a reusable component after its active placements have been resolved. Historical attribution is retained. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_component_placement",
      "title": "Delete component placement",
      "domain": "personalization",
      "scope": "admin",
      "description": "Remove a placement without deleting the reusable component. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_content_variant",
      "title": "Delete content variant",
      "domain": "personalization",
      "scope": "admin",
      "description": "Soft-delete a content variant. The variant is hidden immediately but recoverable via restore_content_variant within 30 days. A history snapshot is automatically created before deletion.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Variant ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_form",
      "title": "Delete form",
      "domain": "personalization",
      "scope": "write",
      "description": "Delete a form template. Already-deployed placements are unaffected (placements copy the payload).",
      "params": [
        {
          "name": "id",
          "type": "string",
          "required": true,
          "description": "Form template ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_integration",
      "title": "Delete integration",
      "domain": "integrations",
      "scope": "admin",
      "description": "Delete an integration permanently.",
      "params": [
        {
          "name": "integration_id",
          "type": "string",
          "required": true,
          "description": "Integration ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_offer",
      "title": "Delete offer",
      "domain": "personalization",
      "scope": "write",
      "description": "Soft-delete an offer. It vanishes from list_offers and stops being hydrated on the next config build, but the row survives so historical per-offer lift stays attributable. Its PLACEMENTS are deliberately not deleted: they keep their offer_id, hydrate to nothing, and are dropped from tag config, so the page shows no half-resolved offer rather than a stale code — fail closed. If a slot should now carry something else, remove or repoint the placement yourself with delete_content_variant.",
      "params": [
        {
          "name": "id",
          "type": "string",
          "required": true,
          "description": "Offer ID (from list_offers)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_page_config",
      "title": "Delete page config",
      "domain": "personalization",
      "scope": "admin",
      "description": "Delete a page type configuration.",
      "params": [
        {
          "name": "config_id",
          "type": "string",
          "required": true,
          "description": "Config ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_profile",
      "title": "Delete profile",
      "domain": "audience",
      "scope": "admin",
      "description": "Permanently delete a profile and all associated data (GDPR erasure). Removes identifiers, unlinks sessions, deletes the profile.",
      "params": [
        {
          "name": "profile_id",
          "type": "string",
          "required": true,
          "description": "Profile UUID to delete"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_trigger",
      "title": "Delete trigger",
      "domain": "personalization",
      "scope": "admin",
      "description": "Permanently delete a trigger and its fire history.",
      "params": [
        {
          "name": "trigger_id",
          "type": "string",
          "required": true,
          "description": "Trigger ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_trigger_template",
      "title": "Delete trigger template",
      "domain": "personalization",
      "scope": "admin",
      "description": "Delete a site-owned trigger payload template.",
      "params": [
        {
          "name": "template_id",
          "type": "string",
          "required": true,
          "description": "Template ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "delete_zone",
      "title": "Delete zone",
      "domain": "personalization",
      "scope": "admin",
      "description": "Delete a zone and all its content variants.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": true,
          "description": "Zone ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "designate_optin_form",
      "title": "Designate optin form",
      "domain": "audience",
      "scope": "write",
      "description": "Designate a SITE-NATIVE form (one the customer already has — newsletter, contact, signup) for opt-in lead capture, or update its field selectors. On submit of a matching form the tag captures the email (plus name/phone ONLY when their field selectors are set here or via data-axo-*-field attributes) into Leads/profiles/CRM. Works on native Episerver/Optimizely Forms too — their AJAX submit is captured via the same vendor hook that fires form_submit. For split-name forms (First + Last as separate inputs, e.g. Episerver) set first_name_field + last_name_field and the tag joins them into the one stored name; use name_field for a single combined input. Upserts by form_selector (max 20 designated forms). This reads visitor-entered PII from the customer's own form — only designate forms the customer owns and has asked to capture; it is inert in passive mode. Verify the selector matches exactly one <form> on the live page first (e.g. via get_form_funnel form ids or probe_zone_binding).",
      "params": [
        {
          "name": "form_selector",
          "type": "string",
          "required": true,
          "description": "CSS selector matching the <form> element (e.g. \"#newsletter-form\", \"form.contact\")"
        },
        {
          "name": "email_field",
          "type": "string",
          "required": false,
          "description": "CSS selector (within the form) for the email input. Optional — without it the tag falls back to input[type=email] / name-contains-\"email\" heuristics."
        },
        {
          "name": "name_field",
          "type": "string",
          "required": false,
          "description": "CSS selector for a single combined name input. Captured ONLY when designated. For split first/last inputs use first_name_field + last_name_field instead."
        },
        {
          "name": "first_name_field",
          "type": "string",
          "required": false,
          "description": "CSS selector for a first-name input on a split-name form (e.g. Episerver). Joined with last_name_field into the one stored name; takes precedence over name_field. Captured ONLY when designated."
        },
        {
          "name": "last_name_field",
          "type": "string",
          "required": false,
          "description": "CSS selector for a last-name input on a split-name form. Joined with first_name_field into the one stored name. Captured ONLY when designated."
        },
        {
          "name": "phone_field",
          "type": "string",
          "required": false,
          "description": "CSS selector for the phone input. Phone is captured ONLY when designated (newsletter-grade opt-in — never TCPA/SMS consent)."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "detach_campaign_component",
      "title": "Detach campaign component",
      "domain": "personalization",
      "scope": "write",
      "description": "Remove a component from a campaign's manifest. Clears the variant/trigger serving-path campaign_id only when it points at THIS campaign (this is also how a variant linked from the content editor is removed). Live content needs confirm_serving_change. 404 when the pair is not attached. Does not delete the referenced resource. Any change voids an approval.",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "kind",
          "type": "\"page\" | \"variant\" | \"trigger\" | \"offer\" | \"audience\" | \"experiment\" | \"provider_campaign\"",
          "required": true,
          "description": "Component kind"
        },
        {
          "name": "ref_id",
          "type": "string",
          "required": true,
          "description": "The attached resource id"
        },
        {
          "name": "confirm_serving_change",
          "type": "boolean",
          "required": false,
          "description": "Required (true) when the variant is active or the trigger enabled: attaching puts live content under this campaign's window and audience (off the site if the campaign is not serving); detaching removes that gate so it serves to everyone. Without it the call returns 409 with details.reason = serving_change_requires_confirmation. Confirm with the operator first."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "detect_tracking_params",
      "title": "Detect tracking params",
      "domain": "site",
      "scope": "write",
      "description": "PROPOSE (never apply) site-specific query params for extra_tracking_params, from the site's own recent pageviews. Returns proposals with the evidence behind each — occurrences, share of the sample, distinct-value churn, whether any value ever reached a second session, path spread, redacted sample values — plus every observed param it did NOT propose and the rule that stopped it. Two bases: `known_vendor` (a named product's per-click/session token such as Awin's awc or PHPSESSID, proposed on sight) and `traffic_evidence` (cleared every floor on this site's own traffic). A site with too little traffic reports status \"insufficient_evidence\" and makes NO evidence-based proposal rather than guessing on thin data. This tool CANNOT change page identity: to apply, call update_site_config with the FULL extra_tracking_params list (existing entries plus the accepted ones). CAUTION before applying: declaring a param that genuinely distinguishes pages merges those pages into one page identity, silently and unrecoverably. The detector deliberately under-proposes, so a param it held back was held back on purpose.",
      "params": [
        {
          "name": "window_days",
          "type": "integer",
          "required": false,
          "description": "Trailing days of pageviews to sample (default 30, capped at 90). Values above the cap are clamped, not rejected."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "discover_sitemap",
      "title": "Discover sitemap",
      "domain": "site",
      "scope": "write",
      "description": "Discover and map semantic URLs from the site, identifying structure automatically.",
      "params": [
        {
          "name": "url",
          "type": "string",
          "required": false,
          "description": "Force extraction starting from a specific URL"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "dismiss_page_suggestion",
      "title": "Dismiss page suggestion",
      "domain": "content",
      "scope": "write",
      "description": "Dismiss a page suggestion. The underlying gap stays quiet for the cool-off window.",
      "params": [
        {
          "name": "suggestion_id",
          "type": "string",
          "required": true,
          "description": "Suggestion (proposal) ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "draft_content_variant",
      "title": "Draft content variant",
      "domain": "personalization",
      "scope": "write",
      "description": "Use AI to generate a content variant draft for a segment + zone. Returns suggested headline, body, and CTA.",
      "params": [
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": "Target segment"
        },
        {
          "name": "zone_key",
          "type": "string",
          "required": false,
          "description": "Zone key (e.g. \"hero\")"
        },
        {
          "name": "variant_type",
          "type": "\"overlay\" | \"hero_swap\" | \"copy_swap\"",
          "required": false,
          "description": "Variant type"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "duplicate_campaign",
      "title": "Duplicate campaign",
      "domain": "personalization",
      "scope": "write",
      "description": "Create a new PLANNING campaign from an existing one (usually a closed one). Copies the brief (as the copy's revision 1), audience eligibility, priority, owner, the goal (only if still active) and the name (with ' (copy)' unless you pass name), and re-references its pages, offers and audiences. Does NOT copy variants or triggers (each serves under one campaign at a time - duplicate those and attach the copies), experiments (tied to the run that measured them) or ad platform campaigns; each comes back in skipped with the reason. Dates are cleared and nothing serves until the copy is approved and launched. The source campaign is untouched.",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Source campaign ID"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "Name for the copy (default: source name + \" (copy)\")"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "duplicate_component",
      "title": "Duplicate component",
      "domain": "content",
      "scope": "write",
      "description": "Duplicate a component into a separate draft so it can evolve independently. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "edit_component",
      "title": "Edit component",
      "domain": "content",
      "scope": "write",
      "description": "Refine a component with a natural-language instruction using its current revision and site brand. Saves a draft revision without changing live placements. Costs AI budget.",
      "params": [
        {
          "name": "placement_context",
          "type": "object",
          "required": false,
          "description": "Optional reference_url, position, element_selector, second_anchor_selector, anchor_html, second_anchor_html. Visual context only."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Stable key for retries of this logical mutation"
        },
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "instruction",
          "type": "string",
          "required": true,
          "description": "instruction"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        }
      ]
    },
    {
      "name": "end_layout_experiment",
      "title": "End layout experiment",
      "domain": "personalization",
      "scope": "write",
      "description": "End a layout experiment — IRREVERSIBLE: an ended experiment can NEVER be restarted (create a new one instead), every arm's variants are deactivated in the same call so visitors immediately see the original layout again, and the results window freezes at ended_at. The transition (with your reason and optional winner_arm_key) is appended to the experiment's history, and the workspace is notified (bell row + same-day email). winner_arm_key here only RECORDS the outcome — it does not keep that arm serving; to end AND keep the winning layout live for everyone, use ship_layout_winner instead. Check get_layout_experiment_results first and do not end a test that is still `collecting` unless the operator asked for it.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "Layout experiment id (lexp_…), from list_layout_experiments"
        },
        {
          "name": "winner_arm_key",
          "type": "string",
          "required": false,
          "description": "Arm key to record as the winner (e.g. \"B\"). Annotation only — the winning layout stops serving; use ship_layout_winner to keep it live."
        },
        {
          "name": "reason",
          "type": "string",
          "required": false,
          "description": "Why the test is being ended (recorded in history and stop_reason; max 80 chars, default \"manual\")"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "explain_variant_exposure",
      "title": "Explain variant exposure",
      "domain": "audience",
      "scope": "read",
      "description": "Answer \"why did this visitor see this variant?\" - or why they saw nothing - with a trace assembled from records. Returns `trace`, an ordered list of claims each carrying the exact rows it rests on: the cohort the scorer assigned and the signals behind it, the holdout arm and which bucketing epoch it was made under, the zone, every live variant's targeting predicate evaluated through the same evaluator the tag itself uses, allocation, and the personalization_applied event that names what actually rendered. Also returns `answer`, the same trace rendered to text by a pure function - no model writes any part of this response. TRACED, NOT INFERRED: report the verdict and the claims AS WRITTEN, offer the cited records when asked how you know, and never supply a reason the trace does not contain. Claims marked \"unknown\" are first-class answers with a stated reason, not gaps to fill. The most important one: display-rule outcomes (country and schedule gates) are evaluated in the visitor's browser and deliberately never beaconed or persisted, so nothing server-side can say whether one refused this visitor - say exactly that rather than guessing. A visitor in the holdout saw nothing BY DESIGN, and that is a complete and correct answer, not a fault. Identify the visitor by their durable vt_sid visitor id (sessions.cookie_session_id, returned by get_sessions and get_profile) or by a single session id. Name a zone to ask why nothing served there; omit it to explain the most recent recorded exposure. Read-only: reading a trace never changes what AXO serves.",
      "params": [
        {
          "name": "visitor",
          "type": "string",
          "required": true,
          "description": "Visitor id (sessions.cookie_session_id) or session id (sessions.id)"
        },
        {
          "name": "zone",
          "type": "string",
          "required": false,
          "description": "Zone id or zone_key. Omit to resolve the zone from the visitor's most recent exposure record"
        },
        {
          "name": "window_days",
          "type": "number",
          "required": false,
          "description": "Lookback window: 1, 7 (default), or 30"
        }
      ]
    },
    {
      "name": "export_audience",
      "title": "Export audience",
      "domain": "audience",
      "scope": "write",
      "description": "Export a segment's audience for a batch email campaign. Returns JSON rows by default; set format:\"webhook\" to POST them to a URL. For an email blast list set email_only:true — only profiles with a captured email are included (anonymous visitors excluded). Works for behavioral AND custom/affinity segments (auto-detected). include_pii is required to emit emails and is recorded for compliance.",
      "params": [
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": "Segment ID to export"
        },
        {
          "name": "format",
          "type": "\"json\" | \"webhook\"",
          "required": false,
          "description": "Output format (default json)"
        },
        {
          "name": "destination",
          "type": "string",
          "required": false,
          "description": "Webhook URL (required when format=webhook)"
        },
        {
          "name": "email_only",
          "type": "boolean",
          "required": false,
          "description": "Only include profiles with an email; produce a campaign-ready email list (implies include_pii)"
        },
        {
          "name": "include_pii",
          "type": "boolean",
          "required": false,
          "description": "Include the profile email column (opt-in; audited)"
        },
        {
          "name": "include_affinities",
          "type": "boolean",
          "required": false,
          "description": "Append each profile's top-3 brand affinities"
        },
        {
          "name": "include_scores",
          "type": "boolean",
          "required": false,
          "description": "Append behavioral scores: intent_score (0–100 value-bid signal), lifecycle_stage, signals (abandonment/milestone seed keys) and value_tier (predicted order-value tier low | mid | high on the latest scored session; null when the site has no order-value model – its measured accuracy is on get_profile.value_tier_model)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "extract_brand_guidelines",
      "title": "Extract brand guidelines",
      "domain": "assets",
      "scope": "write",
      "description": "Extract a brand kit from rendered site pages: color roles, heading/body/button fonts, shapes, palette, and logos, with sources and confidence. Pass persist:true to refresh saved observations and populate all supported brand settings while preserving manual overrides. Onboarding sitemap completion also performs this refresh automatically. Without persist, returns an unsaved observation for review.",
      "params": [
        {
          "name": "persist",
          "type": "boolean",
          "required": false,
          "description": "Save observations and refresh automatically managed brand settings, preserving manual overrides (default false)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "find_stalls",
      "title": "Find stalls",
      "domain": "analytics",
      "scope": "read",
      "description": "Stall finder: where HIGH-INTENT visitors (the top 25% of sessions by conversion propensity) and the largest segments stall at a step: product page → cart → checkout → purchase, form view → start → submit, and visit → each declared goal. Each step is compared with the rest of the site this week (vs_site) and with the same visitors over their trailing weeks (vs_trailing). Read-only: nothing is drafted, created or changed. `status` is stalls_found, no_stalls or insufficient_traffic; insufficient_traffic means \"not enough traffic to call a stall yet\", which is NOT the same as no stalls, so say so plainly. A finding must clear every gate: 100 sessions plus 20 passes and 20 drop-offs on each side, a gap that survives a 5% false-discovery-rate correction across every step and cohort tested, an interval entirely below zero, and a rate at least 10% below the comparison. Each finding gives the step, the cohort, both rates, the gap with its interval, sessions per week, stalled sessions per week and value per week (when conversions carry revenue), ONE `proposed_action` (text only; `tool` names what would create it as a draft, and only after a human approves) and a `read_plan` (holdout, days needed to read a fix that closes half the gap, smallest effect readable in 28 days). A finding is a lead to test, not proof: never call a fix effective until its holdout test reads. `degraded` set means the requested window timed out and this is a this-week-only read with no vs_trailing comparison: say so. `commerce_pages: false` means the site had no product, cart or checkout pages in the last 7 days, so the shop funnel was skipped. A timeout error means no result at all, never \"no stalls\". `source` says where the counts came from: \"rollup\" is the per-step daily rollup (complete UTC days through yesterday; the high-intent cut is taken PER DAY there, so a session is high intent relative to its own day's traffic, and the threshold reported is the scored-weighted mean of the daily cuts), \"raw\" is the on-demand scan including today so far, used whenever the rollup does not cover the whole window. A weekly pass reads the rollup only and, for each finding, posts one notification (kind stall_finding, one per step, cohort and ISO week; read them with list_notifications), files the proposed action as a stall_step experiment proposal that a human accepts to get an unpublished draft (nothing goes live without approval), and emits a stall.found agent event.",
      "params": [
        {
          "name": "trailing_weeks",
          "type": "integer",
          "required": false,
          "description": "Trailing weeks to compare this week against, 0 to 3 (default 3). 0 reads only this week: cheaper, and drops the vs_trailing comparison."
        }
      ]
    },
    {
      "name": "generate_aeo_artifact",
      "title": "Generate AEO artifact",
      "domain": "site",
      "scope": "write",
      "description": "Generate a draft remediation artifact for a finding. First use list_aeo_artifacts with finding_id to read available_kinds and generated_budget; choose kind from that menu. The route enforces allowance for paid kinds. Never approves or delivers; use get_aeo_artifact to prepare human review.",
      "params": [
        {
          "name": "finding_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "kind",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "generate_audience_clusters",
      "title": "Generate audience clusters",
      "domain": "audience",
      "scope": "write",
      "description": "Manually run K-Means machine-learning clustering against recent behavioral profile activity.",
      "params": [
        {
          "name": "k",
          "type": "number",
          "required": false,
          "description": "Number of clusters to generate"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "generate_component",
      "title": "Generate component",
      "domain": "content",
      "scope": "write",
      "description": "Generate an on-brand HTML/CSS component revision from its brief. Costs AI budget; wait for completion. A conflicting version or generation in flight must be resolved by reading get_component before retrying.",
      "params": [
        {
          "name": "placement_context",
          "type": "object",
          "required": false,
          "description": "Optional reference_url, position, element_selector, second_anchor_selector, anchor_html, second_anchor_html. Visual context only."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Stable key for retries of this logical mutation"
        },
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "instruction",
          "type": "string",
          "required": false,
          "description": "instruction"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        }
      ]
    },
    {
      "name": "generate_image",
      "title": "Generate image",
      "domain": "assets",
      "scope": "write",
      "description": "Generate a NEW image via the site's BYOK image provider (gemini | openai | firefly). Real, paid API call (~$0.04) — call search_assets FIRST and reuse an existing asset when one matches. provider + prompt required. Optional: model, width, height, negative_prompt; use_brief prepends brand-voice + active-campaign context; zone_key / variant_type add visual context; reference_images (catalog product / URL / data URL) and plan_with_claude require experimental_creative_enabled. When experimental_generative_dam_enabled is on, the image is persisted and the result carries asset_id + blob_url — pass that asset_id to attach_asset_to_variant to place it on a variant. The base64 data_url is OMITTED from the result (too large); fetch blob_url for the bytes. Web Lite requires idempotency_key; reuse the same key on retry. It allows 50 BYOK attempts per UTC month, 10 per day, one at a time, and excludes Claude planning. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "provider",
          "type": "\"gemini\" | \"openai\" | \"firefly\"",
          "required": true,
          "description": "BYOK provider. The site must have active credentials saved for it."
        },
        {
          "name": "prompt",
          "type": "string",
          "required": true,
          "description": "What to generate (max 4000 chars)."
        },
        {
          "name": "model",
          "type": "string",
          "required": false,
          "description": "Optional provider model id (e.g. \"gemini-3.1-flash-image\", \"gpt-image-2.5-sunburst\"). Omit for the provider default. Retired OpenAI ids (gpt-image-1, dall-e-3) are sent as gpt-image-2.5-sunburst."
        },
        {
          "name": "width",
          "type": "integer",
          "required": false,
          "description": "Optional pixel width."
        },
        {
          "name": "height",
          "type": "integer",
          "required": false,
          "description": "Optional pixel height."
        },
        {
          "name": "negative_prompt",
          "type": "string",
          "required": false,
          "description": "Optional — things to avoid in the image."
        },
        {
          "name": "use_brief",
          "type": "boolean",
          "required": false,
          "description": "When true, prepend brand voice + active campaign brief context. Default false."
        },
        {
          "name": "zone_key",
          "type": "string",
          "required": false,
          "description": "Optional zone hint (e.g. \"hero\") woven into the prompt."
        },
        {
          "name": "variant_type",
          "type": "string",
          "required": false,
          "description": "Optional variant-type hint (overlay | hero_swap | copy_swap)."
        },
        {
          "name": "reference_images",
          "type": "object[]",
          "required": false,
          "description": "Optional subject reference images: product = catalog product id from list_products; url = https image URL; data_url = inline base64. Gemini and OpenAI receive the images; Gemini cannot take a mask. Requires experimental_creative_enabled."
        },
        {
          "name": "plan_with_claude",
          "type": "boolean",
          "required": false,
          "description": "When true, Claude drafts a detailed shot prompt before dispatch. Requires experimental_creative_enabled. Default false."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "generate_page",
      "title": "Generate page",
      "domain": "content",
      "scope": "write",
      "description": "Render a revision of a page from its brief. SLOW: takes 60-120s or more — wait for it. With feedback, regenerates applying it — feedback accumulates, so every regeneration honors all prior feedback. Returns the preview URL. Costs AI budget. A 409 generation_in_flight means a render is already running: do NOT retry — poll get_page for a new revision; if none appears within 6 minutes the lease has expired and one retry is safe. Prefer async=true when your client cannot hold a long call open (many MCP clients time out at 60s): the render is queued as a job and this call returns a job id immediately — poll get_job until it succeeds, then get_page has the new revision. Submitting again while the job is open returns the SAME job (never a second paid render).",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page ID"
        },
        {
          "name": "feedback",
          "type": "string",
          "required": false,
          "description": "Natural-language revision request"
        },
        {
          "name": "async",
          "type": "boolean",
          "required": false,
          "description": "true = enqueue as a background job and return {job} immediately (poll get_job). Default false = wait for the render."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "generate_taxonomy",
      "title": "Generate taxonomy",
      "domain": "site",
      "scope": "write",
      "description": "Auto-generate the site's affinity taxonomy from crawl data: one AI pass consolidates the per-page categories, brands and URL slugs the crawler extracted into canonical brand/topic/category terms (source \"auto\", with parent_key hierarchy where natural). Operator-defined terms are never modified; re-running retires auto terms that fell out of the new proposal. Use after a crawl, or whenever affinities look empty.",
      "params": [
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "get_ad_performance",
      "title": "Get ad performance",
      "domain": "analytics",
      "scope": "read",
      "description": "Daily ad spend + performance per campaign, pulled from the connected ad platforms (Google Ads via GAQL, Meta via Insights) by the ad-platform sync cron, joined to the site's observed UTM traffic. Per row: platform, campaign_ref (the provider's campaign id — the value pause_ad_campaign / set_ad_campaign_budget take), campaign name + status from the metadata pull, windowed totals (spend_micros — micros, 1 USD = 1,000,000 — impressions, clicks, conversions, currency), and `traffic` (sessions / converting_sessions / conversion_count AXO observed for the matching utm_campaign). BE HONEST ABOUT ATTRIBUTION when reporting this: the traffic join is normalized-name equality between utm_campaign and the platform campaign name — a convention join, NOT click-id attribution and NOT causal — and the platform's `conversions` column uses the platform's own attribution model, so it will not agree with AXO's observed conversion_count; they measure different things. `traffic: null` means no utm_campaign matched the campaign name, not that the campaign drove no visits. Keyset-paginated: pass back next_cursor until it is null.",
      "params": [
        {
          "name": "days",
          "type": "integer",
          "required": false,
          "description": "Window in days (default 30, max 90)"
        },
        {
          "name": "platform",
          "type": "string",
          "required": false,
          "description": "Filter to one platform, e.g. \"google_ads\" or \"meta_ads\""
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": "Rows per page (default 25, max 100)"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque keyset cursor from the previous page's next_cursor"
        }
      ]
    },
    {
      "name": "get_aeo_artifact",
      "title": "Get AEO artifact",
      "domain": "site",
      "scope": "read",
      "description": "Read one artifact and supported delivery channels. Review handoff: a signed-in human must inspect, approve and deliver in AI Visibility; agent credentials cannot perform those actions.",
      "params": [
        {
          "name": "artifact_id",
          "type": "string",
          "required": true,
          "description": ""
        }
      ]
    },
    {
      "name": "get_agent_usage",
      "title": "Get agent usage",
      "domain": "analytics",
      "scope": "read",
      "description": "AI assistant usage: conversations, messages, tokens, estimated cost.",
      "params": []
    },
    {
      "name": "get_ai_generations",
      "title": "Get AI generations",
      "domain": "analytics",
      "scope": "read",
      "description": "Read the AI generation history for a content variant — the prompt + guardrails snapshot + provider + model + DAM provenance that produced each piece of AI-generated content on this variant. Use to audit off-brand creative (\"why does this image feel wrong?\") or to trace why a specific variant looks the way it does. Returns { generations, count } with newest first. Each row carries prompt, response, model, tokens, inferred_cost, guardrails (brand_voice + active_briefs + segment), and dam_provider when the asset was push-through'd to a customer DAM.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "content_variants.id to read provenance for."
        }
      ]
    },
    {
      "name": "get_ai_visibility_cycle",
      "title": "Get AI visibility cycle",
      "domain": "site",
      "scope": "read",
      "description": "Read visibility preflight and the current gap map before requesting a paid run.",
      "params": []
    },
    {
      "name": "get_ai_visibility_status",
      "title": "Get AI visibility status",
      "domain": "site",
      "scope": "read",
      "description": "Read visibility engine readiness, recent runs and results. A queued run is not a completed result.",
      "params": []
    },
    {
      "name": "get_analytics",
      "title": "Get analytics",
      "domain": "analytics",
      "scope": "read",
      "description": "Get dashboard analytics overview: total sessions, conversions, revenue, segment breakdown, and trend data for the current site. hour_distribution is session COUNTS only — for conversion rate by time of day / day of week, use get_daypart_performance.",
      "params": [
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Number of days to look back for windowed metrics (default: 90, max: 365); visitor_mix and significance are always all-time"
        }
      ]
    },
    {
      "name": "get_asset",
      "title": "Get asset",
      "domain": "assets",
      "scope": "read",
      "description": "Fetch one DAM asset by id — includes the full generation_metadata (user_prompt, enriched_prompt, brief / reference / planner attribution) and tags. Use to inspect an image's provenance before re-using or attaching it. search_assets returns the list view (no generation_metadata); this is the detail view.",
      "params": [
        {
          "name": "asset_id",
          "type": "string",
          "required": true,
          "description": "Asset id (asset_<uuid> shape)."
        }
      ]
    },
    {
      "name": "get_backtest",
      "title": "Get backtest",
      "domain": "analytics",
      "scope": "read",
      "description": "Get backtest results showing segment performance, variant impact, conversion rates, and revenue attribution. All figures are windowed (default 90 days) and the window is echoed back in the response. Retired segments are included when they still took sessions in the window, because retiring a segment does not stop traffic scoring into it. This is not the verdict on whether personalization is working — use get_scorecard for that.",
      "params": [
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Number of days to look back (default: 90, max: 365)"
        }
      ]
    },
    {
      "name": "get_campaign",
      "title": "Get campaign",
      "domain": "personalization",
      "scope": "read",
      "description": "One campaign with its MANIFEST (#2198): every attached component (page, variant, trigger, offer, audience, experiment, provider_campaign) with `state` resolved from the referenced resource on this read - missing | draft | ready | live | stopped | failed - plus a `reason`, and `readiness` (ready, goal, required_total/required_satisfied, missing[], live[], stopped[], notes[]). `lifecycle` (planning -> ready -> approved; launched/paused/closed arrive with later phases) is the unit-of-work state and is SEPARATE from `status` (draft/active/ended), the serving gate driven by the flight window; readiness.notes says when the two disagree. Also returns the legacy `content` and `triggers` arrays (the FK-linked view).",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        }
      ]
    },
    {
      "name": "get_campaign_launch_plan",
      "title": "Get campaign launch plan",
      "domain": "personalization",
      "scope": "read",
      "description": "Preflight for launch_campaign, with NO writes: whether the campaign can launch (or resume, if paused), the blockers, and per-component steps - which approved pages would be published, which variants would be published on a manual-publishing site, which paused experiments would restart, and what needs an operator (an approved-but-not-live offer: agents cannot set offers live) or is reference-only (ad platform campaigns). Call this and show the operator before launch_campaign.",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        }
      ]
    },
    {
      "name": "get_catalog_import_status",
      "title": "Get catalog import status",
      "domain": "catalog",
      "scope": "read",
      "description": "Get the status and list of imported products in the catalog.",
      "params": []
    },
    {
      "name": "get_component",
      "title": "Get component",
      "domain": "content",
      "scope": "read",
      "description": "Read a component with immutable revision history and all placements. Draft edits never change a live placement. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "get_component_experiment",
      "title": "Get component experiment",
      "domain": "personalization",
      "scope": "read",
      "description": "Read a component test: status, version, history and `arms` in allocation order - arm A is the unchanged-page control, B the first treatment placement, C+ any additional treatment placements (each keeps its own placement id, pinned component revision and equal weight). Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "experiment id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "get_component_experiment_results",
      "title": "Get component experiment results",
      "domain": "analytics",
      "scope": "read",
      "description": "Read real exposures and subsequent conversions for every arm of a component test: `arms` lists the unchanged-page control (A) and each treatment (B, C, ...) with sessions, conversions, cvr, requested vs observed share, relative lift vs the control and a per-arm peek-safe verdict adjusted for the number of treatments; `winner`, `collecting`, sample floors, uncertainty and a sample-ratio check against the configured equal split sit at the top level. Mixed-arm, preview and simulator sessions are excluded. A failed render is not a treatment exposure. Two-arm tests keep their familiar A/B readout. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "experiment id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "get_component_performance",
      "title": "Get component performance",
      "domain": "analytics",
      "scope": "read",
      "description": "Read component delivery and click events by placement and revision. Counts represent observed events, not causal conversion lift; preview traffic is excluded. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "days"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "get_component_placement",
      "title": "Get component placement",
      "domain": "personalization",
      "scope": "read",
      "description": "Read one component placement, including its exact revision, customizations, anchors, targeting and concurrency version. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "get_content",
      "title": "Get content",
      "domain": "content",
      "scope": "read",
      "description": "Get content variants. Pass zone_id to fetch only variants for a single zone (recommended — the full response is 1MB+). Without zone_id, returns all variants for the site. To page through a large catalog instead, pass `limit`: the response then carries `next_cursor` — pass it back as `cursor` to continue, until it is null. Cursored pages are ordered newest-created first (not grouped by zone).",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": false,
          "description": "If provided, return only variants for this zone (much smaller response — use this when working on a specific zone)"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Page size (max 200). Passing this switches the response to paginated mode with `next_cursor`."
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "get_content_experiment",
      "title": "Get content experiment",
      "domain": "personalization",
      "scope": "read",
      "description": "Read one content test: audience, arms (variant per arm, effective weight, Original control if any), split, status, version and history.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "Content experiment id (cxp_…)"
        }
      ]
    },
    {
      "name": "get_content_experiment_results",
      "title": "Get content experiment results",
      "domain": "analytics",
      "scope": "read",
      "description": "Per-arm exposures (distinct sessions actually shown the arm, Original included), conversions after first exposure, conversion rate with uncertainty, requested vs observed traffic share and a sample-ratio-mismatch check, plus evidence_qualification. Repeated-look certification is withheld until the cumulative finalized outcome/analysis policy is verified; descriptive rates are not automatically causal lift. Site-holdout visitors are never in a content test; the Original arm is the test's own control.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "Content experiment id (cxp_…)"
        }
      ]
    },
    {
      "name": "get_content_history",
      "title": "Get content history",
      "domain": "content",
      "scope": "read",
      "description": "Get the version history for a content variant. Returns up to 20 snapshots ordered newest first, each with version number, who changed it (changed_by: an agent token as mcp_token:<id>, a user as user:<id>, or the legacy api/dashboard values, plus a resolved changed_by_label like the token name or user email), when, and the full payload. Use this before calling restore to find the version you want.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Variant ID to inspect"
        }
      ]
    },
    {
      "name": "get_content_map",
      "title": "Get content map",
      "domain": "catalog",
      "scope": "read",
      "description": "The content-site counterpart of list_products: what the crawl understands about a site, grouped section (first URL path segment) → page type, with 30-day engagement per section. Pass `section` to list that section's pages (title, URL, page type, AI summary, meta description, h1, categories, personalization hooks, curation state). Works for any archetype; most useful on non-ecommerce (B2B, lead-gen, content) sites.",
      "params": [
        {
          "name": "section",
          "type": "string",
          "required": false,
          "description": "Section name to drill into (optional — omit for the sections rollup)"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max pages when drilling into a section (default 100, max 500)"
        },
        {
          "name": "offset",
          "type": "number",
          "required": false,
          "description": "Pagination offset for the section drill-down (default 0)"
        }
      ]
    },
    {
      "name": "get_crm_status",
      "title": "Get CRM status",
      "domain": "crm",
      "scope": "read",
      "description": "Get CRM sync history and enrichment statistics. Shows how many profiles have CRM attributes, total enrichment coverage, and recent sync logs.",
      "params": []
    },
    {
      "name": "get_customer_ga4_report",
      "title": "Get customer ga4 report",
      "domain": "analytics",
      "scope": "read",
      "description": "Read a bounded GA4 report for this site’s selected property. Use list_customer_analytics_connections to find its connection ID. A signed-in operator must connect Google in Integrations first. The tool cannot choose a different property or use staff Workspace credentials. Dates are YYYY-MM-DD; inspect returned limitations and provider metadata before comparing with AXO. Paginate with offset; Search Console does not guarantee all rows.",
      "params": [
        {
          "name": "integration_id",
          "type": "string",
          "required": true,
          "description": "Connection ID from list_customer_analytics_connections"
        },
        {
          "name": "start_date",
          "type": "string",
          "required": true,
          "description": "Start date YYYY-MM-DD"
        },
        {
          "name": "end_date",
          "type": "string",
          "required": true,
          "description": "End date YYYY-MM-DD"
        },
        {
          "name": "dimensions",
          "type": "string",
          "required": false,
          "description": "Comma-separated dimensions; omit for totals"
        },
        {
          "name": "metrics",
          "type": "string",
          "required": false,
          "description": "Comma-separated GA4 metrics; defaults to sessions,activeUsers"
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": "Rows per page (default 100)"
        },
        {
          "name": "offset",
          "type": "integer",
          "required": false,
          "description": "Row offset (default 0)"
        }
      ]
    },
    {
      "name": "get_customer_gsc_report",
      "title": "Get customer gsc report",
      "domain": "analytics",
      "scope": "read",
      "description": "Read a bounded Search Console report for this site’s selected property. Use list_customer_analytics_connections to find its connection ID. A signed-in operator must connect Google in Integrations first. The tool cannot choose a different property or use staff Workspace credentials. Dates are YYYY-MM-DD; inspect returned limitations and provider metadata before comparing with AXO. Paginate with offset; Search Console does not guarantee all rows.",
      "params": [
        {
          "name": "integration_id",
          "type": "string",
          "required": true,
          "description": "Connection ID from list_customer_analytics_connections"
        },
        {
          "name": "start_date",
          "type": "string",
          "required": true,
          "description": "Start date YYYY-MM-DD"
        },
        {
          "name": "end_date",
          "type": "string",
          "required": true,
          "description": "End date YYYY-MM-DD"
        },
        {
          "name": "dimensions",
          "type": "string",
          "required": false,
          "description": "Comma-separated dimensions; omit for totals"
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": "Rows per page (default 100)"
        },
        {
          "name": "offset",
          "type": "integer",
          "required": false,
          "description": "Row offset (default 0)"
        }
      ]
    },
    {
      "name": "get_daypart_performance",
      "title": "Get daypart performance",
      "domain": "analytics",
      "scope": "read",
      "description": "Conversion performance by day-of-week × hour-of-day, in the visitor's LOCAL time (first-touch stamps: the hour the visit STARTED; dow 0=Sunday..6=Saturday). THE source for \"when do visitors convert / when should campaigns run?\" — never infer timing from a time-based segment's lift, which usually comes from the segment's other clauses. Returns: `grid` (dow×hour cells, up to 168 — only combinations with at least one session appear, so never index it positionally; raw sessions and converting_sessions per cell, no per-cell rate on purpose: cells are small, judge significance from the counts before ranking them), `weekday_weekend` and `hour_blocks` rollups (with rates — prefer these for headlines; blocks are a fixed vocabulary, so check the grid for a peak straddling a block boundary), `window` (rates are floored at first conversion, like the dashboard CVR — days/floored say what span the rates actually cover), and `coverage` (measured_sessions the grid describes, vs rate_window_sessions — every session in that same floored window). Numerators are primary-goal converting sessions (the conversions ledger, as in get_analytics conversion_stats), but computed over the measured subset only: sessions without first-touch stamps and simulator sessions are EXCLUDED, never guessed, so rates here legitimately differ from conversion_stats wherever coverage is below 100% or simulator traffic exists — that gap is deliberate exclusion, not a data problem.",
      "params": [
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Days to look back (clamped to 1-365, default: 90); the rate window still floors at the first recorded conversion"
        }
      ]
    },
    {
      "name": "get_decision_outcomes",
      "title": "Get decision outcomes",
      "domain": "analytics",
      "scope": "read",
      "description": "The decision→outcome ledger: every recorded measurable decision (variant created/updated, offer created, trigger created, proposal accepted) with whether it MEASURABLY worked. Each row carries outcome_status: \"pending\" = recorded but the evidence lane has not yet cleared its own sample gate (NOT a failure, NOT a zero — the sweep re-checks ~daily); \"measured\" = outcome carries the verdict (higher/lower/inconclusive) WITH its confidence interval, p-value, both arms' sample sizes, and peek_safe where available — \"inconclusive\" is a real measurement of \"we can't tell\"; \"not_measurable\" = structural (never exposed inside the maximum window, empty control arm that cannot fill, or the accepted proposal minted nothing with a lift lane) with the reason named in the outcome. Never present a pending or not_measurable decision as evidence for or against the change. Filter by since_days / decision_kind / entity_type; page with the cursor from next_cursor.",
      "params": [
        {
          "name": "since_days",
          "type": "number",
          "required": false,
          "description": "How far back to list decisions (default 90, max 365)."
        },
        {
          "name": "decision_kind",
          "type": "string",
          "required": false,
          "description": "Filter to one kind: variant_created, variant_updated, offer_created, trigger_created, proposal_accepted, campaign_closed (written already resolved when a campaign closes, with its outcome summary)."
        },
        {
          "name": "entity_type",
          "type": "string",
          "required": false,
          "description": "Filter to one entity type: content_variant, offer, trigger, experiment_proposal."
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Rows per page (default 50, max 200)."
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque cursor from a previous response's next_cursor."
        }
      ]
    },
    {
      "name": "get_events",
      "title": "Get events",
      "domain": "analytics",
      "scope": "read",
      "description": "Get recent site events: pageviews, clicks, conversions, custom events. Paginated: the response carries `next_cursor` — pass it back as `cursor` to continue, until it is null.",
      "params": [
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max events (default 100, max 200)"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "get_form_funnel",
      "title": "Get form funnel",
      "domain": "analytics",
      "scope": "read",
      "description": "Form conversion funnel analytics: views → starts → submits → abandons, per form and per page, with field-level drop-off. Counts and rates exclude sessions classified as automation at ingest (crawlers such as Meta's ad landing-page checker, which focuses an email field and leaves); each form reports that excluded volume under `automation`. OptinMonster popups (no <form> element) are tracked with form_id om-<campaignId>.",
      "params": [
        {
          "name": "window",
          "type": "\"24h\" | \"7d\" | \"30d\" | \"90d\"",
          "required": false,
          "description": "Lookback window (default 30d)"
        }
      ]
    },
    {
      "name": "get_growth_traffic_plan",
      "title": "Get growth traffic plan",
      "domain": "analytics",
      "scope": "read",
      "description": "Read prelaunch 50/50 copy-test traffic advice for one zone and audience. Uses observed personalized zone exposures over 28 days, not all eligible visits. Fixed-horizon estimates may be unknown and do not qualify repeated-look decisions or automatic launch. Does not create, publish or launch anything.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": ""
        }
      ]
    },
    {
      "name": "get_integration",
      "title": "Get integration",
      "domain": "integrations",
      "scope": "read",
      "description": "Get full config for a single integration.",
      "params": [
        {
          "name": "integration_id",
          "type": "string",
          "required": true,
          "description": "Integration ID"
        }
      ]
    },
    {
      "name": "get_integration_logs",
      "title": "Get integration logs",
      "domain": "integrations",
      "scope": "read",
      "description": "Recent fire log for an integration with success/error stats.",
      "params": [
        {
          "name": "integration_id",
          "type": "string",
          "required": true,
          "description": "Integration ID"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max rows (default 50)"
        }
      ]
    },
    {
      "name": "get_job",
      "title": "Get job",
      "domain": "site",
      "scope": "read",
      "description": "One async job's status: queued | running | succeeded | failed, with progress while running and result + result_ref (the API resource carrying the full artifact) once done. Poll this after a tool returns a job id (e.g. generate_page with async=true — a render takes 60-120s, so poll every ~15-30s). A failed job carries the error; generation_in_flight requeues itself, so failed means genuinely failed.",
      "params": [
        {
          "name": "job_id",
          "type": "string",
          "required": true,
          "description": "Job ID (job_…), from the submitting tool's response or list_jobs"
        }
      ]
    },
    {
      "name": "get_layout_experiment",
      "title": "Get layout experiment",
      "domain": "personalization",
      "scope": "read",
      "description": "One layout experiment in full: status, hypothesis, traffic split, covered zones (page_pattern + element_selector per container), every arm (control \"A\" plus fragment arms, each with per-zone variants, fragment metadata and impressions), schedule (ends_at), auto_stop and CWV guardrail settings, stop_reason, winner_arm_key — and `history`: every status transition with its timestamp, from/to, reason (manual | scheduled | significance | guardrail:<metric>:<arm>) and actor (user | scheduler | guardrail | api), plus winner/shipped flags, so \"why did this test stop?\" is always answerable from this one call. For the per-arm conversion/lift numbers use get_layout_experiment_results. Read-only.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "Layout experiment id (lexp_…), from list_layout_experiments"
        }
      ]
    },
    {
      "name": "get_layout_experiment_results",
      "title": "Get layout experiment results",
      "domain": "analytics",
      "scope": "read",
      "description": "Per-arm delivered-session descriptive results for a layout experiment: counts, conversions, rates, fixed-time p-values, sample-ratio checks and vitals guardrails. Read evidence_qualification first. peek_safe_significant is withheld until a finalized cumulative cohort and frozen analysis policy are verified; the legacy z heuristic cannot certify repeated-look decisions. Delivered-session rates are not automatically intent-to-treat causal lift. Read-only.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "Layout experiment id (lexp_…), from list_layout_experiments"
        },
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Exposure window in days (clamped to 1-365, default 90); the window still starts no earlier than the test's started_at and freezes at ended_at once ended"
        }
      ]
    },
    {
      "name": "get_lift_report",
      "title": "Get lift report",
      "domain": "analytics",
      "scope": "read",
      "description": "CONVERSION-RATE lift vs the always-on holdout control group (not revenue — revenue lift lives on get_scorecard). Default: the per-variant table; each row carries `zone_label` and `variant_label`, the friendly names to use instead of `zone_key` or ids. Pass `offer_id` for the per-OFFER roll-up (#1476): treated-vs-holdout CVR pooled across the offer's placements, delivery volume, and — once discount capture is live — an observed `redemption` block (redemptions, order-level discount spend, holdout-redemption leakage signal) that is explicitly NON-CAUSAL: redemption says people accepted free money, and the holdout comparison is the causal number. The response's `not_measured` array names what the report deliberately does not claim (incremental margin needs observed exposure). Pass offer_id=\"\" (empty string) to get every offer on the site. The response carries `holdout_epoch` — arms are measured per VISITOR only since `epoch_started_at` (#1380 cutover); pre-cutover per-visit sessions are excluded, so a window that spans the cutover covers fewer sessions than its day-count implies.",
      "params": [
        {
          "name": "offer_id",
          "type": "string",
          "required": false,
          "description": "Offer to roll up (from list_offers). Empty string = all offers. Omit entirely for the per-variant table."
        },
        {
          "name": "days",
          "type": "integer",
          "required": false,
          "description": "Window in days (default 30)."
        }
      ]
    },
    {
      "name": "get_optimizer",
      "title": "Get optimizer",
      "domain": "personalization",
      "scope": "read",
      "description": "RAW multi-armed bandit state: per-arm Thompson Sampling allocation weights, impressions, conversions and Beta posterior params (alpha/beta, expected_reward). READ THE GATES BEFORE COMPARING: every arm carries cvr_ci95 (95% Wilson interval on its observed rate) and `sufficient` (impressions at/above the scorecard's shared per-variant floor), and the response carries a top-level `warning` naming how many arms are below it. A 40-impression arm will regularly out-\"score\" a 4,000-impression arm by pure noise — never rank or declare winners across arms whose intervals overlap or whose sufficient flag is false, and never report expected_reward as a measured conversion rate (it is a posterior mean that includes the prior). These numbers refresh continuously: re-reading until a comparison looks decisive is peeking and inflates false positives. For the causal \"is it working\" verdict use get_scorecard.",
      "params": []
    },
    {
      "name": "get_page",
      "title": "Get page",
      "domain": "content",
      "scope": "read",
      "description": "One page in full: brief, revision history, publications, shareable preview URL (pre-publish, noindex) and the public URL when published.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page ID"
        }
      ]
    },
    {
      "name": "get_page_domain",
      "title": "Get page domain",
      "domain": "site",
      "scope": "read",
      "description": "The site's custom page-domain status (awaiting_ownership → awaiting_dns → cert_pending → live) with the exact DNS records to add. Calling this re-checks DNS/SSL state, so it doubles as \"check again\".",
      "params": []
    },
    {
      "name": "get_placement_report",
      "title": "Get placement report",
      "domain": "analytics",
      "scope": "read",
      "description": "The default report for \"what did each placement, version and segment do for goal X\" (email sign-ups, affiliate clicks, any site goal or raw event). Use it before get_zone_exposure_report, which stays available for the zone-level saw / did not render diagnostics. Three levels. (1) Placements: ONE row per thing placed, on however many pages. Every page that places the same component outside a test is one row; a component test is one row with the same test on other pages (the same arm letters, each arm rendering the same component, pinned revision and design settings, never matched on names or product lists; one test can use several components), a test that renders differently is its own row saying how, and the same component live outside a test on other pages joins it; version labels drop page tokens, item counts and status words; content zones on different pages are one row only when their names match once the page is taken out, and never when two of them are on the same page; a content or layout test is its own row. `pages`, `page_count` and `members` list its pages, each with its own `state` and test; `state.label` reads \"In a test on 5 pages\", \"Live on 5 pages\", or both. `goal_mixed` is set when its pages disagree on the goal: say \"mixed\" and list each goal with its pages. `aimed` lists placements built for the goal (its goal set on the placement, `goal_source` \"set\", or else a current campaign holding its content names it, \"campaign\"); `other` lists the remaining placements on pages where the goal happened; `not_shown` counts the rest. `pooling_note` says how the pages were added up, or why some were kept apart. Optional `qualified_views` contains separate geometry-qualified canary counts per source. Coverage is partial or unknown: empty cells stay null, source counts are non-additive, and none can be divided by legacy renders or used as a causal estimate. Use returned visibility rules and source windows; old caches may lack these measurements. Optional `attribution` carries separate same-session associations under `placement-attribution/1`: `after_click`, `after_qualified_view` and `associated` contain outcome-event and visit counts per source. Missing channels remain null and coverage is partial or unknown; check source status and reason. `direct` is null because accepted origins are not instrumented. Read `outcome_source`: primary conversion records, goal-hit observations or selected events have different meanings; a goal event does not prove accepted form completion. Categories are exclusive per outcome/source, while each visit belongs to its strongest category. Distinct legitimate outcomes remain distinct. Rows across sources are non-additive and do not establish causal lift. This section supports only its declared source types and windows; never infer a portfolio union or a historical accepted origin. (2) `variants`: who each version is for (`audience_label`), visitors shown (for a component test, visitors the test put in the version, rendered or not), conversions and rate; a test's versions, the original included, count from the test's start (`test.started_at`, each page's own when added up), so exposures from before the test never enter the comparison, added up across the placement's pages, with `by_page` giving each page's own counts. The versions of a component test are added up across the per-page tests by recorded arm/render identity; each page's test splits visitors the same way, and tests with different versions or splits stay separate rows. A visitor who reached two of the pages counts once on each. `comparison.kind` is \"vs_original\" (a test version against its original; separate page-test totals are descriptive with no pooled significance claim, while an individual source comparison uses the existing 0.05 / new versions method), \"original_arm\" (the original itself) or \"not_in_test\" (rates only: its effect can't be separated from other placements on the same pages; describe its segment and page performance; a test is optional). (3) `segments` under each version, added up the same way: the segment on the visitor's first pageview of the page, for both groups. For an individual source test, `control` is the original in the same segment, with the existing peek-safe test and then Benjamini–Hochberg correction (`p_adjusted`). Cells added across separate page tests are descriptive, with no combined decision or adjusted p-value. Source-specific `by_page.comparison` is available on newly computed reports; never reconstruct missing cached decisions from totals; for a version outside a test it is held-out visitors in the same segment and pages, rates only. A version for a custom audience or a targeting rule is not split by segment (`segments_note`). Personalized vs control group is in `pages`, the By page table, ONCE per page: everyone on the page who was not held out vs held-out visitors on the same page, counted from each visitor's first view of the page (from the visit's first event for every page), with the placements on it. The control group is held out site-wide, so that number covers every placement on the page: never present it as one placement's effect. Placement rows point to their pages with `page_ids`. The Scorecard headline's 30-day rates for visits before 2026-10-05 count from the visit's first score call instead, so they can read lower until those days leave the window. Every comparison carries `verdict` (too_early below the gate of enough visitors and 20 conversions per group, which is not a flat result and carries no p; higher, lower or no_clear_difference once the peek-safe test decides; descriptive for rates only), `counts_line` (\"1 of 6 vs 0 of 4\") and a plain `summary`; quote the counts with every rate. For a click or form goal (affiliate click, an outbound click-out goal, form submit), each placement and version also carries `inside_clicks`: clicks or submits that happened on an element inside it, added up across its pages with each visit counted once. `source` \"component_click\" means a component placement counted from its own click events over the whole window (`window_days`, the days it covers; `window_since` is set when the window was cut at a holdout-rate change, or for a test version when its test started inside the window: say \"since\" that day; for an affiliate goal only links the tag counts as affiliate, for an outbound goal links to other sites), merged with stamped clicks so a click counts once; \"tagged_click\" means a placement that is not a component, counted only from stamped clicks the tag sends since 2026-10-19 (`since`). Say which: never present a tagged_click count as the placement's whole history. A tagged_click zero is left out (null). It is a count with no control group: give it no verdict and never mix it into a comparison. The report's `inside_clicks.tagged_note` is true when a listed row counts from that day. Every id comes with a friendly `label`: use it, never the raw id (`label_is_id` true means no name exists). Each version (and each of its `segments` and `by_page` entries) also carries `engagement`, counted among personalized sessions with a render record (component_rendered for a component, personalization_applied otherwise): `saw`, `converted`, `direct` (reached the goal through the placement: the goal event fired inside it, or a click inside a component of the goal's link class), `influenced` (saw it, no Direct conversion, reached the goal at or after first seeing it in the same visit), `clickers` and `clicks` (any click on it), for the `full` window and `since_tracking` (visitors who saw it since 2026-10-19). A test version counts from its test's start, and only visitors the test put in it, so `saw` is never more than assigned. Read clicks from `clicks_window` and Direct / Influenced from `breakdown_window`, never mixing the two windows in one figure; `breakdown_window` null means the goal fires no event inside a placement. Display legacy saw as Rendered, not viewed or seen; rendering does not verify visibility. Display legacy influenced as Converted later: it is an observed same-session association after rendering. Do not infer an after-click/after-view split from legacy engagement; use only the separate versioned attribution section when available. Read reporting and definitions in the response, including old cached results. Ordinary placements show observed performance without a vs Original column or a winner. Placement-associated outcomes and per-page sessions are non-additive; never sum them into unique site totals. A configured test comparison requires valid assignment and analysis before claiming causality. Only a test version's vs_original comparison can support a causal conclusion when its design and analysis are valid. `summary` holds the section's tiles (placements built for the goal, how many are in a test, and their pages as one union vs the control group); `engagement` at the top says how Direct is known for the goal. The first run for a goal can take minutes, so it runs in the background: an answer with status \"computing\" means it is still working (ask again in a minute), and status \"failed\" means it did not finish (ask again with retry: true to start a new run). Answers come from a 5-minute cache: state `computed_at`, and the age when `cache.stale` is true. `state` (live, in_test, paused, draft, or mixed across pages) is read fresh: it says what the placement does now, while the counts cover the window.",
      "params": [
        {
          "name": "goal",
          "type": "string",
          "required": false,
          "description": "Goal to measure: \"primary\" (default), \"goal:<goal id>\" for any site goal from get_site_config, or \"event:<event type>\" for a raw tag event such as event:affiliate_click."
        },
        {
          "name": "days",
          "type": "integer",
          "required": false,
          "description": "Window in days (default 30). The window is cut at the last holdout-rate change."
        },
        {
          "name": "retry",
          "type": "boolean",
          "required": false,
          "description": "Start a new run after one that failed or ran out of time."
        }
      ]
    },
    {
      "name": "get_profile",
      "title": "Get profile",
      "domain": "audience",
      "scope": "read",
      "description": "Get detailed profile information including timeline, identifiers, CRM attributes, lifetime signals, session history, lead status (lead_captured history), and the opportunities this profile is a contact/lead on. Also returns `propensity_model`: the measured accuracy of this site’s conversion-propensity model that feeds `scores.intent` – status graded (held-out AUC with its method, holdout counts and a predicted-vs-observed calibration table), ungraded (\"Not enough conversions to grade this model yet (n/50)\") or no_model, each with a plain-words headline. Quote the headline with its method, never the AUC as a bare accuracy percentage. Also returns `value_tier` (low | mid | high: the predicted order-value tier on the visitor’s latest scored session if they convert, thirds of this site’s own order values; null when the site has no order-value model) with `value_tier_model`: that model’s measured accuracy – graded (held-out pairwise concordance with its method, tier agreement and the tier cuts) or no_model with the reason (\"not enough valued conversions (n/100)\"). Never quote a tier without its model’s headline.",
      "params": [
        {
          "name": "profile_id",
          "type": "string",
          "required": true,
          "description": "The profile UUID to look up"
        },
        {
          "name": "for_update",
          "type": "boolean",
          "required": false,
          "description": "Return only editable stored state and revision, without lazy healing or timeline enrichment. Use before a guarded update_profile."
        }
      ]
    },
    {
      "name": "get_route_performance",
      "title": "Get route performance",
      "domain": "content",
      "scope": "read",
      "description": "The OBSERVED route funnel per route (shown → routed_sessions via vt_route → destination conversions), descriptive not causal — filter by variant_id or campaign_id (the campaign rollup), days 1-90 (default 30). For the CAUSAL number use get_variant_performance: a route is an ordinary variant to the holdout lift lane.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": false,
          "description": "One route"
        },
        {
          "name": "campaign_id",
          "type": "string",
          "required": false,
          "description": "All routes under a campaign — the campaign rollup"
        },
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Window in days, 1-90 (default 30)"
        }
      ]
    },
    {
      "name": "get_salesforce_status",
      "title": "Get salesforce status",
      "domain": "crm",
      "scope": "read",
      "description": "Get Salesforce integration status, sync history, and coverage stats. Shows connection state, recent syncs, and how many profiles are enriched from Salesforce.",
      "params": []
    },
    {
      "name": "get_scorecard",
      "title": "Get scorecard",
      "domain": "analytics",
      "scope": "read",
      "description": "Whole-site primary-conversion comparison against the currently configured holdout, including eligible content, layout and component personalization. Returns arm counts, rates, goal, control_group, holdout_epoch, simulated and a gated status. Compare only the returned allocation period; older or unknown allocation stamps are excluded. Flat/learning means no clear difference yet, not equal performance. Relative lift is null when control has zero conversions. `lift.relative_ci` is the 95% interval on that relative lift (ratio scale, asymmetric; null when either arm has zero conversions) and `lift.absolute_ci` the interval on the rate difference; state the interval alongside the lift, not the point estimate alone. Use the status and peek_safe_significant gate, not the sign of lift or p-value alone, for a conclusion. Individual content/layout/component tests and trigger holdouts have separate result tools; this is not an average of their arm results. A labeled backtest projection may accompany insufficient live data. `simulated` reports whether simulator sessions were admitted into the verdict (demo sites only, behind a super-admin override) and how many were in the window; when `simulated.included` is true and `simulated.sessions` > 0 the result is demo evidence, not customer proof, and the text summary says so first. With include_goals (default true) the response also carries `results_by_goal`: every active site goal measured on the SAME treated and held-out sessions as this verdict (up to 90 days when the daily rollup covers the window; otherwise shortened to 30 and flagged window_capped). The primary goal's verdict is THIS response's status; never present a supporting goal as the headline. Supporting (equal-priority) goals are a corrected family: `higher`/`lower` only past a stricter check-any-time bar (alpha/m, stated in `family`), `too_early` means the sample gate is not met (not a flat result), and `descriptive` (lower-priority) rows carry rates only, no verdict. A goal row `ci` and `p_value` come from that same check-any-time test, so they always agree with its status (once the sample gate is met, p_value is at or below ci.alpha exactly when it is higher or lower; below it the status is too_early whatever p says); `p_value_single_look` is a one-look figure for reference only. `results_by_goal` is null when that read failed; the primary verdict is unaffected. `allocation_check` (on the verdict and on results_by_goal) tests whether visitors split into treated and control in the configured proportions; on a mismatch the verdict is withheld (status learning, goal rows `withheld`) because the split itself is broken – report that, never a lift.",
      "params": [
        {
          "name": "days",
          "type": "integer",
          "required": false,
          "description": "Number of days to look back (default: 30)"
        },
        {
          "name": "include_goals",
          "type": "boolean",
          "required": false,
          "description": "Also return results_by_goal: every active goal on the verdict's own visitors (default true)."
        }
      ]
    },
    {
      "name": "get_segment_members",
      "title": "Get segment members",
      "domain": "audience",
      "scope": "read",
      "description": "Paginated list of profiles in a segment with their identifiers. Prefer the cursor: the response carries `next_cursor` — pass it back as `cursor` to continue, until it is null. `offset` is legacy and skips/repeats rows when membership changes mid-walk. `total_members` counts the profiles AXO actually classified into the segment; `unclassified_members` counts those assigned at confidence 0 — the tag's default slot when nothing matched — and they belong to \"Default (unclassified)\", not to this segment. The listed rows are NOT filtered (filtering a cursor walk skips people): read each row's `segment_confidence` and treat 0 as unclassified.",
      "params": [
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": "Segment ID"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max results (default 50, max 200)"
        },
        {
          "name": "offset",
          "type": "number",
          "required": false,
          "description": "Legacy pagination offset — prefer cursor"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "get_segment_orchestration",
      "title": "Get segment orchestration",
      "domain": "audience",
      "scope": "read",
      "description": "List everything currently acting on one segment - zone variants, triggers, recurring audience syncs and recent exports - each annotated with whether the goals measured downstream of it are the goal the segment's conversion thesis names. Serving is zone-local, so five touchpoints can pull one segment in five directions with nothing above them noticing; this is the view where that disagreement becomes visible. Alignment is MEASURED, never inferred from copy: a variant is credited with the goals hit in sessions that saw it, a trigger with the goals hit by profiles after it fired. Below the observation floors the verdict is \"undetermined\" with a reason - report those as written rather than treating them as alignment. A segment whose thesis says \"insufficient evidence\" has no primary goal to align against, and alignment_blocked_by says so instead of every touchpoint being guessed at. Exports are annotated with whether the THESIS travelled with the membership list, not with a goal: AXO does not observe what an audience converts to off-platform. Descriptive only - this view reports what it finds and never re-targets anything. Use it when asked why a segment is underperforming, or before adding another touchpoint to a segment that already has several.",
      "params": [
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": "Segment id, from get_segments"
        },
        {
          "name": "window_days",
          "type": "number",
          "required": false,
          "description": "Measurement window: 7, 30 (default), or 90"
        }
      ]
    },
    {
      "name": "get_segment_performance",
      "title": "Get segment performance",
      "domain": "analytics",
      "scope": "read",
      "description": "Performance metrics for a segment: sessions, conversions, rate, revenue, AOV — all of them over the sessions AXO actually classified into the segment. `unclassified_sessions` counts the sessions carrying this segment's id at confidence 0, the tag's default slot when nothing matched; they are \"Default (unclassified)\", none of the figures above include them, and quoting them as this segment's audience is wrong even though the tag does serve them its variants. Also returns `attribution` (30-day window): `label_timing` says how many of the converting sessions ending in this segment already carried the label BEFORE they converted - `post_conversion_label_share` near 0 means the blended conversion_rate is real, while a high share means the segment absorbs converters after the fact (their conversion behavior produced the classifying signals) and the blended rate must never be quoted as intent or opportunity. `entry_label` is the strictest cohort - sessions already in this segment at their first event - and its conversion_rate is the honest predictive number to quote when the share is high. `attribution` is null when the segment has no conversions to attribute or the measurement failed.",
      "params": [
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": "Segment ID"
        }
      ]
    },
    {
      "name": "get_segment_thesis",
      "title": "Get segment thesis",
      "domain": "audience",
      "scope": "read",
      "description": "Get one segment's conversion thesis in full: which site goals it most over- or under-reaches versus the site baseline, an operator-readable reading, the evidence behind that reading (member vs site-wide sessions, conversion rates, and per-goal reach measured in the window), the confidence, and whether the thesis is stale. Works for pack, custom, ad-source, and ML-discovered segments. Every figure traces to a measurement in the window; a segment with too little traffic returns an explicit insufficient-evidence reading rather than a guess. Descriptive only - reading a thesis never changes what AXO serves.",
      "params": [
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": "Segment id, from get_segments"
        },
        {
          "name": "window_days",
          "type": "number",
          "required": false,
          "description": "Evidence window: 7, 30 (default), or 90"
        }
      ]
    },
    {
      "name": "get_segments",
      "title": "Get segments",
      "domain": "audience",
      "scope": "read",
      "description": "List all behavioral segments (plus operator-built custom segments) with session counts, conversion rates, descriptions, and each segment's conversion thesis. Counts are live over the last 30 days, consistent with get_analytics segment_distribution. `session_count` is CLASSIFIED membership: the sessions AXO actually scored into the segment. Sessions the tag's fallback parked on a segment's slug because nothing matched (confidence 0) are reported separately as `unclassified_session_count` and belong to \"Default (unclassified)\" — never describe those as members of the named segment or fold them into its size, and when a segment shows session_count 0 beside a large unclassified_session_count, say plainly that it is the default bucket rather than an audience. The two sum to the raw stamped membership. Use this to understand how visitors are being classified. Segments AXO discovered from behavior also carry `discovery`: what the group keyed on, as plain comparisons against the site average (\"about 3x the site average\"), plus its size, share of traffic, how many recent refreshes it has appeared in, and a coarse quality grade. Use it to explain why a discovered segment exists. `discovery` is null for rule-based segments (read their signal_rules instead) and for discovered ones the latest run did not produce. Its comparisons are deliberately coarse and its states (\"too small to characterize\", \"no distinguishing signals\") are honest — report them as written and do not extrapolate exact figures from them. Where `discovery` says what a group keyed on and the description says WHO is in it, `thesis` says why it matters to conversion: which site goals it over- or under-reaches, a short reading, and how much evidence is behind it. `thesis` is null when none has been built yet, and carries stale:true when the goals or the segment membership changed after it was written. Use get_segment_thesis for the full evidence behind one segment. The thesis is descriptive: it does not change what AXO serves. Every row also carries `expected_alignment`: non-null when the segment's own DEFINITION is upstream of, or overlapping with, a site goal, so its lift on that goal is fixed by construction rather than discovered - a form-starter segment against a form-submit goal, or a cart-adder against a purchase goal - or MEASURED from label-vs-conversion ordering (reason `post_goal_labeling`): a large share of the segment's conversions only received the segment label at or after the conversion itself, meaning visitors are classified by behavior the conversion produces, not behavior that predicts it. Scorer rows also carry `label_timing` (30-day window): of the converting sessions ending in this segment, how many carried the label before converting - `post_conversion_label_share` near 0 means the blended conversion_rate is trustworthy, and null label_timing means the measurement did not run (not that the segment is clean). When `expected_alignment` is set, report the number if asked but never present it as a finding, an opportunity, or evidence that the segment is high-intent, and never lead an audience report with it; its `label` says why in one sentence. null means the ordinary case, where the lift IS a finding.",
      "params": []
    },
    {
      "name": "get_sessions",
      "title": "Get sessions",
      "domain": "audience",
      "scope": "read",
      "description": "Get recent visitor sessions with behavioral signals, segment assignment, and page URLs. Useful for understanding real-time visitor behavior. Paginated: the response carries `next_cursor` — pass it back as `cursor` to continue, until it is null.",
      "params": [
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max results (default: 50, max: 200)"
        },
        {
          "name": "segment",
          "type": "string",
          "required": false,
          "description": "Filter by segment ID"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "get_site_allowance",
      "title": "Get site allowance",
      "domain": "site",
      "scope": "read",
      "description": "Read the current site account AI allowance and site entitlement limits. This is advisory, not a spend reservation or approval; execution rechecks policy and provider eligibility. Returns no credentials or payment details.",
      "params": []
    },
    {
      "name": "get_site_config",
      "title": "Get site config",
      "domain": "site",
      "scope": "read",
      "description": "Get current site configuration: brand, mode, conversion goal, settings. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "get_site_taxonomy",
      "title": "Get site taxonomy",
      "domain": "catalog",
      "scope": "read",
      "description": "Retrieve the parsed taxonomy mapping representing the site hierarchy.",
      "params": []
    },
    {
      "name": "get_sitemap_health",
      "title": "Get sitemap health",
      "domain": "analytics",
      "scope": "read",
      "description": "Sitemap crawl status, page counts, health metrics.",
      "params": []
    },
    {
      "name": "get_tag_health",
      "title": "Get tag health",
      "domain": "analytics",
      "scope": "read",
      "description": "Tag health: 24h beacon/pageview/personalization counts, render errors, first and last event time (lifetime, not windowed), and status — healthy (beacons in 24h) | no_data (none in 24h but this site HAS reported before, so something stopped) | no_signal (never reported at all; a fresh or unvisited install, not evidence of breakage). Also per-goal liveness (goal_liveness / goals_never_fired_90d): a goal with last_fired_at null has fired ZERO times in 90 days — usually a misconfigured detection rule (wrong URL pattern, tag not on the conversion page), not an absence of conversions.",
      "params": [
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which site to read (from get_account_status). Ignored on site-scoped sessions, which always read their own site."
        }
      ]
    },
    {
      "name": "get_target_account_matches",
      "title": "Get target account matches",
      "domain": "crm",
      "scope": "read",
      "description": "Which target accounts VISITED the site: cross-references saved company lists (TALs) against sessions whose visitor IP resolved to a company, returning per-account session counts and last-seen. Defaults to every list over the last 30 days; pass list_id and/or days to narrow. A match means an outbound target is now inbound — surface it, and ratify it into the pipeline with pipeline_create_opportunity.",
      "params": [
        {
          "name": "list_id",
          "type": "string",
          "required": false,
          "description": "Match a single list (default: all lists on the site)"
        },
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Observation window in days, 1-90 (default 30)"
        }
      ]
    },
    {
      "name": "get_trigger",
      "title": "Get trigger",
      "domain": "personalization",
      "scope": "read",
      "description": "Get full trigger configuration: events, dormancy, identifier type, destinations, payload templates.",
      "params": [
        {
          "name": "trigger_id",
          "type": "string",
          "required": true,
          "description": "Trigger ID"
        }
      ]
    },
    {
      "name": "get_trigger_lift",
      "title": "Get trigger lift",
      "domain": "analytics",
      "scope": "read",
      "description": "Per-trigger holdout-attributed lift: for every trigger with counted candidates in the window, the treated vs held-out conversion comparison (two-proportion z-test) plus link-token click-through. HONEST-NULL SEMANTICS, never round them to zero: `sufficient_data: false` means the sample gate (20 treated / 3 held-out candidates) has not been met and the lift/p-value fields are placeholders, not a flat result; `relative_lift` is null when the holdout arm converted zero times (no denominator — the absolute difference is still real); `links.ctr` is null when no link token was ever minted (nothing was attempted, not a 0% click rate). A trigger whose holdout arm is EMPTY may have a 0% holdout configured (safety triggers) — waiting will not fill it. An empty triggers array means no trigger had a counted candidate in the window, which is different from having no triggers (check list_triggers).",
      "params": [
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Window in days (default 30, max 90)."
        }
      ]
    },
    {
      "name": "get_trigger_template",
      "title": "Get trigger template",
      "domain": "personalization",
      "scope": "read",
      "description": "Get full details of a trigger payload template.",
      "params": [
        {
          "name": "template_id",
          "type": "string",
          "required": true,
          "description": "Template ID"
        }
      ]
    },
    {
      "name": "get_variant_performance",
      "title": "Get variant performance",
      "domain": "analytics",
      "scope": "read",
      "description": "RAW performance counts for one content variant over the bandit window: impressions, conversions, conversion_rate, render errors — observational, NOT causal (conversions are counted at segment level, so the rate is a rough engagement signal, and the causal number lives on get_lift_report / get_scorecard). The response carries conversion_rate_ci95 (95% Wilson interval), `sufficient` (impressions at/above the shared per-variant floor) and a top-level `warning` when the sample is below it: at small samples the rate is noise, so do not compare two variants' raw rates — especially at very different impression counts — and re-checking until a difference appears is peeking.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Variant ID"
        }
      ]
    },
    {
      "name": "get_vertical_pack",
      "title": "Get vertical pack",
      "domain": "site",
      "scope": "read",
      "description": "Get the full contents of a vertical pack (segments, zones, variants) before applying it.",
      "params": [
        {
          "name": "vertical",
          "type": "\"ecommerce\" | \"home_services\" | \"saas\" | \"financial\" | \"healthcare\" | \"nonprofit\" | \"affiliate\" | \"content_media\" | \"education\"",
          "required": true,
          "description": ""
        }
      ]
    },
    {
      "name": "get_zone_exposure_report",
      "title": "Get zone exposure report",
      "domain": "analytics",
      "scope": "read",
      "description": "Zone-level diagnostics; for placements, their versions and segments on a goal use get_placement_report, the default. Per ZONE: how many sessions SAW it (the tag recorded the render), how many should have but it never rendered, and how many were HELD OUT on the same pages, each with its rate on a chosen outcome. The outcome can be the primary goal, ANY site goal (metric \"goal:<id>\", including supporting goals the scorecard ignores), or a raw tag event (metric \"event:affiliate_click\"). Use this for \"did the zone move metric X\", e.g. affiliate clicks when they are not the primary goal. Three readings, kept apart. (1) `pages`: everyone who reached a page vs held out, ONCE per page pattern. The control group is held out site-wide, so every zone on the same page shares this comparison (each zone row repeats it as `causal`, scope \"page\"); never present it as one zone effect, because it cannot separate zones on the same page. (2) `test_comparisons` on a zone in a running test: visitors the test put in this arm vs visitors it put in the ORIGINAL arm of the same test, same measure and window (basis \"assignment\" for component tests, counted whether or not the component rendered; \"exposure\" for content and layout tests). This is the per-zone comparison; each treatment is held to 0.05 / `comparisons`. Known test comparisons begin at the later of the report start and `experiment.started_at`, including the latest content split change. Outcomes begin at the first eligible arm record. `measurement_window` equal to \"unknown_test_start\" preserves descriptive history but withholds statistical decisions. `counting_status` equal to \"unavailable\" means the count read failed: numeric compatibility fields are placeholders, never measured zeros. Page-wide holdout totals retain their separate report-window population; matching time bounds does not establish identical denominators. Display legacy SAW counts as Rendered; a render does not verify visibility. (3) saw / not rendered / held out are descriptive: saw vs not rendered is selection (visitors who stay long enough for a render differ from those who leave first), and `engagement` (saw vs held out) flatters the zone. Every verdict carries `decided_by` (\"sample_gate\" = too early: under the minimum sessions or 20 outcomes per group, which is not a flat result; \"peek_safe_test\") and `decision` (rule, alpha, z, threshold_z, p_value, interval). `p_value` is the peek-safe p of the test that sets the verdict: once the sample gate is met, higher or lower exactly when p_value <= decision.alpha (below the gate the verdict is insufficient_data whatever p says). `p_value_single_look` and `ci95` are one-look figures for reference only; never quote them beside a verdict. Grouping by goal: `goal_group` is \"aimed\" when one of the zone `intended_goals` is the measure, else \"other\" (built for another goal, or `goal_note` \"no_goal_set\"); `goal_grouping` counts them and is null when the links could not be read. Intended goals: the goal set on the zone or component placement (\"Built for goal\", `goal_source` \"set\", written with update_zone or update_component_placement goal_id) wins; a zone with none set takes the goal of each current (not ended or closed) campaign holding its content (`goal_source` \"campaign\"). For a raw event measure no zone is aimed. suggest_placement_goals proposes goals for zones with none set. Honest nulls: `relative_lift` is null when the comparison group converted zero times. `allocation_check.mismatch` true means the split did not land as configured: explain that before trusting any rate. `holdout_leaks` > 0 means held-out sessions have render records. \"Converted\" = the outcome happened in the session at or after the first view of a page the zone targets (for a test arm, at or after its first record in the test). The `all_zones` row counts each session once. Answers come from a 5-minute cache: `computed_at` says when the numbers were computed, and when `cache.stale` is true they are `cache.age_seconds` old (up to six hours) while a refresh runs, so state their age. Each zone row carries `lifecycle` (live, test_arm, paused or draft, with the running experiment for a test arm), read fresh on every call: it says what the zone does NOW, while the counts cover the window. Read the zone state from `lifecycle`, never from its name.",
      "params": [
        {
          "name": "metric",
          "type": "string",
          "required": false,
          "description": "Outcome to measure: \"primary\" (default), \"goal:<goal id>\" for any site goal from get_site_config, or \"event:<event type>\" for a raw tag event such as event:affiliate_click."
        },
        {
          "name": "days",
          "type": "integer",
          "required": false,
          "description": "Window in days (default 30). The window is cut at the last holdout-rate change."
        },
        {
          "name": "zone_id",
          "type": "string",
          "required": false,
          "description": "Limit to one zone (from get_zones). Omit for every zone with live content."
        }
      ]
    },
    {
      "name": "get_zones",
      "title": "Get zones",
      "domain": "personalization",
      "scope": "read",
      "description": "Get personalization zones. Pass zone_id to fetch a single zone (recommended when working on a specific zone — full response is 200KB+). Without zone_id, returns all zones for the site. Every zone carries `lifecycle`: `state` is whether it serves right now — live, test_arm (a serving arm of a running content, layout or component test; `experiment` names it, and `also_live_outside_test` says it serves plain content too), paused (everything in it is switched off or ended, or the zone itself is switched off while it holds published content) or draft (empty, nothing published yet, or not approved with nothing published) — plus a plain `summary`. Read the state from this field, never from the zone name: names like \"DRAFT ·\" or \"LIVE ·\" are free text and go stale. null means the state could not be read.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": false,
          "description": "If provided, return only this zone (much smaller response — use this when repairing a specific zone's field_schema)"
        }
      ]
    },
    {
      "name": "import_catalog",
      "title": "Import catalog",
      "domain": "catalog",
      "scope": "write",
      "description": "Import products into the product catalog. `url` must point at the product-detail page — it is the natural key, and rows without one are skipped. `inventory_quantity` is the only real stock number the system holds and is what makes rules like \"don't recommend items with 10 or fewer in stock\" expressible (see configure_product_reco filters).",
      "params": [
        {
          "name": "products",
          "type": "object[]",
          "required": true,
          "description": "Array of products to import"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "import_component_from_page",
      "title": "Import component from page",
      "domain": "content",
      "scope": "write",
      "description": "Turn a page revision (typically a design imported with import-claude-design-from-url) into a draft reusable component with one compiled revision. Strips fonts, scripts and inline SVG, re-hosts inlined images as site assets, keeps the design's CSS scoped to the component, and reports what was dropped. Does not approve or place: follow with approve_component_revision and create_component_placement. Re-running on the same page saves a new revision of the same component.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page id (page_…)"
        },
        {
          "name": "revision_id",
          "type": "string",
          "required": false,
          "description": "Page revision id (prev_…); defaults to the page's current revision"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "Component name; defaults to the page title"
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Component identity; defaults to page:<page_id>. Pass a different key to fork a second component from the same page."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        }
      ]
    },
    {
      "name": "import_figma_brand",
      "title": "Import figma brand",
      "domain": "assets",
      "scope": "write",
      "description": "Import and configure site brand definitions from a Figma file URL.",
      "params": [
        {
          "name": "figma_url",
          "type": "string",
          "required": true,
          "description": "URL to the Figma file/component"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "import_target_accounts",
      "title": "Import target accounts",
      "domain": "crm",
      "scope": "write",
      "description": "Import a target-account list (TAL) from an external prospecting tool — e.g. an Apollo accounts export — as a saved company list. Provide domains[] and/or paste raw export text as csv: anything domain-shaped is extracted (website URLs, bare domains, the company domain of corporate emails), free-mail domains are dropped, and everything is deduped. Requires the Company Graph module. After importing, use get_target_account_matches to see which target accounts are visiting the site.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Name for the list, e.g. \"Apollo — Q3 outbound\""
        },
        {
          "name": "domains",
          "type": "string[]",
          "required": false,
          "description": "Company domains or website URLs, one per target account."
        },
        {
          "name": "csv",
          "type": "string",
          "required": false,
          "description": "Raw pasted export (CSV or newline list). No column mapping needed — domains are extracted from anything domain-shaped."
        },
        {
          "name": "source",
          "type": "string",
          "required": false,
          "description": "Provenance slug recorded on the list, e.g. 'apollo'. Defaults to 'csv' when csv text was parsed, else 'import'."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "import-claude-design-from-url",
      "title": "Import claude design from url",
      "domain": "content",
      "scope": "write",
      "description": "Import a design into axo from a publicly fetchable URL. The file is a self-contained HTML bundle with all images, fonts, and styles inlined.",
      "params": [
        {
          "name": "url",
          "type": "string",
          "required": true,
          "description": "Public HTTPS URL to the design file. Valid for ~1 hour. Fetched server-side."
        },
        {
          "name": "claude_design_project_id",
          "type": "string",
          "required": false,
          "description": "Stable Claude Design project identifier. Reuse it to update the same imported axo page."
        },
        {
          "name": "title",
          "type": "string",
          "required": false,
          "description": "Suggested title for the imported design."
        },
        {
          "name": "as",
          "type": "\"page\" | \"component\"",
          "required": false,
          "description": "Also return a draft COMPONENT compiled from the imported page ('component'), ready to approve and place on the customer's own pages with approve_component_revision + create_component_placement. Default 'page'. Fonts, scripts and inline SVG are stripped; inlined images are re-hosted as site assets; the component_import.report says what was kept."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "install_lifecycle_recipe",
      "title": "Install lifecycle recipe",
      "domain": "personalization",
      "scope": "write",
      "description": "Install a lifecycle recipe as a trigger on this site. AXO builds the lifecycle event and shapes it for each destination; no payload template is needed. The trigger is created switched off unless enabled is true. Marketing Cloud Engagement destinations need mapping.event_definition_key unless the integration already has one.",
      "params": [
        {
          "name": "recipe_id",
          "type": "string",
          "required": true,
          "description": "Recipe id from list_lifecycle_recipes, e.g. abandoned_cart"
        },
        {
          "name": "destinations",
          "type": "object[]",
          "required": true,
          "description": "Where to deliver the event"
        },
        {
          "name": "settings",
          "type": "object",
          "required": false,
          "description": "Recipe settings; omitted settings take the recipe defaults"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "Trigger name (default: the recipe name)"
        },
        {
          "name": "enabled",
          "type": "boolean",
          "required": false,
          "description": "Switch the trigger on now (default false)"
        },
        {
          "name": "allow_duplicate",
          "type": "boolean",
          "required": false,
          "description": "Install again on an integration that already has this recipe (default false: refused with 409)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "launch_campaign",
      "title": "Launch campaign",
      "domain": "personalization",
      "scope": "write",
      "description": "Launch an APPROVED campaign (or resume a PAUSED one) as a durable job. Turns visitors-facing content ON: publishes approved pages, publishes live variants on manual-publishing sites, restarts experiments a pause stopped, then sets the campaign's serving gate active so linked variants and triggers serve inside the flight window. Refused (409 with details.plan) unless approved/paused and the manifest is ready. When approval is required, an unapproved campaign cannot launch (approval_required); submit it via request_review first. Idempotent and deduped per campaign: a retry or a second agent gets the same job, and completed steps are skipped. Returns the job – if status is queued/running, poll get_job. Never sets offers live and never enables ad platform campaigns (reported as needs_operator / reference_only). Confirm with the operator first; call get_campaign_launch_plan to show what will happen.",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "actor",
          "type": "string",
          "required": false,
          "description": "Who is launching (recorded on the job and audit trail)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "link_identifier",
      "title": "Link identifier",
      "domain": "audience",
      "scope": "write",
      "description": "Link a new identifier (email, phone, external_id) to a profile.",
      "params": [
        {
          "name": "profile_id",
          "type": "string",
          "required": true,
          "description": "Profile ID"
        },
        {
          "name": "type",
          "type": "\"email\" | \"phone\" | \"external_id\"",
          "required": true,
          "description": "Identifier type"
        },
        {
          "name": "value",
          "type": "string",
          "required": true,
          "description": "Identifier value"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "list_ad_theme_suggestions",
      "title": "List ad theme suggestions",
      "domain": "personalization",
      "scope": "read",
      "description": "List pending ad-theme suggestions for the current site before approving or rejecting them.",
      "params": []
    },
    {
      "name": "list_aeo_artifacts",
      "title": "List AEO artifacts",
      "domain": "site",
      "scope": "read",
      "description": "List visibility remediation artifacts. Supply finding_id to discover available_kinds and generated_budget before generating; optional status filters draft, approved, rejected, delivered or superseded. Approval and delivery require a signed-in human.",
      "params": [
        {
          "name": "finding_id",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "status",
          "type": "string",
          "required": false,
          "description": ""
        }
      ]
    },
    {
      "name": "list_aeo_findings",
      "title": "List AEO findings",
      "domain": "site",
      "scope": "read",
      "description": "List answer-engine visibility findings for this site: blocked AI crawlers, missing structured data, prompts no engine answers with your brand, third-party sources the engines keep citing instead. Ranked worst-first.",
      "params": [
        {
          "name": "status",
          "type": "string",
          "required": false,
          "description": "Comma-separated: open, acknowledged, resolved, wont_fix"
        },
        {
          "name": "category",
          "type": "string",
          "required": false,
          "description": "Comma-separated finding categories"
        },
        {
          "name": "severity",
          "type": "string",
          "required": false,
          "description": "Comma-separated: low, medium, high, critical"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Maximum findings to return (default 100, max 500)"
        }
      ]
    },
    {
      "name": "list_agent_activity",
      "title": "List agent activity",
      "domain": "analytics",
      "scope": "read",
      "description": "READ-ONLY: this site's own change ledger — the tenant-facing audit feed of every recorded mutation on the site: MCP tool calls by connected agents (yours and any other token's, so you can review your own past actions), dashboard edits by humans, and system/background writes. Each event carries time, actor (agent = MCP token name, human = user email, or system), action (e.g. \"mcp.create_segment\", \"profile.delete\"), tool, the recorded argument KEYS only (values are never stored — the trail is not a PII sink), entity hints, and status. Use it to answer \"what changed on this site, when, and by whom / by which agent\". Newest first; page with limit + cursor (pass next_cursor from the previous page — keyset, stable under new writes). Honest limits: events are recorded at the call boundary, so a row proves the call was MADE, not that it succeeded; PII-bearing READ tools appear only as a small sample (sampled_read: true) and most reads never appear at all. Changes nothing.",
      "params": [
        {
          "name": "actor",
          "type": "\"agent\" | \"human\" | \"system\"",
          "required": false,
          "description": "Filter by actor kind: agent = MCP token, human = dashboard user, system = background/api-key writes."
        },
        {
          "name": "since",
          "type": "string",
          "required": false,
          "description": "Only events at/after this ISO 8601 timestamp."
        },
        {
          "name": "until",
          "type": "string",
          "required": false,
          "description": "Only events at/before this ISO 8601 timestamp."
        },
        {
          "name": "tool",
          "type": "string",
          "required": false,
          "description": "Action/tool name prefix, e.g. \"create_\", \"mcp.\", or \"profile.\"."
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": "Page size (default 50, max 200)."
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "next_cursor from the previous page."
        }
      ]
    },
    {
      "name": "list_ai_actions",
      "title": "List AI actions",
      "domain": "analytics",
      "scope": "read",
      "description": "Read the durable, partial AI change ledger. Currently covers active AI-derived content published through manual revisions; auto-mode edits, experiment activation, pages and overrides are not yet covered. Stored application actor attribution does not prove human approval. Includes before/after snapshots, model when recorded, reversal links and keyset pagination. No mutation or rollback is performed.",
      "params": [
        {
          "name": "actor",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "entity_type",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": ""
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": ""
        }
      ]
    },
    {
      "name": "list_campaigns",
      "title": "List campaigns",
      "domain": "personalization",
      "scope": "read",
      "description": "List all campaigns for the site with status and briefs. To page through a long list, pass `limit`: the response then carries `next_cursor` — pass it back as `cursor` to continue, until it is null. Cursored pages are ordered newest-created first (not active-first).",
      "params": [
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Page size (max 200). Passing this switches the response to paginated mode with `next_cursor`."
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "list_catalog_filter_fields",
      "title": "List catalog filter fields",
      "domain": "catalog",
      "scope": "read",
      "description": "What can recommendations be filtered or weighted on? Returns every product_catalog column an operator may write a predicate against, READ FROM THE LIVE SCHEMA (so a column added by a recent migration is listed the day it exists), plus the operator list, the weight multiplier range, and the missing-value rules for filters and weights (which differ). Call this before configure_product_reco filters or catalog_weights so field names, ops and multipliers are real rather than guessed.",
      "params": []
    },
    {
      "name": "list_component_experiments",
      "title": "List component experiments",
      "domain": "personalization",
      "scope": "read",
      "description": "List controlled component tests and lifecycle state. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": false,
          "description": "placement id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "list_component_placement_history",
      "title": "List component placement history",
      "domain": "personalization",
      "scope": "read",
      "description": "Read previous full placement bindings, revisions, audiences and schedules for rollback. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "list_component_placements",
      "title": "List component placements",
      "domain": "personalization",
      "scope": "read",
      "description": "List component placements with anchors, pinned revision, audience, active state and concurrency version. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": false,
          "description": "component id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "list_components",
      "title": "List components",
      "domain": "content",
      "scope": "read",
      "description": "List reusable site components, current draft and approved revisions, and live usage counts. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "q",
          "type": "string",
          "required": false,
          "description": "q"
        },
        {
          "name": "status",
          "type": "string",
          "required": false,
          "description": "status"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "list_content_experiments",
      "title": "List content experiments",
      "domain": "personalization",
      "scope": "read",
      "description": "List fixed-split content tests (A/B/n of ordinary hero/copy/offer variants) and their lifecycle state. Optional zone_id / status filters.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": false,
          "description": "Restrict to one zone id"
        },
        {
          "name": "status",
          "type": "\"draft\" | \"running\" | \"paused\" | \"ended\"",
          "required": false,
          "description": "Restrict to one status"
        }
      ]
    },
    {
      "name": "list_content_sources",
      "title": "List content sources",
      "domain": "content",
      "scope": "read",
      "description": "List captured content-backlog sources for the blog pipeline. Filter by status (new/used/skipped) and recency.",
      "params": [
        {
          "name": "status",
          "type": "\"new\" | \"used\" | \"skipped\"",
          "required": false,
          "description": "Default: new"
        },
        {
          "name": "days",
          "type": "number",
          "required": false,
          "description": "Look-back window in days (default 30)"
        }
      ]
    },
    {
      "name": "list_content_variant_revisions",
      "title": "List content variant revisions",
      "domain": "personalization",
      "scope": "read",
      "description": "List the published revisions of a content variant, newest first, plus which one is currently published. A revision is a publish act (what visitors saw); get_content_history is the per-save audit trail of the working copy. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Variant ID"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max revisions to return (default 20, max 100)"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "list_custom_segments",
      "title": "List custom segments",
      "domain": "audience",
      "scope": "read",
      "description": "List saved custom audience definitions and their latest computation state.",
      "params": []
    },
    {
      "name": "list_customer_analytics_connections",
      "title": "List customer analytics connections",
      "domain": "analytics",
      "scope": "read",
      "description": "List this site’s GA4 and Search Console bindings without credentials. authorization_saved means a grant is stored, not that Google currently accepts it. Use the returned id with get_customer_ga4_report or get_customer_gsc_report.",
      "params": []
    },
    {
      "name": "list_dam_connections",
      "title": "List DAM connections",
      "domain": "assets",
      "scope": "read",
      "description": "List the customer's DAM connections (Cloudinary, S3-compatible, etc.) for this site — masked credentials, active flag, provider metadata. Use before search_dam to discover which DAMs are wired (or to confirm one is connected before suggesting search_dam to the operator). Returns { connections: [{ id, provider, provider_meta, config_preview, active, ... }] }; credentials in config_preview are masked (api_secret as \"••••XXXX\"). Empty array when no DAM is connected.",
      "params": []
    },
    {
      "name": "list_dedup_candidates",
      "title": "List dedup candidates",
      "domain": "audience",
      "scope": "read",
      "description": "Find profiles sharing identifiers (merge candidates for deduplication).",
      "params": []
    },
    {
      "name": "list_forms",
      "title": "List forms",
      "domain": "personalization",
      "scope": "read",
      "description": "List reusable lead-capture form templates for this site. Each has a name and a payload (title, cta_text, tcpa_text, success_text, accent_color, fields[], calendar_enabled).",
      "params": []
    },
    {
      "name": "list_image_gen_keys",
      "title": "List image gen keys",
      "domain": "assets",
      "scope": "read",
      "description": "List BYOK image-gen provider credentials for this site (Gemini, OpenAI, Adobe Firefly). The generate_image tool routes through whichever has saved credentials + provider matches the request. Use to discover which providers are available before suggesting generate_image, or to confirm a custom_model_id (BYOM) is set on a connection. Returns { keys: [...] } with masked credentials. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "list_integrations",
      "title": "List integrations",
      "domain": "integrations",
      "scope": "read",
      "description": "List all integrations (Klaviyo, Braze, Resend, webhooks, CAPI) with status.",
      "params": []
    },
    {
      "name": "list_jobs",
      "title": "List jobs",
      "domain": "site",
      "scope": "read",
      "description": "List async jobs for this site, newest first — the queue behind async tool calls (generate_page async=true first). Filter by kind and/or status (queued, running, succeeded, failed). Pages by keyset: pass the next_cursor from a previous response as cursor to fetch older rows.",
      "params": [
        {
          "name": "kind",
          "type": "string",
          "required": false,
          "description": "Filter to one job kind, e.g. \"generate_page\""
        },
        {
          "name": "status",
          "type": "\"queued\" | \"running\" | \"succeeded\" | \"failed\"",
          "required": false,
          "description": "Filter by status. Omit for all."
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Rows per page (default 50, max 200)."
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque keyset cursor from a previous response's next_cursor — fetches the next (older) page"
        }
      ]
    },
    {
      "name": "list_layout_experiments",
      "title": "List layout experiments",
      "domain": "personalization",
      "scope": "read",
      "description": "List the site's layout experiments — page-level A/B tests of whole containers (header, hero, grid, footer), each testing the original layout (control arm \"A\") against one or more alternative-layout fragment arms. Returns per experiment: id, name, status (draft | running | paused | ended), page_type, covered zones, arms with allocation weight and impressions, schedule (ends_at), auto_stop and Core Web Vitals guardrail settings, stop_reason and winner_arm_key. Pass page_type to scope to one page type. Pass include_results: true to attach each non-draft experiment's per-arm real-exposure results inline (same readout as get_layout_experiment_results; draft experiments get results: null) — the Scorecard's \"Layout tests\" view in one call. Read-only.",
      "params": [
        {
          "name": "page_type",
          "type": "string",
          "required": false,
          "description": "Scope to one page type (e.g. home, pdp, plp). Omit for all experiments."
        },
        {
          "name": "include_results",
          "type": "boolean",
          "required": false,
          "description": "Attach per-arm results inline for every non-draft experiment (heavier; omit when only the list/status is needed)"
        }
      ]
    },
    {
      "name": "list_leads",
      "title": "List leads",
      "domain": "audience",
      "scope": "read",
      "description": "List captured leads (lead_captured events) for this site: contact info, scheduled appointment slot, behavioral segment, and source page. Paginated: the response carries `next_cursor` — pass it back as `cursor` to continue, until it is null.",
      "params": [
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max results (default 100, max 200)"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "list_lifecycle_recipes",
      "title": "List lifecycle recipes",
      "domain": "personalization",
      "scope": "read",
      "description": "List the ready-made lifecycle recipes (abandoned cart, abandoned browse, abandoned checkout, high-intent return, lapsed visitor). Each entry gives the event it watches, the settings a site may change with their bounds, and the integration types it can deliver to (klaviyo, braze, sfmc, webhook, custom).",
      "params": []
    },
    {
      "name": "list_lightbox_presets",
      "title": "List lightbox presets",
      "domain": "personalization",
      "scope": "read",
      "description": "List the canned lightbox display-rule presets (Welcome offer, Exit intent newsletter, Cart recovery, Promo bar, Returning visitor offer). Each preset returns a complete DisplayRules object that can be passed verbatim as create_zone.display_rules or update_zone.display_rules. Use this instead of authoring the rules JSON from scratch unless you need a custom combination.",
      "params": []
    },
    {
      "name": "list_notifications",
      "title": "List notifications",
      "domain": "site",
      "scope": "read",
      "description": "List the workspace notifications inbox for this site: layout-test alerts, tips, and recommendations, newest first. Each row has id, kind, title, body, status (active = unread/unhandled, dismissed, acted), instance_key, and created_at. Use status=\"active\" to see what still needs attention. Pages by keyset: pass the next_cursor from a previous response as cursor to fetch older rows.",
      "params": [
        {
          "name": "status",
          "type": "\"active\" | \"dismissed\" | \"acted\"",
          "required": false,
          "description": "Filter by status. Omit for the full log."
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max rows per page (1-200; default 100 for active, 200 otherwise)"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque keyset cursor from a previous response’s next_cursor — fetches the next (older) page"
        }
      ]
    },
    {
      "name": "list_offers",
      "title": "List offers",
      "domain": "personalization",
      "scope": "read",
      "description": "List this site's offer library plus the guardrail dials every offer write is validated against. Returns each non-deleted offer (name, status, offer_type, value_bp / value_cents, code, validity window, stacking, approved, code_verified_at), `site_dials` (max_discount_pct — the hard depth ceiling; gross_margin_pct — null when the operator has not configured one), and a per-offer `break_even` hint: the relative conversion lift the discount must produce just to pay for itself (d / (m - d)), null when no margin rate is set. Call this before authoring or placing anything — it is how you tell a ratified, servable offer (status \"live\" AND approved AND code_verified_at) from a draft that will render nothing. AXO is a SHADOW promo layer: these rows REFERENCE a discount code the merchant's own platform already honors; we never mint one and never touch redemption accounting.",
      "params": []
    },
    {
      "name": "list_optin_forms",
      "title": "List optin forms",
      "domain": "audience",
      "scope": "read",
      "description": "List the SITE-NATIVE forms designated for opt-in lead capture. Designated forms are the only native forms the tag ever reads values from — on submit, the email (and name/phone when field selectors are designated) lands in Leads, profiles, and CRM auto-intake. Forms can also be designated config-free by adding the data-axo-optin attribute in the site's own HTML; those do NOT appear here. Use get_form_funnel to discover which native forms exist and how they perform before designating one.",
      "params": []
    },
    {
      "name": "list_page_configs",
      "title": "List page configs",
      "domain": "personalization",
      "scope": "read",
      "description": "List page type configurations (product, category, blog).",
      "params": []
    },
    {
      "name": "list_page_suggestions",
      "title": "List page suggestions",
      "domain": "content",
      "scope": "read",
      "description": "List suggested pages for this site — each an answer-engine visibility gap (a question no engine answers with this brand) with evidence and a proposed archetype + outline. Accepting creates an unpublished draft; nothing publishes itself.",
      "params": []
    },
    {
      "name": "list_pages",
      "title": "List pages",
      "domain": "content",
      "scope": "read",
      "description": "List generated pages for this site with status (draft, preview, approved, published, retired). To page through a long list, pass `limit`: the response then carries `next_cursor` — pass it back as `cursor` to continue, until it is null.",
      "params": [
        {
          "name": "status",
          "type": "string",
          "required": false,
          "description": "Optional status filter"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Page size (max 200). Passing this switches the response to paginated mode with `next_cursor`."
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "list_preview_share_links",
      "title": "List preview share links",
      "domain": "personalization",
      "scope": "read",
      "description": "List this site's client review links (newest first) with status (active, expired, revoked), audience, view, page, expiry and whether they have been opened. Pass zone_id to narrow to one zone.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": false,
          "description": "Optional zone id filter"
        }
      ]
    },
    {
      "name": "list_products",
      "title": "List products",
      "domain": "catalog",
      "scope": "read",
      "description": "Search and list catalog products/items for a site. Allows filtering by category, specific product IDs, or including ignored items. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "ids",
          "type": "string[]",
          "required": false,
          "description": "Array of specific product IDs to fetch (optional)"
        },
        {
          "name": "category",
          "type": "string",
          "required": false,
          "description": "Filter by category (optional)"
        },
        {
          "name": "include_ignored",
          "type": "boolean",
          "required": false,
          "description": "If true, include ignored products in the output (default false)"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max results (default 100, max 500)"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "list_reco_presets",
      "title": "List reco presets",
      "domain": "catalog",
      "scope": "read",
      "description": "Ready-made recommendation rules — exclude out of stock, de-emphasise low stock, exclude discontinued, rating floor, require a product image, and so on. This is the answer to \"what can I filter on\" for someone who does not already know: a list rather than a schema. Each preset returns the LITERAL filters and catalog_weights it would write, plus an impact preview measured against this site's own catalog — how many items it would drop, how many it would re-rank, and which referenced columns have no data at all (a rule on an unpopulated column changes nothing, which is worth saying before it is applied rather than after). Apply one by passing its filters / catalog_weights verbatim to configure_product_reco; there is no one-word apply on purpose. State the rules and the impact to the operator before applying them. A preset naming a column this catalog does not have comes back with available:false and the reason.",
      "params": []
    },
    {
      "name": "list_reviews",
      "title": "List reviews",
      "domain": "personalization",
      "scope": "read",
      "description": "List approval requests for the current site across workflow states.",
      "params": [
        {
          "name": "state",
          "type": "\"needs_me\" | \"in_review\" | \"changes_requested\" | \"approved\" | \"drafts\" | \"live\" | \"all\"",
          "required": false,
          "description": "Workflow state filter. Defaults to all."
        }
      ]
    },
    {
      "name": "list_routes",
      "title": "List routes",
      "domain": "content",
      "scope": "read",
      "description": "Every route on the site: entry variant, hosting zone, audience, campaign (name/status), destination page, and health — destination_published=false flags a route whose page will 404.",
      "params": []
    },
    {
      "name": "list_taxonomy_terms",
      "title": "List taxonomy terms",
      "domain": "catalog",
      "scope": "read",
      "description": "List the site's taxonomy terms — the brand/topic/category vocabulary that drives per-visitor interest/affinity scoring. Optionally filter by dimension.",
      "params": [
        {
          "name": "dimension",
          "type": "\"brand\" | \"topic\" | \"category\"",
          "required": false,
          "description": "Filter to one dimension"
        }
      ]
    },
    {
      "name": "list_text_gen_keys",
      "title": "List text gen keys",
      "domain": "assets",
      "scope": "read",
      "description": "List BYOM text-gen credentials for this site (OpenAI-compatible endpoints — vLLM, Ollama, Azure OpenAI, LM Studio). When a key is active AND experimental_byom_enabled is on, /api/content/draft routes the variant-authoring AI calls through it instead of platform Anthropic. Use to discover whether the operator has BYOM text wired before suggesting draft_content_variant. Returns { keys: [...] } with masked api_key fields.",
      "params": []
    },
    {
      "name": "list_trigger_fires",
      "title": "List trigger fires",
      "domain": "personalization",
      "scope": "read",
      "description": "Get the audit log of fire attempts for a trigger: successes, errors, skipped fires with skip_reason. Paginated: the response carries `next_cursor` — pass it back as `cursor` to continue, until it is null.",
      "params": [
        {
          "name": "trigger_id",
          "type": "string",
          "required": true,
          "description": "Trigger ID"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max rows (default 100, max 500)"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "list_trigger_templates",
      "title": "List trigger templates",
      "domain": "personalization",
      "scope": "read",
      "description": "List available payload templates: system seeds and site-owned custom templates.",
      "params": []
    },
    {
      "name": "list_triggers",
      "title": "List triggers",
      "domain": "personalization",
      "scope": "read",
      "description": "List all triggered messages for the site with watch event, mode, destinations, and enabled status.",
      "params": []
    },
    {
      "name": "list_verticals",
      "title": "List verticals",
      "domain": "site",
      "scope": "read",
      "description": "List available industry vertical packs (ecommerce, home_services, saas, financial, healthcare, nonprofit, affiliate, content_media, education). Each pack bundles default segments, zones, and variants tailored to that industry.",
      "params": []
    },
    {
      "name": "log_article",
      "title": "Log article",
      "domain": "content",
      "scope": "write",
      "description": "Log an interesting article, link, or point of view into the content backlog. The weekly blog pipeline drafts posts from these. Provide a url and/or a note explaining why it matters.",
      "params": [
        {
          "name": "url",
          "type": "string",
          "required": false,
          "description": "Link to the article/source (optional if note is given)"
        },
        {
          "name": "title",
          "type": "string",
          "required": false,
          "description": "Title of the article (optional)"
        },
        {
          "name": "note",
          "type": "string",
          "required": false,
          "description": "Your angle: why this is interesting, what point it supports (optional if url is given)"
        },
        {
          "name": "tags",
          "type": "string[]",
          "required": false,
          "description": "Freeform tags, e.g. [\"agentic\", \"segmentation\"]"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "mark_notification_read",
      "title": "Mark notification read",
      "domain": "site",
      "scope": "write",
      "description": "Resolve a notification from the inbox after you have seen or handled it. status \"dismissed\" (default) = acknowledged/no longer needs attention; \"acted\" = the recommended action was actually taken. One-way: a resolved notification never returns to active, but it stays visible in the inbox log.",
      "params": [
        {
          "name": "notification_id",
          "type": "string",
          "required": true,
          "description": "Notification ID (from list_notifications)"
        },
        {
          "name": "status",
          "type": "\"dismissed\" | \"acted\"",
          "required": false,
          "description": "Target status (default: dismissed)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "merge_categories",
      "title": "Merge categories",
      "domain": "catalog",
      "scope": "write",
      "description": "Merge and rename product categories (renames a category across all products in sitemap and page profiles).",
      "params": [
        {
          "name": "old_category",
          "type": "string",
          "required": true,
          "description": "Current category name to rename"
        },
        {
          "name": "new_category",
          "type": "string",
          "required": true,
          "description": "New category name"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "merge_profiles",
      "title": "Merge profiles",
      "domain": "audience",
      "scope": "admin",
      "description": "Merge two profiles. Moves identifiers, sessions, conversions from source to target, deletes source.",
      "params": [
        {
          "name": "source_id",
          "type": "string",
          "required": true,
          "description": "Profile to merge FROM (deleted)"
        },
        {
          "name": "target_id",
          "type": "string",
          "required": true,
          "description": "Profile to merge INTO (kept)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "mint_link_tokens",
      "title": "Mint link tokens",
      "domain": "audience",
      "scope": "write",
      "description": "Mint single-use link tokens so an email sent from a third-party ESP (Klaviyo, HubSpot, Mailchimp, Braze, Salesforce Marketing Cloud) can identify the recipient on click. Give it the column the ESP list is keyed on — usually email — and it returns a merge-field column: paste axo_token (or axo_link, if you pass landing_url) into the send, and when a recipient clicks, the AXO tag on the landing page redeems it and the anonymous session merges with their known profile. Recipients with no matching profile come back under unresolved_recipients and simply get no token; their send still works, the visitor just stays anonymous. IMPORTANT: this is deterministic identity, not authentication — a forwarded email hands it to whoever opens it, so use it to personalize content, never to unlock account data, order history or PII. Tokens are single-use and expire (72h default); re-mint per send rather than reusing a column. Profiles that declined marketing consent are skipped automatically.",
      "params": [
        {
          "name": "recipients",
          "type": "string[]",
          "required": true,
          "description": "Identifier values from the ESP list, one per recipient. Max 200 per call from this tool."
        },
        {
          "name": "identifier_type",
          "type": "string",
          "required": false,
          "description": "What the recipients array contains: email|email_sha256|phone|phone_sha256|external_id|profile_id. Defaults to email, the realistic ESP join key."
        },
        {
          "name": "landing_url",
          "type": "string",
          "required": false,
          "description": "Absolute URL the email links to. When given, each row also gets a ready-to-paste tokenized URL."
        },
        {
          "name": "ttl_seconds",
          "type": "number",
          "required": false,
          "description": "Token lifetime. Defaults to 72h; capped at 14 days. Raise it when the send is scheduled well after the export."
        },
        {
          "name": "format",
          "type": "string",
          "required": false,
          "description": "Output shape: 'rows' or 'csv'. Defaults to csv, which is upload-ready."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pause_ad_campaign",
      "title": "Pause ad campaign",
      "domain": "integrations",
      "scope": "admin",
      "description": "PAUSE a LIVE ad campaign on the customer's connected Google Ads or Meta ad account. THIS SPENDS-CONTROL REAL MONEY: delivery stops within minutes of the platform applying it, and there is deliberately NO un-pause tool — restarting the campaign means the platform's own UI. Requires admin scope and is refused to destructive-denied tokens. Confirm with the operator that they mean THIS campaign (get_ad_performance gives campaign_ref + name + current spend) before calling. Fails with a re-consent hint when the stored OAuth grant lacks the write permission (Meta grants made before the ads_management scope change need a re-connect).",
      "params": [
        {
          "name": "platform",
          "type": "string",
          "required": true,
          "description": "\"google_ads\" or \"meta_ads\""
        },
        {
          "name": "campaign_ref",
          "type": "string",
          "required": true,
          "description": "The provider's numeric campaign id, from get_ad_performance"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pause_campaign",
      "title": "Pause campaign",
      "domain": "personalization",
      "scope": "write",
      "description": "Pause a LAUNCHED campaign as a durable job. Immediately stops every variant and trigger linked to the campaign (serving gate off) and pauses its running experiments (never ends them). Does NOT retire published pages and does NOT pause ad platform campaigns - both come back as needs_operator so you can tell the operator exactly what is still running. Reversible with launch_campaign. Idempotent: pausing a paused campaign runs again to finish anything an interrupted pause left running, and job.result.outcome is pause_completed (it paused something) or already_paused (nothing left).",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "actor",
          "type": "string",
          "required": false,
          "description": "Who is pausing (recorded on the job and audit trail)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pause_component_placement",
      "title": "Pause component placement",
      "domain": "personalization",
      "scope": "write",
      "description": "Pause a component placement; insertion is removed or original content restored on the next config reconciliation. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_acquire_claim",
      "title": "Pipeline acquire claim",
      "domain": "crm",
      "scope": "write",
      "description": "Acquire an opportunity-wide execution lease for an owned live run. Another live holder conflicts. Same-run retries return the existing token without extending it. Expiry does not prove an earlier external effect never happened; reconcile before repeating work.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "Opportunity id in the selected account"
        },
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": "Owned live run UUID"
        },
        {
          "name": "lease_seconds",
          "type": "integer",
          "required": false,
          "description": "30..300 seconds; default 120, bounded by run expiry"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_add_contact",
      "title": "Pipeline add contact",
      "domain": "crm",
      "scope": "write",
      "description": "Add a contact (name, email, phone, role) to a CRM opportunity. Set is_primary to make them the headline contact.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…)"
        },
        {
          "name": "name",
          "type": "string",
          "required": true,
          "description": "Contact full name"
        },
        {
          "name": "email",
          "type": "string",
          "required": false,
          "description": "Email address"
        },
        {
          "name": "phone",
          "type": "string",
          "required": false,
          "description": "Phone number"
        },
        {
          "name": "role",
          "type": "string",
          "required": false,
          "description": "Role, e.g. \"Owner\""
        },
        {
          "name": "is_primary",
          "type": "boolean",
          "required": false,
          "description": "Make this the primary contact"
        },
        {
          "name": "run_id",
          "type": "string",
          "required": false,
          "description": "Your live run UUID from pipeline_start_run, when writing under an execution claim. Pass together with claim_token; a stale or replaced claim is refused (409) — re-acquire, never retry blindly."
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": "Current claim token from pipeline_acquire_claim for this opportunity."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_add_note",
      "title": "Pipeline add note",
      "domain": "crm",
      "scope": "write",
      "description": "Append a note to a CRM opportunity's activity timeline (append-only): call summaries, next steps, observations.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…)"
        },
        {
          "name": "body",
          "type": "string",
          "required": true,
          "description": "The note text (max 10k chars)"
        },
        {
          "name": "run_id",
          "type": "string",
          "required": false,
          "description": "Your live run UUID from pipeline_start_run, when writing under an execution claim. Pass together with claim_token; a stale or replaced claim is refused (409) — re-acquire, never retry blindly."
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": "Current claim token from pipeline_acquire_claim for this opportunity."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_add_task",
      "title": "Pipeline add task",
      "domain": "crm",
      "scope": "write",
      "description": "Add a task (next step) to a CRM opportunity: \"send the quote\", \"follow up Friday\". Every open deal should have a next step — the earliest-due open task shows as the deal's next step on the board and in the digest. Most tasks are day-granular; set due_time only when a real clock time was stated (a callback \"at 10 AM\").",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…)"
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": "What needs doing, imperative, ≤200 chars — e.g. \"Send revised quote\""
        },
        {
          "name": "due_date",
          "type": "string",
          "required": false,
          "description": "YYYY-MM-DD (omit for undated)"
        },
        {
          "name": "due_time",
          "type": "string",
          "required": false,
          "description": "HH:MM 24h — only with due_date, and only when a specific time was stated"
        },
        {
          "name": "body",
          "type": "string",
          "required": false,
          "description": "Optional detail / context"
        },
        {
          "name": "assigned_to_user_id",
          "type": "string | null",
          "required": false,
          "description": "User id from pipeline_list_assignees; null or omitted creates an unassigned task"
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Stable key (e.g. \"sweep:<run_id>:<opportunity_id>:follow-up\") so a retried create after a timeout returns the first task (deduped: true) instead of a second one. Strongly recommended for agent-created tasks."
        },
        {
          "name": "run_id",
          "type": "string",
          "required": false,
          "description": "Your live run UUID from pipeline_start_run, when writing under an execution claim. Pass together with claim_token; a stale or replaced claim is refused (409) — re-acquire, never retry blindly."
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": "Current claim token from pipeline_acquire_claim for this opportunity."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        }
      ]
    },
    {
      "name": "pipeline_begin_source_page",
      "title": "Pipeline begin source page",
      "domain": "crm",
      "scope": "write",
      "description": "Persist a fetched page BEFORE processing its items. Requires an owned live run; workflow/mailbox derive from that run. Reuse page_key only with the same manifest. expected_revision/input_cursor must match the checkpoint (0/null initially). Keep the exact query/window while paging; next_cursor=null means the provider page is terminal. Max 100 unique source item IDs; never include credentials.",
      "params": [
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "page_key",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "expected_revision",
          "type": "integer",
          "required": true,
          "description": ""
        },
        {
          "name": "input_cursor",
          "type": "string | null",
          "required": true,
          "description": ""
        },
        {
          "name": "next_cursor",
          "type": "string | null",
          "required": true,
          "description": ""
        },
        {
          "name": "window",
          "type": "object",
          "required": true,
          "description": ""
        },
        {
          "name": "items",
          "type": "string[]",
          "required": true,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_create_handoff",
      "title": "Pipeline create handoff",
      "domain": "crm",
      "scope": "write",
      "description": "Persist an addressed question, review, or uncertain outcome before ending work. Reviews store the exact shared body, named reviewer, intended sender and recipient; this creates no provider draft and sends nothing. Reuse action_key for identical retries. Machine writes require an owned live run and a current claim when opportunity_id is supplied.",
      "params": [
        {
          "name": "action_key",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "kind",
          "type": "\"question\" | \"review\" | \"uncertain\"",
          "required": true,
          "description": ""
        },
        {
          "name": "title",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "body",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "assigned_to_user_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "sender_user_id",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "recipient_email",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "opportunity_id",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_create_opportunity",
      "title": "Pipeline create opportunity",
      "domain": "crm",
      "scope": "write",
      "description": "Create a CRM opportunity. Stage must be a key from pipeline_get_funnel (omit for the first open stage). value_cents is the deal value in cents. custom carries values for the account's defined custom fields (see settings.fields).",
      "params": [
        {
          "name": "company",
          "type": "string",
          "required": true,
          "description": "Company / customer name"
        },
        {
          "name": "title",
          "type": "string",
          "required": false,
          "description": "Deal title (default: \"<company> deal\")"
        },
        {
          "name": "stage",
          "type": "string",
          "required": false,
          "description": "Funnel stage key (default: first open stage)"
        },
        {
          "name": "value_cents",
          "type": "number",
          "required": false,
          "description": "Deal value in cents"
        },
        {
          "name": "website",
          "type": "string",
          "required": false,
          "description": "Company website URL"
        },
        {
          "name": "source",
          "type": "string",
          "required": false,
          "description": "Where the deal came from"
        },
        {
          "name": "notes",
          "type": "string",
          "required": false,
          "description": "Free-form notes"
        },
        {
          "name": "expected_close_date",
          "type": "string",
          "required": false,
          "description": "YYYY-MM-DD"
        },
        {
          "name": "custom",
          "type": "object",
          "required": false,
          "description": "Custom-field values keyed by the defs in settings.fields"
        },
        {
          "name": "funnel_key",
          "type": "string",
          "required": false,
          "description": "Which pipeline (accounts can run several funnels with different stage ladders, e.g. sales + recruiting). Omit for the default funnel. The stage (if given) must belong to THAT funnel."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_get_claim",
      "title": "Pipeline get claim",
      "domain": "crm",
      "scope": "read",
      "description": "Read the current opportunity execution holder without disclosing its token. A claim is separate from business ownership, holds and send authorization.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "Opportunity id in the selected account"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_get_funnel",
      "title": "Pipeline get funnel",
      "domain": "crm",
      "scope": "read",
      "description": "The account's CRM pipeline definition: ordered stages (key, label, open/won/lost kind, close probability, stale_after_days) plus module settings (auto_create_from_leads, lost_reasons, fields, digest, …) and a vertical preset (suggested stages/lost reasons/fields for the account's business type — a seed to offer, never auto-applied). Call this FIRST before creating or moving opportunities — stage keys are per-account vocabulary, not a fixed set.",
      "params": [
        {
          "name": "funnel_key",
          "type": "string",
          "required": false,
          "description": "Which pipeline (accounts can run several funnels with different stage ladders, e.g. sales + recruiting). Omit for the default funnel."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_get_handoff",
      "title": "Pipeline get handoff",
      "domain": "crm",
      "scope": "read",
      "description": "Read the current handoff and up to 100 immutable historical snapshots. Copy version for expected_version. Open /dashboard/pipeline?handoff=<id> for human review of the same stored content.",
      "params": [
        {
          "name": "handoff_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_get_opportunity",
      "title": "Pipeline get opportunity",
      "domain": "crm",
      "scope": "read",
      "description": "One CRM opportunity in full: fields, firmographic enrichment, contacts, tasks, and the activity timeline. Contacts and the company carry live on-site behavioral context — segments, interests/affinities (brand/topic/category), high-intent page signals, sessions, conversions, recency, and hotness — plus a rollup of other open deals at the same company domain. opportunity.lead_quality carries the suspected-spam verdict ({verdict, score, reasons}) or null for never scored; null is an absence of information, not a clean bill of health.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…)"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_get_source_page",
      "title": "Pipeline get source page",
      "domain": "crm",
      "scope": "read",
      "description": "Read the stored page manifest, outcomes, version, source and owner_active. An inactive owner permits a matching fresh run to resume the SAME page through pipeline_update_source_page. Reconcile previous effects instead of assuming expiry means no effect occurred.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_assignees",
      "title": "Pipeline list assignees",
      "domain": "crm",
      "scope": "read",
      "description": "List the selected CRM account’s members (user_id, name, email, role). Resolve task owners from this roster; never guess user IDs or infer a human from a machine actor.",
      "params": [
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_deleted_opportunities",
      "title": "Pipeline list deleted opportunities",
      "domain": "crm",
      "scope": "read",
      "description": "Deals in the CRM trash: soft-deleted, still restorable, newest first — with who deleted each one and the date its restore window closes. Deleted deals are invisible to every other pipeline tool, so this is the only way to see them. Use it when someone says a deal has gone missing, and before telling anyone a deal does not exist. Read-only.",
      "params": [
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_handoffs",
      "title": "Pipeline list handoffs",
      "domain": "crm",
      "scope": "read",
      "description": "Read shared questions, exact review snapshots, and uncertain outcomes. Follow next_cursor; pending excludes resolved/sent. Machine identity does not imply a human: use the verified roster ID to filter. Approval is separate from delivery.",
      "params": [
        {
          "name": "next_user_id",
          "type": "string",
          "required": false,
          "description": "Whose next action: reviewer while open, intended sender after approval"
        },
        {
          "name": "status",
          "type": "\"pending\" | \"all\"",
          "required": false,
          "description": ""
        },
        {
          "name": "assigned_to_user_id",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_opportunities",
      "title": "Pipeline list opportunities",
      "domain": "crm",
      "scope": "read",
      "description": "List the account's CRM opportunities (max 500, newest first) with stage, value, primary contact, hot-lead flag — plus the funnel stages for grouping into a board view. Each deal carries lead_quality: {verdict: ok|suspect|spam, score, reasons} when its originating website lead was scored, or null when it was NEVER SCORED (manual entry, agent-created, CSV import). null does NOT mean clean — never describe an unscored deal as verified or spam-free. A suspect/spam deal is excluded from the hot-lead flag; treat it as unqualified until a human releases it from the review queue.",
      "params": [
        {
          "name": "lead_quality",
          "type": "\"all\" | \"flagged\" | \"unflagged\"",
          "required": false,
          "description": "Filter by lead quality: \"flagged\" for only suspected-spam deals, \"unflagged\" for everything else (which includes never-scored deals), \"all\" (default) for everything."
        },
        {
          "name": "funnel_key",
          "type": "string",
          "required": false,
          "description": "Which pipeline (accounts can run several funnels with different stage ladders, e.g. sales + recruiting). Omit for the default funnel."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_prospects",
      "title": "Pipeline list prospects",
      "domain": "crm",
      "scope": "read",
      "description": "Shadow opportunities (\"Companies on your site\"): corporate-email domain clusters of identified visitors whose behavior crossed the intent threshold, with evidence (people, sessions this week, top pages). These are deal CANDIDATES AXO observed — not yet in the pipeline. To ratify one, show it to the user and create an opportunity with pipeline_create_opportunity (company from the domain, contacts from the people, source \"axo_prospect\"). Domains already on open/won deals and dismissed domains are excluded.",
      "params": [
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_runs",
      "title": "Pipeline list runs",
      "domain": "crm",
      "scope": "read",
      "description": "List durable CRM runs in the selected account. Follow next_cursor with the same since filter. active=false distinguishes expired/finished runs; run summaries are not source checkpoints.",
      "params": [
        {
          "name": "since",
          "type": "string",
          "required": false,
          "description": "ISO timestamp: runs started at or after this time"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "next_cursor from the prior page"
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": "Page size 1..100; default 50"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_source_checkpoints",
      "title": "Pipeline list source checkpoints",
      "domain": "crm",
      "scope": "read",
      "description": "Read durable source coverage and pending fetched pages by workflow/mailbox. Follow next_cursor. Coverage is agent-reported over an explicit provider query/window with durable CRM evidence, not independent proof that every provider item was fetched. Resume a pending_page before fetching beyond it.",
      "params": [
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_list_tasks",
      "title": "Pipeline list tasks",
      "domain": "crm",
      "scope": "read",
      "description": "List CRM tasks (next steps) across the account's opportunities, earliest-due first. Default: open tasks only. Use due_before with today's date to find overdue tasks, or opportunity_id to see one deal's list. An open deal with no open task has \"no next step\" — worth flagging to the user.",
      "params": [
        {
          "name": "status",
          "type": "string",
          "required": false,
          "description": "open|done|skipped|all (default open)"
        },
        {
          "name": "opportunity_id",
          "type": "string",
          "required": false,
          "description": "Narrow to one opportunity (opp_…)"
        },
        {
          "name": "assignee",
          "type": "string",
          "required": false,
          "description": "User id from pipeline_list_assignees, or unassigned; omit for everyone. Agent callers must use a user id, not me."
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "next_cursor from the previous task page; keep the same filters"
        },
        {
          "name": "due_before",
          "type": "string",
          "required": false,
          "description": "YYYY-MM-DD — only tasks due strictly before this date (overdue scan)"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        }
      ]
    },
    {
      "name": "pipeline_log_activity",
      "title": "Pipeline log activity",
      "domain": "crm",
      "scope": "write",
      "description": "Log a touchpoint (email, call, or meeting) on a CRM opportunity's timeline. Use external_ref / external_refs for idempotency — re-logging any known ref returns the existing row instead of duplicating, so sweep agents can re-run safely and two mailboxes sweeping the same email land on one row. For plain observations use pipeline_add_note; for inferred stage moves use pipeline_suggest_stage_move.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…)"
        },
        {
          "name": "kind",
          "type": "string",
          "required": true,
          "description": "email|call|meeting"
        },
        {
          "name": "body",
          "type": "string",
          "required": true,
          "description": "What happened — summary shown on the timeline (max 10k chars)"
        },
        {
          "name": "occurred_at",
          "type": "string",
          "required": false,
          "description": "When it happened, ISO 8601 (defaults to now; stored in metadata)"
        },
        {
          "name": "external_ref",
          "type": "string",
          "required": false,
          "description": "Primary idempotency ref, e.g. \"gmail:189ab…\" — strongly recommended for swept sources"
        },
        {
          "name": "external_refs",
          "type": "string[]",
          "required": false,
          "description": "Every other stable ref you know for the SAME touchpoint (≤10): \"msgid:<RFC 5322 Message-ID without angle brackets>\" for email (cross-mailbox dedupe), \"gmail:<mailbox>:<id>\" when a message has no Message-ID, \"gcal:<event-id>\", \"notetaker:<id>\". Refs already known on the existing row are aliased onto it."
        },
        {
          "name": "run_id",
          "type": "string",
          "required": false,
          "description": "Your live run UUID from pipeline_start_run, when writing under an execution claim. Pass together with claim_token; a stale or replaced claim is refused (409) — re-acquire, never retry blindly."
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": "Current claim token from pipeline_acquire_claim for this opportunity."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_propose_funnel",
      "title": "Pipeline propose funnel",
      "domain": "crm",
      "scope": "write",
      "description": "Turn a plain-language description of how the business sells into a validated pipeline proposal: stages with probabilities, settings, and a rationale. READ-ONLY — returns the proposal plus the current stages for diffing; nothing is saved. Apply with pipeline_set_funnel after the user approves. Uses an AI call (allowance-metered).",
      "params": [
        {
          "name": "description",
          "type": "string",
          "required": true,
          "description": "The user's plain-language description of their sales process (10-4000 chars)"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_release_claim",
      "title": "Pipeline release claim",
      "domain": "crm",
      "scope": "write",
      "description": "Expire an owned current execution claim without deleting its audit history. Requires the current run/token; stale releases conflict. This does not release a business hold.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "Opportunity id in the selected account"
        },
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": "Owned live run UUID"
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": true,
          "description": "Opaque current token from pipeline_acquire_claim"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_renew_claim",
      "title": "Pipeline renew claim",
      "domain": "crm",
      "scope": "write",
      "description": "Renew a current unexpired execution claim using its run and opaque token. A stale or expired token conflicts; never drop the token to force a write.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "Opportunity id in the selected account"
        },
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": "Owned live run UUID"
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": true,
          "description": "Opaque current token from pipeline_acquire_claim"
        },
        {
          "name": "lease_seconds",
          "type": "integer",
          "required": false,
          "description": "30..300 seconds; default 120, bounded by run expiry"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_restore_opportunity",
      "title": "Pipeline restore opportunity",
      "domain": "crm",
      "scope": "write",
      "description": "Bring a deleted CRM opportunity back, with its contacts, tasks and full timeline. Ids come from pipeline_list_deleted_opportunities. If the deal's original stage no longer exists in the funnel it lands in the first open stage instead and the response says so in rehomed_to — say that out loud rather than leaving the user to find the card somewhere unexpected. Fails once the restore window has closed, at which point the deal is genuinely gone.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…), from pipeline_list_deleted_opportunities"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_set_funnel",
      "title": "Pipeline set funnel",
      "domain": "crm",
      "scope": "write",
      "description": "Save the account's CRM pipeline: stages replace wholesale, settings merge per key (a single knob never wipes the others). Stage validation: 3-12 stages, snake_case keys, exactly one won + one lost stage, probabilities 0-100, optional stale_after_days (1-365, open stages) for the stale-deal nudge; a stage currently holding opportunities cannot be removed (move them first). Omit stages to change settings alone.",
      "params": [
        {
          "name": "stages",
          "type": "object[]",
          "required": false,
          "description": "The complete ordered stage list — replaces the existing funnel wholesale; omit to keep the current ladder and only touch settings"
        },
        {
          "name": "auto_create_from_leads",
          "type": "boolean",
          "required": false,
          "description": "When true, captured website leads auto-create/attach opportunities. Omit to leave unchanged."
        },
        {
          "name": "lost_reasons",
          "type": "string[]",
          "required": false,
          "description": "Lost-reason vocabulary (max 12, each ≤40 chars) — powers the picker and the \"why we lose\" grouping; deals can still record any free-text reason. Omit to leave unchanged."
        },
        {
          "name": "fields",
          "type": "object[]",
          "required": false,
          "description": "Custom-field definitions for opportunities (max 10); money = integer cents; options required for select. Omit to leave unchanged."
        },
        {
          "name": "funnel_key",
          "type": "string",
          "required": false,
          "description": "Which pipeline (accounts can run several funnels with different stage ladders, e.g. sales + recruiting). Omit for the default funnel. Passing a NEW slug (lowercase letters, digits, - or _) together with stages CREATES that funnel — how a second pipeline (e.g. \"recruiting\") is born."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_start_run",
      "title": "Pipeline start run",
      "domain": "crm",
      "scope": "write",
      "description": "Start a durable CRM run before work. Reuse run_key only for identical retries. Authenticated principal is recorded separately from the optional represented human. This does not authorize external actions.",
      "params": [
        {
          "name": "run_key",
          "type": "string",
          "required": true,
          "description": "UUID idempotency key generated once for this run"
        },
        {
          "name": "agent_instance",
          "type": "string",
          "required": true,
          "description": "Distinct identity for this agent instance"
        },
        {
          "name": "workflow",
          "type": "string",
          "required": true,
          "description": "Workflow name, e.g. inbox-sync"
        },
        {
          "name": "source_account",
          "type": "string",
          "required": true,
          "description": "Verified mailbox/source account identity, never a secret"
        },
        {
          "name": "protocol_version",
          "type": "string",
          "required": true,
          "description": "Must be axo-crm-coordination/1"
        },
        {
          "name": "represented_user_id",
          "type": "string | null",
          "required": false,
          "description": "Optional account member from pipeline_list_assignees; metadata, not authority"
        },
        {
          "name": "parent_run_id",
          "type": "string | null",
          "required": false,
          "description": "Optional prior run UUID in this account"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_suggest_stage_move",
      "title": "Pipeline suggest stage move",
      "domain": "crm",
      "scope": "write",
      "description": "Propose (not perform) a stage move on a CRM opportunity: appends a suggestion the user accepts or dismisses in the CRM. USE THIS whenever you INFER a deal progressed — pipeline_update_opportunity is only for user-directed moves.",
      "params": [
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…)"
        },
        {
          "name": "to_stage",
          "type": "string",
          "required": true,
          "description": "The proposed funnel stage key"
        },
        {
          "name": "reason",
          "type": "string",
          "required": true,
          "description": "The evidence, 1-3 sentences — shown verbatim on the timeline"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_update_handoff",
      "title": "Pipeline update handoff",
      "domain": "crm",
      "scope": "write",
      "description": "Revise your handoff (resets approval), acknowledge/resolve work addressed to your run’s represented member, or record provider-observed sent evidence for the intended sender. Reuse action_key for identical retries; stale expected_version conflicts. record_sent requires the approved version and does not send mail or independently verify evidence. Only the named human reviewer can approve in their signed-in app session.",
      "params": [
        {
          "name": "handoff_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "action_key",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "expected_version",
          "type": "integer",
          "required": true,
          "description": ""
        },
        {
          "name": "action",
          "type": "\"revise\" | \"acknowledge\" | \"resolve\" | \"record_sent\"",
          "required": true,
          "description": ""
        },
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "title",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "body",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "assigned_to_user_id",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "sender_user_id",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "recipient_email",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "resolution",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "evidence_ref",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "evidence_source",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_update_opportunity",
      "title": "Pipeline update opportunity",
      "domain": "crm",
      "scope": "write",
      "description": "Update a CRM opportunity — move stage (auto-logs a stage_change and fires the behavioral rail), change value, title, close date, custom fields, or record a lost_reason. Only pass changed fields. IMPORTANT: only move stages when the user asked for the move or approved a suggestion; when you merely INFER progress, use pipeline_suggest_stage_move instead. When moving a deal to the lost stage, ask for (or infer from the user's words) a short lost_reason.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Copy revision unchanged from the latest resource read. On conflict reload and reconsider the edit; never remove the guard to force it."
        },
        {
          "name": "opportunity_id",
          "type": "string",
          "required": true,
          "description": "The opportunity id (opp_…)"
        },
        {
          "name": "stage",
          "type": "string",
          "required": false,
          "description": "New funnel stage key"
        },
        {
          "name": "company",
          "type": "string",
          "required": false,
          "description": "Company name"
        },
        {
          "name": "title",
          "type": "string",
          "required": false,
          "description": "Deal title"
        },
        {
          "name": "value_cents",
          "type": "number",
          "required": false,
          "description": "Deal value in cents"
        },
        {
          "name": "website",
          "type": "string",
          "required": false,
          "description": "Company website URL"
        },
        {
          "name": "source",
          "type": "string",
          "required": false,
          "description": "Deal source"
        },
        {
          "name": "notes",
          "type": "string",
          "required": false,
          "description": "Free-form notes (replaces existing)"
        },
        {
          "name": "expected_close_date",
          "type": "string",
          "required": false,
          "description": "YYYY-MM-DD"
        },
        {
          "name": "lost_reason",
          "type": "string",
          "required": false,
          "description": "Why the deal was lost, ≤80 chars — free text; prefer one of settings.lost_reasons when it fits. Only settable on a deal in (or moving to) the lost stage; cleared on reopen."
        },
        {
          "name": "custom",
          "type": "object",
          "required": false,
          "description": "Custom-field values to merge (null deletes a key), keyed by the defs in settings.fields"
        },
        {
          "name": "enrichment_override",
          "type": "object",
          "required": false,
          "description": "Corrections to the AXO-observed company facts, merged per key: industry, employee_range (a headcount band; \"1-10\", \"11-50\", \"51-200\", \"201-1000\", \"1000+\" are the usual ones but any short band is accepted, since the firmographic dataset uses its own), hq, founded_year (four-digit number), linkedin_url (a linkedin.com/company/… URL), tech_hints (1–3 strings), description (≤200 chars). A key set here overrides AXO and pins that field; setting a key to null removes the correction and hands that field back to automatic enrichment. Company name and website are NOT here — use the `company` and `website` arguments."
        },
        {
          "name": "next_touch_user_id",
          "type": "string | null",
          "required": false,
          "description": "Who owns the NEXT outbound touch (user id from pipeline_list_assignees), or null to clear. Independent of the deal owner. Read it before drafting: if it names someone else, do not draft for them."
        },
        {
          "name": "hold_reason",
          "type": "string | null",
          "required": false,
          "description": "Business hold on the deal (\"do not send until Thursday's call\"); null clears the hold. Read before any outbound work; an expired hold is still shown as expired for a human to lift, never assumed lifted."
        },
        {
          "name": "hold_expires_at",
          "type": "string | null",
          "required": false,
          "description": "ISO 8601 instant the hold lapses; null = until cleared. Needs a hold_reason."
        },
        {
          "name": "run_id",
          "type": "string",
          "required": false,
          "description": "Your live run UUID from pipeline_start_run, when writing under an execution claim. Pass together with claim_token; a stale or replaced claim is refused (409) — re-acquire, never retry blindly."
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": "Current claim token from pipeline_acquire_claim for this opportunity."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_update_run",
      "title": "Pipeline update run",
      "domain": "crm",
      "scope": "write",
      "description": "Heartbeat an owned live run or finish it as completed/partial/failed/abandoned with a summary. Finish atomically expires its claims. Expired runs may only be abandoned; never infer an external action from a run status.",
      "params": [
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": "UUID returned by pipeline_start_run"
        },
        {
          "name": "action",
          "type": "string",
          "required": true,
          "description": "heartbeat or finish"
        },
        {
          "name": "status",
          "type": "string",
          "required": false,
          "description": "For finish only: completed, partial, failed, or abandoned"
        },
        {
          "name": "summary",
          "type": "string",
          "required": false,
          "description": "For finish only: observed outcome and pending work (required, may be empty)"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_update_source_page",
      "title": "Pipeline update source page",
      "domain": "crm",
      "scope": "write",
      "description": "Save the complete current outcomes snapshot with advance=false, or advance the cursor atomically with advance=true only after EVERY manifest item has durable account-scoped effects or an explicit no-action reason. Uncertain/missing outcomes cannot advance. Reuse action_key and payload after response loss. A matching new live run can take over only after the prior owner is inactive. This does not make business effects idempotent or perform provider actions.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "run_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "action_key",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "expected_version",
          "type": "integer",
          "required": true,
          "description": ""
        },
        {
          "name": "advance",
          "type": "boolean",
          "required": true,
          "description": ""
        },
        {
          "name": "outcomes",
          "type": "object[]",
          "required": true,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "pipeline_update_task",
      "title": "Pipeline update task",
      "domain": "crm",
      "scope": "write",
      "description": "Update a CRM task: complete it (status done), skip it, reopen it, reschedule (due_date, optionally due_time), or edit the title/body. Completing or skipping logs a system activity on the deal's timeline.",
      "params": [
        {
          "name": "task_id",
          "type": "string",
          "required": true,
          "description": "The task id (task_…)"
        },
        {
          "name": "assigned_to_user_id",
          "type": "string | null",
          "required": false,
          "description": "User id from pipeline_list_assignees; null clears assignment; omit to preserve it"
        },
        {
          "name": "expected_updated_at",
          "type": "string",
          "required": false,
          "description": "Copy updated_at exactly from the last task read, including all six fractional digits. On conflict reload and reconsider the edit; never retry stale state blindly."
        },
        {
          "name": "status",
          "type": "string",
          "required": false,
          "description": "open|done|skipped"
        },
        {
          "name": "title",
          "type": "string",
          "required": false,
          "description": "New title"
        },
        {
          "name": "body",
          "type": "string",
          "required": false,
          "description": "New detail (empty string clears)"
        },
        {
          "name": "due_date",
          "type": "string",
          "required": false,
          "description": "New due date YYYY-MM-DD (empty string clears)"
        },
        {
          "name": "due_time",
          "type": "string",
          "required": false,
          "description": "New due time HH:MM 24h (empty string clears; needs a due date; clearing the date clears the time)"
        },
        {
          "name": "run_id",
          "type": "string",
          "required": false,
          "description": "Your live run UUID from pipeline_start_run, when writing under an execution claim. Pass together with claim_token; a stale or replaced claim is refused (409) — re-acquire, never retry blindly."
        },
        {
          "name": "claim_token",
          "type": "string",
          "required": false,
          "description": "Current claim token from pipeline_acquire_claim for this opportunity."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required on cross-site super-admin sessions: which SITE names the target account (the pipeline is account-scoped; the account is resolved from the site). Discover ids via get_account_status. Ignored on site-scoped sessions, which always work their own account."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "place_lead_form",
      "title": "Place lead form",
      "domain": "personalization",
      "scope": "write",
      "description": "Deploy a lead-capture form onto a page: creates a lead_form content variant bound to a zone (anchor) + audience. The form INSERTS relative to the anchor by `position` (default \"after\" — slots between sections without taking over). Target ONE segment via `segment_id`, OR set `all_visitors: true` to show the form to EVERYONE regardless of segment (provide exactly one). Get a payload from list_forms (copy-on-place) or pass one directly. To anchor somewhere that is not yet a zone, use AXO skills with browser access to inspect the page and bind the anchor with create_zone first.",
      "params": [
        {
          "name": "zone_id",
          "type": "string",
          "required": true,
          "description": "Anchor zone to place the form relative to"
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": "Audience segment to show it to. Omit when all_visitors is true."
        },
        {
          "name": "all_visitors",
          "type": "boolean",
          "required": false,
          "description": "Show the form to ALL visitors regardless of segment. When true, omit segment_id."
        },
        {
          "name": "payload",
          "type": "object",
          "required": true,
          "description": "Form design payload (e.g. from a template via list_forms). Optional form_id string is sent with each capture and drives CRM funnel routing (form_routing)."
        },
        {
          "name": "position",
          "type": "\"after\" | \"before\" | \"inside_start\" | \"inside_end\" | \"replace\"",
          "required": false,
          "description": "Where the form inserts relative to the anchor element. Default \"after\". \"replace\" takes over the anchor (legacy / dedicated containers)."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "place_offer",
      "title": "Place offer",
      "domain": "personalization",
      "scope": "write",
      "description": "Deploy an offer into a zone: creates a type=\"offer\" content variant bound to a zone + audience. Target ONE segment via `segment_id`, OR set `all_visitors: true` to show it to EVERYONE regardless of segment (provide exactly one). The placement stores ONLY the offer_id plus presentation overrides — the economics (value, code, window, terms) are joined in from the offer row on every config build, which is what makes an offer ending at midnight go dark everywhere instead of leaving a stale code on the page. The renderer writes into the anchor zone's element, so pick a zone whose contents the offer should own (create_zone first if none exists). Two DIFFERENT offers cannot compete in one zone + audience and the write is rejected if you try: the optimizer ranks on conversion rate, so given a choice of depths it always crowns the deepest one and reports the lost margin as a win — competing arms must share one offer and vary only presentation. Placing an unapproved or non-live offer succeeds but serves nothing until a human ratifies it in the dashboard; check status/approved via list_offers first.",
      "params": [
        {
          "name": "offer_id",
          "type": "string",
          "required": true,
          "description": "Offer to serve (from list_offers). Must belong to this site and not be deleted."
        },
        {
          "name": "zone_id",
          "type": "string",
          "required": true,
          "description": "Zone whose element the offer renders into"
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": "Audience segment to show it to. Omit when all_visitors is true."
        },
        {
          "name": "all_visitors",
          "type": "boolean",
          "required": false,
          "description": "Show the offer to ALL visitors regardless of segment. When true, omit segment_id."
        },
        {
          "name": "presentation",
          "type": "object",
          "required": false,
          "description": "Per-placement presentation overrides — { headline, body, cta, cta_href, show_code, show_expiry }. These win over the offer's own presentation defaults. Never put economics here: value and code come from the offer row."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "preview_component_revision",
      "title": "Preview component revision",
      "domain": "personalization",
      "scope": "read",
      "description": "Compile an isolated component preview, optionally with placement-specific field values. Returns HTML and hash without saving or publishing; use the Content page picker for host-page anchor validation. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "field_overrides",
          "type": "object",
          "required": false,
          "description": "Optional exposed field values for this preview"
        },
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "revision_id",
          "type": "string",
          "required": true,
          "description": "revision id"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        }
      ]
    },
    {
      "name": "preview_custom_segment",
      "title": "Preview custom segment",
      "domain": "audience",
      "scope": "read",
      "description": "Preview a custom audience definition before saving. Returns counts and diagnostic hints; identifiers are never requested. No membership is changed.",
      "params": [
        {
          "name": "definition",
          "type": "object",
          "required": true,
          "description": "Custom audience definition, e.g. {predicate:{any_of:[{kind:\"event_occurred\",event_type:\"pageview\"}]}}"
        },
        {
          "name": "sample_limit",
          "type": "integer",
          "required": false,
          "description": ""
        }
      ]
    },
    {
      "name": "publish_content_variants",
      "title": "Publish content variants",
      "domain": "personalization",
      "scope": "write",
      "description": "PUBLISH the working copy of one or more content variants so visitors see it: snapshots each into an immutable revision and moves its published pointer. All ids must be live variants of this site or nothing is published. On an auto-publishing site (the default) visitors already see the working copy, so this only records a restore point. Read GET /api/content first: has_unpublished_changes tells you which variants have pending edits on a manual site. When content approval is required, publishing unapproved changes returns approval_required; call request_review first to submit for approval.",
      "params": [
        {
          "name": "variant_ids",
          "type": "string[]",
          "required": true,
          "description": "Variant ids to publish together (max 200)."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "publish_page",
      "title": "Publish page",
      "domain": "content",
      "scope": "write",
      "description": "PUBLISH a page – the one action that makes it public and indexable, at /p/<site>/<slug> (axo_hosted), or at https://<custom-domain>/<slug> (custom_subdomain, needs a live page domain). When content approval is required, publishing returns approval_required; call request_review first.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page ID"
        },
        {
          "name": "revision_id",
          "type": "string",
          "required": false,
          "description": "Exact revision reviewed from get_page; required for private pages. A newer revision is rejected."
        },
        {
          "name": "adapter",
          "type": "\"axo_hosted\" | \"custom_subdomain\"",
          "required": false,
          "description": "Delivery adapter. custom_subdomain serves at https://<your-domain>/<slug> and requires a live page domain (add_page_domain)."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "reclassify_catalog_item",
      "title": "Reclassify catalog item",
      "domain": "catalog",
      "scope": "write",
      "description": "Change a catalog product/item details (category, name, brand, description, sku, price) or mark it as ignored (which hides it from the personalization catalog and excludes it from variant recommendation). Sets is_override to true.",
      "params": [
        {
          "name": "item_id",
          "type": "string",
          "required": true,
          "description": "Catalog product UUID"
        },
        {
          "name": "category",
          "type": "string",
          "required": false,
          "description": "New category name (optional)"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "New product name (optional)"
        },
        {
          "name": "brand",
          "type": "string",
          "required": false,
          "description": "New brand name (optional)"
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": "New product description (optional)"
        },
        {
          "name": "sku",
          "type": "string",
          "required": false,
          "description": "New SKU (optional)"
        },
        {
          "name": "price",
          "type": "string",
          "required": false,
          "description": "New price (optional)"
        },
        {
          "name": "ignored",
          "type": "boolean",
          "required": false,
          "description": "Set to true to ignore the product, false to restore (optional)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "reclassify_page",
      "title": "Reclassify page",
      "domain": "catalog",
      "scope": "write",
      "description": "Curate one content-map page (the page_profiles counterpart of reclassify_catalog_item): override its page type or section (null restores the crawl/derived value), or set ignored to hide it from the content map and reco candidates.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page profile ID (from get_content_map's section drill-down)"
        },
        {
          "name": "page_type_override",
          "type": "string | null",
          "required": false,
          "description": "New page type, e.g. pricing, docs, service, article (optional; null clears the override)"
        },
        {
          "name": "section_override",
          "type": "string | null",
          "required": false,
          "description": "New section name (optional; null clears the override)"
        },
        {
          "name": "ignored",
          "type": "boolean",
          "required": false,
          "description": "true to hide the page from the content map, false to restore (optional)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "recompute_custom_segment",
      "title": "Recompute custom segment",
      "domain": "audience",
      "scope": "write",
      "description": "Recompute a saved custom audience using its current definition and site window. Refuses externally managed audiences without a predicate.",
      "params": [
        {
          "name": "segment_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "register_dam_asset",
      "title": "Register DAM asset",
      "domain": "assets",
      "scope": "write",
      "description": "Create an AXO thin asset record from a brand-DAM asset (e.g. one returned by search_dam). Returns { id, url, reused } — the id is the asset_<uuid> you pass to attach_asset_to_variant. Dedups by (site_id, dam_provider, dam_asset_id) so picking the same asset twice reuses the existing row. No bytes pass through AXO; the asset stays in the customer's DAM. Gated on experimental_byo_dam_enabled + an active connection for the named provider.",
      "params": [
        {
          "name": "provider",
          "type": "string",
          "required": true,
          "description": "Vendor id from the search_dam hit (\"cloudinary\" | \"s3_compatible\")."
        },
        {
          "name": "dam_asset_id",
          "type": "string",
          "required": true,
          "description": "Vendor-side id from the hit (Cloudinary public_id, S3 key)."
        },
        {
          "name": "external_url",
          "type": "string",
          "required": true,
          "description": "Public URL from the hit (Cloudinary secure_url, S3 public URL)."
        },
        {
          "name": "mime_type",
          "type": "string",
          "required": false,
          "description": "Optional, defaults to image/png."
        },
        {
          "name": "width",
          "type": "number",
          "required": false,
          "description": "Optional pixel width."
        },
        {
          "name": "height",
          "type": "number",
          "required": false,
          "description": "Optional pixel height."
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "Optional display name for the AXO library. Defaults to the asset id."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "reject_ad_theme_suggestion",
      "title": "Reject ad theme suggestion",
      "domain": "personalization",
      "scope": "write",
      "description": "Reject a pending ad-theme suggestion for this site.",
      "params": [
        {
          "name": "suggestion_id",
          "type": "string",
          "required": true,
          "description": ""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "release_component_revision",
      "title": "Release component revision",
      "domain": "personalization",
      "scope": "write",
      "description": "Update only the listed placements to an approved revision. Returns individual success/conflict results; never assume a partial release was atomic. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "revision_id",
          "type": "string",
          "required": true,
          "description": "revision id"
        },
        {
          "name": "placements",
          "type": "object[]",
          "required": true,
          "description": "Array of {id,expected_version} for exactly the placements to update"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "remove_optin_form",
      "title": "Remove optin form",
      "domain": "audience",
      "scope": "write",
      "description": "Remove a native form's opt-in capture designation by its form_selector (see list_optin_forms). The tag stops reading that form on the next config refresh; already-captured leads are unaffected.",
      "params": [
        {
          "name": "form_selector",
          "type": "string",
          "required": true,
          "description": "form_selector of the designation to remove (exact match)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "remove_page_domain",
      "title": "Remove page domain",
      "domain": "site",
      "scope": "write",
      "description": "Remove the custom page domain: detaches it from serving (pages published to it stop resolving). The hostname stays reserved so it cannot be hijacked while the CNAME lingers.",
      "params": [
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "rename_component_placement",
      "title": "Rename component placement",
      "domain": "personalization",
      "scope": "write",
      "description": "Change only a placement's name, everywhere it is shown, without saving a new version or re-publishing. Content, targeting, activation and test assignment stay as they are, so it works on a live placement and on an arm of a running component test. Read expected_version first; a mismatch returns 409. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "display_name",
          "type": "string",
          "required": true,
          "description": "New placement name, up to 200 characters"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "rename_page",
      "title": "Rename page",
      "domain": "content",
      "scope": "write",
      "description": "Rename/move an UNPUBLISHED page (draft, preview, or approved) to a new slug/path — the recovery for a typo'd folder. May be a nested path like \"blog/spring/launch\" (max 5 segments). Refused once the page is published or retired: the slug is write-once after publish. 409 slug_taken means another page holds that path — pick a different one. Preview links survive a rename.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page ID"
        },
        {
          "name": "slug",
          "type": "string",
          "required": true,
          "description": "New slug/path, e.g. \"blog/spring/launch\""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "rename_section",
      "title": "Rename section",
      "domain": "catalog",
      "scope": "write",
      "description": "Rename a content-map section across the site's pages (writes a curation override; re-crawls never undo it). Merging two sections is the same operation: rename A to B and A's pages join B's bucket.",
      "params": [
        {
          "name": "old_section",
          "type": "string",
          "required": true,
          "description": "Current section name"
        },
        {
          "name": "new_section",
          "type": "string",
          "required": true,
          "description": "New section name (lowercased on save)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "request_review",
      "title": "Request review",
      "domain": "personalization",
      "scope": "write",
      "description": "Submit one or more items (variants, placements, pages, campaigns) for human review and approval. When approval is required by workspace policy, changes cannot go live directly and must be submitted through this tool first. Approvers are notified to review in the dashboard or via connected channels.",
      "params": [
        {
          "name": "items",
          "type": "object[]",
          "required": true,
          "description": "List of 1 to 20 items to submit for approval together."
        },
        {
          "name": "title",
          "type": "string",
          "required": false,
          "description": "Optional title for the review request."
        },
        {
          "name": "note",
          "type": "string",
          "required": false,
          "description": "Optional note explaining the changes for approvers."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "resolve_aeo_finding",
      "title": "Resolve AEO finding",
      "domain": "site",
      "scope": "write",
      "description": "Move a finding: acknowledge it, mark it resolved, mark it won't fix, or reopen it. Won't-fix is sticky - a re-observation will never reopen it, only an explicit reopen will.",
      "params": [
        {
          "name": "finding_id",
          "type": "string",
          "required": true,
          "description": "Finding ID"
        },
        {
          "name": "action",
          "type": "\"acknowledge\" | \"resolve\" | \"wont_fix\" | \"reopen\"",
          "required": true,
          "description": "What to do with it"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "restore_component_placement",
      "title": "Restore component placement",
      "domain": "personalization",
      "scope": "write",
      "description": "Restore a previous full placement binding as a new private draft. Preview and explicitly publish to change delivery. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "version",
          "type": "number",
          "required": true,
          "description": "version"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "expected version"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "restore_content_variant",
      "title": "Restore content variant",
      "domain": "personalization",
      "scope": "write",
      "description": "Restore a content variant. Two modes: (1) pass only variant_id to un-delete a soft-deleted variant; (2) also pass version to roll back the payload to a specific historical version (use get_content_history to find version numbers). Restoring to a version is itself snapshotted, so you can always roll forward again.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Variant ID to restore"
        },
        {
          "name": "version",
          "type": "integer",
          "required": false,
          "description": "History version to restore payload to. Omit to only un-delete."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "restore_content_variant_revision",
      "title": "Restore content variant revision",
      "domain": "personalization",
      "scope": "write",
      "description": "Copy a published revision back into the variant's WORKING COPY. The current state is snapshotted into the variant's history first. On an auto-publishing site the working copy is what visitors see, so it takes effect at once; on a manual site it becomes pending edits to preview and then publish. Component placements and routed variants are refused: restore those with restore_content_variant, which re-validates them.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Variant ID"
        },
        {
          "name": "revision_id",
          "type": "string",
          "required": true,
          "description": "Revision id from list_content_variant_revisions"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "retire_page",
      "title": "Retire page",
      "domain": "content",
      "scope": "write",
      "description": "Retire a page: unpublishes any live publication and ends its lifecycle. History is kept.",
      "params": [
        {
          "name": "page_id",
          "type": "string",
          "required": true,
          "description": "Page ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "revoke_preview_share_link",
      "title": "Revoke preview share link",
      "domain": "personalization",
      "scope": "write",
      "description": "Turn a client review link off immediately. The row is kept for the audit trail; anyone opening the link sees that it was turned off. Use list_preview_share_links to find codes.",
      "params": [
        {
          "name": "code",
          "type": "string",
          "required": true,
          "description": "The link code (the last path segment of its URL)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "rotate_mcp_token",
      "title": "Rotate mcp token",
      "domain": "site",
      "scope": "write",
      "description": "Rotate THIS session's own MCP token: mints a replacement secret carrying the exact same policy on every axis (scope, domains, expiry, deny_destructive, read_only, propose_only, allow_issuance), and the current secret stays valid for a short grace window (~5 minutes) before it expires, so in-flight work finishes cleanly. Returns the new plaintext ONCE — store it immediately and switch every subsequent call to it; it can never be re-fetched. Gated behind the allow_issuance grant (rotation mints a credential), refused for super_admin tokens (their lifecycle stays with a human in the dashboard), and unavailable to legacy api_key sessions, which have no delegated token to rotate — mint a scoped token from /dashboard/settings/mcp-tokens. A token cannot rotate again while inside its own grace window.",
      "params": [
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "run_aeo_audit",
      "title": "Run AEO audit",
      "domain": "site",
      "scope": "write",
      "description": "Start a read-only audit of this site's own public surface - robots.txt, llms.txt, sitemap and a sample of pages - and derive findings from it. Costs nothing: no answer engines are queried.",
      "params": [
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "run_ai_visibility_cycle",
      "title": "Run AI visibility cycle",
      "domain": "site",
      "scope": "write",
      "description": "Run visibility preflight, site audit and a paid question run. Existing module, budget and preflight checks apply. Returns accepted run status, not completed results; read get_ai_visibility_status for progress.",
      "params": [
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "save_component_revision",
      "title": "Save component revision",
      "domain": "content",
      "scope": "write",
      "description": "Save edited HTML/CSS as a sanitized immutable draft revision. To restore a previous revision, read its HTML with get_component and save those bytes here. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Stable key for retries of this logical mutation"
        },
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "html",
          "type": "string",
          "required": true,
          "description": "html"
        },
        {
          "name": "instruction",
          "type": "string",
          "required": false,
          "description": "instruction"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        }
      ]
    },
    {
      "name": "search_assets",
      "title": "Search assets",
      "domain": "assets",
      "scope": "read",
      "description": "Search the generative DAM asset library for previously-generated images. Filters: q (text match on name + prompt, ILIKE), subject (tags.subject exact), theme (tags.themes contains), provider (gemini|openai|firefly), include_archived (default false). Returns up to `limit` assets with their blob_url, name, prompt, provider/model, and auto-tagged subject/scene/themes/palette. Use this BEFORE generating new images — if the operator asks for \"a warm autumn kitchen background\" and one already exists in the library, attaching it saves $0.04 of generation cost and ~3 seconds of wall-clock. Requires experimental_generative_dam_enabled on site_config.product_catalog.",
      "params": [
        {
          "name": "q",
          "type": "string",
          "required": false,
          "description": "Text match against name + prompt (ILIKE)."
        },
        {
          "name": "subject",
          "type": "string",
          "required": false,
          "description": "Exact match on tags.subject (e.g. \"kitchen\")."
        },
        {
          "name": "theme",
          "type": "string",
          "required": false,
          "description": "Tag in tags.themes (e.g. \"minimalist\")."
        },
        {
          "name": "provider",
          "type": "\"gemini\" | \"openai\" | \"firefly\"",
          "required": false,
          "description": ""
        },
        {
          "name": "include_archived",
          "type": "boolean",
          "required": false,
          "description": ""
        },
        {
          "name": "limit",
          "type": "integer",
          "required": false,
          "description": "Default 20."
        }
      ]
    },
    {
      "name": "search_dam",
      "title": "Search DAM",
      "domain": "assets",
      "scope": "read",
      "description": "Search the customer's CONNECTED brand DAM (Cloudinary, S3-compatible) for assets — including ones the brand team uploaded directly without going through AXO. Use when the brand owns assets you want to BIND to a variant rather than generate fresh: the brand has approved imagery you should reuse. q matches filename + public_id + tag values (Cloudinary) or key-substring (S3); folder scopes to a folder prefix; tags filters to assets carrying ALL of the listed tags (Cloudinary only — S3 tags aren't indexed for search). Returns hits with asset_id (vendor public_id / S3 key), external_url, mime_type, tags, folder. THREE-STEP BIND FLOW: search_dam → register_dam_asset (creates AXO thin record + returns asset_id) → attach_asset_to_variant (binds to a variant field with provenance). Returns empty when no DAM is connected — search_assets handles the AXO-side library separately.",
      "params": [
        {
          "name": "q",
          "type": "string",
          "required": false,
          "description": "Free-text query — matched against filename, public_id, and tag values."
        },
        {
          "name": "folder",
          "type": "string",
          "required": false,
          "description": "Restrict to a folder/prefix (e.g. \"axo/franken-store-com\")."
        },
        {
          "name": "tags",
          "type": "string[]",
          "required": false,
          "description": "Restrict to assets carrying ALL of these tags (Cloudinary only — S3 ignores)."
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Default 30, max 100."
        },
        {
          "name": "provider",
          "type": "string",
          "required": false,
          "description": "Optional provider scope (\"cloudinary\" | \"s3_compatible\"). When omitted, uses the site's first active DAM connection."
        }
      ]
    },
    {
      "name": "search_profiles",
      "title": "Search profiles",
      "domain": "audience",
      "scope": "read",
      "description": "Search visitor profiles by email, segment, or identification status. Returns profile list with identifiers and session counts. Paginated: the response carries `next_cursor` — pass it back as `cursor` to continue, until it is null.",
      "params": [
        {
          "name": "search",
          "type": "string",
          "required": false,
          "description": "Search by email or identifier value"
        },
        {
          "name": "segment",
          "type": "string",
          "required": false,
          "description": "Filter by segment ID"
        },
        {
          "name": "identified",
          "type": "\"true\" | \"false\"",
          "required": false,
          "description": "Filter by identified status"
        },
        {
          "name": "limit",
          "type": "number",
          "required": false,
          "description": "Max results (default: 50, max: 200)"
        },
        {
          "name": "cursor",
          "type": "string",
          "required": false,
          "description": "Opaque pagination cursor. To fetch the next page, pass the `next_cursor` value returned by the previous call verbatim; omit for the first page. The response's `next_cursor` is null when there are no more rows. Never construct or modify a cursor yourself."
        }
      ]
    },
    {
      "name": "set_ad_campaign_budget",
      "title": "Set ad campaign budget",
      "domain": "integrations",
      "scope": "admin",
      "description": "SET the DAILY budget of a live campaign on the customer's connected Google Ads or Meta ad account. THIS CHANGES REAL AD SPEND IMMEDIATELY, in either direction: raising it authorizes the platform to spend more of the customer's money starting now; lowering it throttles a live campaign. The amount is micros of the ad account's billing currency (1 USD = 1,000,000 micros) — a unit mistake here is a money mistake, so restate the amount in plain currency to the operator before calling. Google campaigns on an explicitly-SHARED budget are refused (mutating one re-funds every campaign sharing it). Requires admin scope and is refused to destructive-denied tokens. The previous budget is not recorded — note it from the platform first if the operator may want to revert. Fails with a re-consent hint when the stored OAuth grant lacks the write permission.",
      "params": [
        {
          "name": "platform",
          "type": "string",
          "required": true,
          "description": "\"google_ads\" or \"meta_ads\""
        },
        {
          "name": "campaign_ref",
          "type": "string",
          "required": true,
          "description": "The provider's numeric campaign id, from get_ad_performance"
        },
        {
          "name": "daily_budget_micros",
          "type": "number",
          "required": true,
          "description": "New daily budget in MICROS of the account currency (1 USD = 1,000,000). Must be > 0."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "set_campaign_lifecycle",
      "title": "Set campaign lifecycle",
      "domain": "personalization",
      "scope": "write",
      "description": "Stage a campaign: planning -> ready -> approved (and back). `ready` and `approved` are REFUSED (409, with `details.readiness`) until the manifest is ready: an active goal, at least one required component, every required component ready/live. `approved` pins approved_revision to the latest brief version; a later brief edit voids it (the campaign drops back to ready). launched / paused / closed are refused here - use launch_campaign / pause_campaign / close_campaign. Never writes `status` (the serving gate).",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "lifecycle",
          "type": "\"planning\" | \"ready\" | \"approved\"",
          "required": true,
          "description": "Target stage"
        },
        {
          "name": "actor",
          "type": "string",
          "required": false,
          "description": "Who is approving (recorded as approved_by when lifecycle=approved)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "set_publishing_mode",
      "title": "Set publishing mode",
      "domain": "site",
      "scope": "admin",
      "description": "Switch how this site serves content variants. auto (default): visitors see the working copy as it autosaves. manual: visitors see only PUBLISHED revisions, so edits stay private until publish_content_variants. Entering manual first publishes every live variant, so nothing on screen changes at the switch. Admin-only: it changes how the whole site is served.",
      "params": [
        {
          "name": "mode",
          "type": "\"auto\" | \"manual\"",
          "required": true,
          "description": "Publishing mode"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "ship_layout_winner",
      "title": "Ship layout winner",
      "domain": "personalization",
      "scope": "write",
      "description": "End a layout experiment AND ship the winning layout: the winning fragment arm's variants STAY ACTIVE and serve to 100% of visitors through the ordinary variant path (the always-on holdout keeps measuring them), while every other arm's variants — the control included — are retired. IRREVERSIBLE as a test: the experiment is ended, can never be restarted, and its results freeze at ended_at; the ship is recorded in history (shipped: true) and the workspace is notified. winner_arm_key must name one of the test's FRAGMENT arms — shipping the control is meaningless (that is just end_layout_experiment: naming the control here ends the test WITHOUT shipping anything). Before shipping, read get_layout_experiment_results: do not ship while `collecting` is true, while a guardrail breach stands against the winner, or when the winner is not significant, unless the operator explicitly accepts that.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "Layout experiment id (lexp_…), from list_layout_experiments"
        },
        {
          "name": "winner_arm_key",
          "type": "string",
          "required": true,
          "description": "Fragment arm to ship (e.g. \"B\"). Must be one of the experiment's fragment arms."
        },
        {
          "name": "reason",
          "type": "string",
          "required": false,
          "description": "Why the winner is being shipped (recorded in history and stop_reason; max 80 chars, default \"manual\")"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "suggest_donation_amounts",
      "title": "Suggest donation amounts",
      "domain": "personalization",
      "scope": "read",
      "description": "Return the segment-aware donation ask ladder for a donor — the amounts a fundraising form should show first, plus the recurrence default + rationale. Pass `segment_suffix` for the default ladder per segment (e.g. \"donor_ready\", \"high_capacity_uncontacted\", \"lapsed_quarterly\"), OR pass `profile_id` to personalise off the donor's CRM-fed attributes (last_gift_amount_cents / recurring_amount_cents / lifetime_giving_cents / capacity_score). When `profile_id` is given without `segment_suffix`, the profile's current segment is used. Returns { ladder: { amounts_cents, recurrence, rationale, highlight_index?, highlight_label? }, resolved }. Amounts are in CENTS. Use before authoring a copy_swap variant that writes the amounts onto a donation form, or when the agent needs to suggest \"what should we ask for\" from the donor context.",
      "params": [
        {
          "name": "segment_suffix",
          "type": "string",
          "required": false,
          "description": "verticals.ts segment suffix (e.g. donor_ready, high_capacity_uncontacted, lapsed_quarterly)."
        },
        {
          "name": "profile_id",
          "type": "string",
          "required": false,
          "description": "Profile to personalise off — last gift, recurring amount, capacity. CRM attributes pulled from profiles.attributes."
        },
        {
          "name": "last_gift_amount_cents",
          "type": "integer",
          "required": false,
          "description": "Override (cents). Useful when profile data is stale or unavailable."
        },
        {
          "name": "recurring_amount_cents",
          "type": "integer",
          "required": false,
          "description": "Override (cents). For recurring_card_declined the ladder surfaces ONLY this amount."
        },
        {
          "name": "lifetime_giving_cents",
          "type": "integer",
          "required": false,
          "description": "Override (cents). Elevates the high-capacity ladder when ≥ $10k."
        },
        {
          "name": "capacity_score",
          "type": "number",
          "required": false,
          "description": "Override (0..1). Elevates the high-capacity ladder when ≥ 0.9."
        }
      ]
    },
    {
      "name": "suggest_placement_goals",
      "title": "Suggest placement goals",
      "domain": "personalization",
      "scope": "read",
      "description": "Read-only. Suggested \"Built for goal\" values for this site's placements (content zones and component placements) that have no goal set. Rules: an email or lead-form placement, or a sign-up call to action (zone key or name says signup, email, newsletter or enews, or the zone holds a lead form), maps to the site's matching form, email or newsletter goal; on an affiliate site, a placement whose live content shows product picks, a comparison or a product list (a product recommendation, a list of two or more products or links, or a picks or comparison name), or holds affiliate links or two or more links to other sites, maps to the affiliate click goal; headline, hero and copy zones never qualify on their name or the site vertical; anything else gets no suggestion. Only live placements are considered: drafts, paused and unapproved zones get none. Returns { suggestions: [{ zone_id, placement_id, kind (\"zone\" | \"component_placement\"), name, page_pattern, goal {id, name, kind}, rule (\"signup\" | \"affiliate\"), reason }], goals, affiliate_site, can_accept }. Nothing is written: show the suggestions to the owner and, only after they agree, accept each with update_zone {zone_id, goal_id} (that works for component placement zones too). can_accept false means goals cannot be saved on this database yet.",
      "params": []
    },
    {
      "name": "sync_crm",
      "title": "Sync CRM",
      "domain": "integrations",
      "scope": "write",
      "description": "Push CRM records to enrich visitor profiles. Matches by email, phone, or external_id. Attributes (ltv, plan, customer_tier, etc.) are merged into the matched profile.",
      "params": [
        {
          "name": "records",
          "type": "object[]",
          "required": true,
          "description": "Array of CRM records to sync (max 1000)"
        },
        {
          "name": "source",
          "type": "string",
          "required": false,
          "description": "Source label (e.g. \"salesforce\", \"hubspot\")"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "test_fire_trigger",
      "title": "Test fire trigger",
      "domain": "personalization",
      "scope": "write",
      "description": "Fire a trigger against a test target, bypassing cooldown/rate limits. Goes through the real dispatch pipeline — WILL send real email/SMS. A lifecycle trigger (installed from a recipe) takes only identifier {type: email, phone or external_id, value} and is a dry run that returns the exact payload for each destination; pass dry_run: false to send it. Test fires stay out of lift.",
      "params": [
        {
          "name": "trigger_id",
          "type": "string",
          "required": true,
          "description": "Trigger ID"
        },
        {
          "name": "profile_id",
          "type": "string",
          "required": false,
          "description": "Profile ID"
        },
        {
          "name": "identifier",
          "type": "object",
          "required": false,
          "description": "Test target identifier"
        },
        {
          "name": "event_properties",
          "type": "object",
          "required": false,
          "description": "Override event properties"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Lifecycle triggers only. Default true: return the payloads and send nothing. false sends to the test identifier."
        },
        {
          "name": "integration_id",
          "type": "string",
          "required": false,
          "description": "Lifecycle triggers only: test one destination of the trigger"
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "test_integration",
      "title": "Test integration",
      "domain": "integrations",
      "scope": "write",
      "description": "Send a test payload to an integration to verify the connection works.",
      "params": [
        {
          "name": "integration_id",
          "type": "string",
          "required": true,
          "description": "Integration ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "trigger_salesforce_sync",
      "title": "Trigger salesforce sync",
      "domain": "crm",
      "scope": "write",
      "description": "Trigger a Salesforce sync. Direction can be outbound (push AXO→SF), inbound (pull SF→AXO), or bidirectional.",
      "params": [
        {
          "name": "direction",
          "type": "\"outbound\" | \"inbound\" | \"bidirectional\"",
          "required": false,
          "description": "Sync direction (default: uses configured direction)"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "unlink_identifier",
      "title": "Unlink identifier",
      "domain": "audience",
      "scope": "write",
      "description": "Remove an identifier from a profile.",
      "params": [
        {
          "name": "profile_id",
          "type": "string",
          "required": true,
          "description": "Profile ID"
        },
        {
          "name": "identifier_id",
          "type": "string",
          "required": true,
          "description": "Identifier record ID"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_asset",
      "title": "Update asset",
      "domain": "assets",
      "scope": "write",
      "description": "Update a DAM asset: rename (name), replace its tags (tags object), or archive/unarchive (archived). Pass archived:true to soft-delete — it disappears from search_assets but any variant already pointing at its blob_url keeps working; archived:false restores it. At least one of name | tags | archived must be set. The blob bytes are never mutated.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Copy revision from the latest resource read. A stale revision returns conflict; reload and reconsider the edit, never remove the guard to force it."
        },
        {
          "name": "asset_id",
          "type": "string",
          "required": true,
          "description": "Asset id (asset_<uuid> shape)."
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "New display name (max 200 chars)."
        },
        {
          "name": "tags",
          "type": "object",
          "required": false,
          "description": "Replacement tags object. Overwrites the existing tags."
        },
        {
          "name": "archived",
          "type": "boolean",
          "required": false,
          "description": "true to archive (soft-delete / hide from search), false to restore."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_campaign",
      "title": "Update campaign",
      "domain": "personalization",
      "scope": "write",
      "description": "Update a campaign name, brief, status, goal or owner. A brief edit on an APPROVED campaign voids the approval (lifecycle drops to ready; response carries approval_cleared:true). status can only be changed while the campaign is planning or ready; from approved on it is controlled by launch_campaign / pause_campaign (409 otherwise).",
      "params": [
        {
          "name": "campaign_id",
          "type": "string",
          "required": true,
          "description": "Campaign ID"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brief",
          "type": "object",
          "required": false,
          "description": ""
        },
        {
          "name": "status",
          "type": "\"active\" | \"draft\" | \"paused\" | \"archived\"",
          "required": false,
          "description": ""
        },
        {
          "name": "goal_id",
          "type": "string | null",
          "required": false,
          "description": "Primary goal: a site_goals.id (see get_site_config goals). Must be an ACTIVE goal of this site; null clears it. Readiness requires one."
        },
        {
          "name": "owner_user_id",
          "type": "string | null",
          "required": false,
          "description": "Owner (a user id or name) responsible for the campaign; null clears it."
        },
        {
          "name": "starts_at",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "ends_at",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "eligibility",
          "type": "object | null",
          "required": false,
          "description": ""
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_component",
      "title": "Update component",
      "domain": "content",
      "scope": "write",
      "description": "Rename, update the brief, archive or restore a component. Use approve_component_revision for approval. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "component_id",
          "type": "string",
          "required": true,
          "description": "component id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "name"
        },
        {
          "name": "brief",
          "type": "string",
          "required": false,
          "description": "brief"
        },
        {
          "name": "status",
          "type": "\"draft\" | \"retired\"",
          "required": false,
          "description": "status"
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_component_experiment",
      "title": "Update component experiment",
      "domain": "personalization",
      "scope": "write",
      "description": "Start, pause or end a controlled component test, or add a treatment to a DRAFT one. ship=A keeps the unchanged page; ship=<treatment key> (B, C, ...) releases that treatment to everyone eligible and hides the others; no ship restores each placement's pre-test state. action=add_treatment with placement_id attaches the next arm (same page, anchor, audience and schedule as the first treatment) and is refused once the test has started: adding an arm would re-slice the equal split under visitors already assigned, so end the running test and start a fresh one instead. Changes live delivery. Alternatively, omit action and send ends_at (UTC ISO instant or null) and/or auto_stop alone to configure scheduling. Statistical auto-stop and automatic ship settings are recorded but withheld pending evidence qualification. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "experiment id"
        },
        {
          "name": "action",
          "type": "\"start\" | \"pause\" | \"end\" | \"add_treatment\"",
          "required": false,
          "description": "action; omit to configure scheduling"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "expected version"
        },
        {
          "name": "ship",
          "type": "string",
          "required": false,
          "description": "Arm key to ship on end: A (unchanged page) or a treatment key such as B or C"
        },
        {
          "name": "placement_id",
          "type": "string",
          "required": false,
          "description": "For add_treatment: the placement to attach as the next treatment arm"
        },
        {
          "name": "ends_at",
          "type": "string | null",
          "required": false,
          "description": "UTC ISO deadline; null removes it"
        },
        {
          "name": "auto_stop",
          "type": "object",
          "required": false,
          "description": ""
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_component_placement",
      "title": "Update component placement",
      "domain": "personalization",
      "scope": "write",
      "description": "Save a private draft of placement anchors, targeting, schedule or component revision. Visitors retain the published placement. Read expected_version first; publish explicitly after review. goal_id alone sets the placement's built-for goal without a new version or review. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "field_overrides",
          "type": "object",
          "required": false,
          "description": "Only exposed revision field keys; placement-specific text/image/link values. Empty object clears customizations."
        },
        {
          "name": "placement_id",
          "type": "string",
          "required": true,
          "description": "placement id"
        },
        {
          "name": "expected_version",
          "type": "number",
          "required": true,
          "description": "Version from the latest read; conflicts return 409"
        },
        {
          "name": "component_revision_id",
          "type": "string",
          "required": false,
          "description": "component revision id"
        },
        {
          "name": "position",
          "type": "\"before\" | \"after\" | \"between\" | \"inside_start\" | \"inside_end\" | \"replace\"",
          "required": false,
          "description": "position"
        },
        {
          "name": "element_selector",
          "type": "string",
          "required": false,
          "description": "element selector"
        },
        {
          "name": "second_anchor_selector",
          "type": "string",
          "required": false,
          "description": "second anchor selector"
        },
        {
          "name": "anchor_policy",
          "type": "\"required\" | \"when_present\"",
          "required": false,
          "description": "required reports a missing anchor. when_present waits for a conditional primary anchor without reporting absence as an error; invalid or ambiguous anchors still fail. Omit to preserve."
        },
        {
          "name": "page_pattern",
          "type": "string",
          "required": false,
          "description": "page pattern"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "name"
        },
        {
          "name": "page_type",
          "type": "string",
          "required": false,
          "description": "page type"
        },
        {
          "name": "order",
          "type": "number",
          "required": false,
          "description": "order"
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": "priority"
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": "eligibility"
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": "segment id"
        },
        {
          "name": "starts_at",
          "type": "string",
          "required": false,
          "description": "ISO timestamp, or empty string to remove start"
        },
        {
          "name": "ends_at",
          "type": "string",
          "required": false,
          "description": "ends at"
        },
        {
          "name": "goal_id",
          "type": "string | null",
          "required": false,
          "description": "Built for goal: id of an active goal of this site (from get_site_config) that this placement is meant to move. The Impact page and get_zone_exposure_report group placements by it. A goal of another site is refused with goal_not_found. It is a reporting label: it never changes what visitors see, makes no new placement version and is allowed during a test. Pass null (or an empty string) to clear it; omit to leave it unchanged. Sent alone (with expected_version), it is saved without a new placement version. Returns placement_goal_unavailable (503) on a database that does not have the column yet."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_content_experiment",
      "title": "Update content experiment",
      "domain": "personalization",
      "scope": "write",
      "description": "Start, pause or end a content test (action), or change its name/hypothesis/split (no action). Ending with ship=<armKey> keeps that arm live and turns the others off (ship=A with an Original arm restores the page); ending without ship restores each variant's pre-test state. Changing the split on a RUNNING test re-assigns some visitors and restarts the measured window. Requires expected_version (409 on conflict). Changes live delivery. When content approval is required, starting a test with unapproved arm content returns approval_required; call request_review first. Alternatively, omit action and send ends_at (UTC ISO instant or null) and/or auto_stop alone to configure scheduling. Statistical auto-stop and automatic ship settings are recorded but withheld pending evidence qualification.",
      "params": [
        {
          "name": "experiment_id",
          "type": "string",
          "required": true,
          "description": "Content experiment id (cxp_…)"
        },
        {
          "name": "action",
          "type": "\"start\" | \"pause\" | \"end\"",
          "required": false,
          "description": "Lifecycle transition; omit to update settings only"
        },
        {
          "name": "expected_version",
          "type": "integer",
          "required": true,
          "description": "The version you read (optimistic lock)"
        },
        {
          "name": "ship",
          "type": "string",
          "required": false,
          "description": "With action=end: arm key to keep live"
        },
        {
          "name": "reason",
          "type": "string",
          "required": false,
          "description": "With action=end: why"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "hypothesis",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "ends_at",
          "type": "string | null",
          "required": false,
          "description": "UTC ISO deadline; null removes it"
        },
        {
          "name": "auto_stop",
          "type": "object",
          "required": false,
          "description": ""
        },
        {
          "name": "split",
          "type": "object | null",
          "required": false,
          "description": "New {armKey: weight}; null for equal"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_content_variant",
      "title": "Update content variant",
      "domain": "personalization",
      "scope": "write",
      "description": "Update a content variant payload or active status. A history snapshot of the previous state is automatically saved before the update, so changes are always reversible via restore_content_variant. When replacing a copy_swap/hero_swap payload, read-modify-write against the zone's field_schema keys — a re-keyed payload sharing NO renderable key with the zone is refused with a 400 naming the accepted keys.",
      "params": [
        {
          "name": "variant_id",
          "type": "string",
          "required": true,
          "description": "Variant ID"
        },
        {
          "name": "payload",
          "type": "object",
          "required": false,
          "description": "Updated payload"
        },
        {
          "name": "active",
          "type": "boolean",
          "required": false,
          "description": "Enable/disable"
        },
        {
          "name": "type",
          "type": "\"overlay\" | \"hero_swap\" | \"copy_swap\" | \"product_reco\" | \"move_object\"",
          "required": false,
          "description": ""
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": ""
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "segment_id",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "campaign_id",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "destination_page_id",
          "type": "string | null",
          "required": false,
          "description": "Re-point the route at another page, or null to clear it (strips the vt_route attribution param). On a routed variant the CTA URL stays server-derived; activating re-validates the destination is published."
        },
        {
          "name": "ends_at",
          "type": "string | null",
          "required": false,
          "description": "Scheduled expiry (ISO 8601, at least 5 minutes out, within a year); null clears it. The hourly watchdog sweep deactivates the variant once this passes. active: true is also the manual re-enable after a watchdog auto-disable — it clears the disable stamp and restarts the CVR measurement window."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_form",
      "title": "Update form",
      "domain": "personalization",
      "scope": "write",
      "description": "Update a form template name and/or payload.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Copy revision from the latest resource read. On conflict reload and reconsider; never drop the guard to force the edit."
        },
        {
          "name": "id",
          "type": "string",
          "required": true,
          "description": "Form template ID"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "New name"
        },
        {
          "name": "payload",
          "type": "object",
          "required": false,
          "description": "Updated form design"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_integration",
      "title": "Update integration",
      "domain": "integrations",
      "scope": "write",
      "description": "Update integration config, events, or active status.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Copy revision from the latest resource read. On conflict reload and reconsider; never drop the guard to force the edit."
        },
        {
          "name": "integration_id",
          "type": "string",
          "required": true,
          "description": "Integration ID"
        },
        {
          "name": "config",
          "type": "object",
          "required": false,
          "description": ""
        },
        {
          "name": "events",
          "type": "string[]",
          "required": false,
          "description": ""
        },
        {
          "name": "active",
          "type": "boolean",
          "required": false,
          "description": ""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_offer",
      "title": "Update offer",
      "domain": "personalization",
      "scope": "write",
      "description": "Update an offer's terms, or move it between draft / paused / archived. `status` accepts ONLY those three. \"live\" is a dashboard-only transition gated on human approval plus code verification, and it is refused outright on the API-key path that every agent surface authenticates with — so you CAN pause or archive an offer that is misbehaving, and you CANNOT start one. Ask the operator to approve it in the dashboard instead. Editing the economics (offer_type, value_bp, value_cents, currency, tiers, gift_sku, code) UN-RATIFIES the offer and it stops serving until a human re-approves: approval attaches to specific terms, not to a row id, so a 10%-off offer approved yesterday can never silently become 40% off today — the response reports `approval_reset`. Changing `code` also clears the code verification. Same units and guardrails as create_offer (cents, basis points, the site depth ceiling, the margin floor). OMITTING a field leaves it untouched; sending it as NULL REMOVES it, which is how you make a promo open-ended (ends_at: null = runs indefinitely) or drop a threshold, a cap or a link. Only starts_at, ends_at, min_order_cents, max_discount_cents, max_exposures_per_visitor, code, redeem_url and terms_url accept null — null on anything else is rejected rather than ignored, so a mistaken null never silently no-ops. The response echoes what you removed as `cleared`.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Revision from the latest read; on conflict reload and reconsider, never drop the guard"
        },
        {
          "name": "id",
          "type": "string",
          "required": true,
          "description": "Offer ID (from list_offers)"
        },
        {
          "name": "status",
          "type": "\"draft\" | \"paused\" | \"archived\"",
          "required": false,
          "description": "draft | paused | archived only. \"live\" is dashboard-only and is rejected on this path — no agent tool can make an offer servable."
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "New internal name (max 200 chars)"
        },
        {
          "name": "offer_type",
          "type": "\"percent\" | \"fixed\" | \"free_shipping\" | \"gift\" | \"tiered\"",
          "required": false,
          "description": "Changing this un-ratifies the offer."
        },
        {
          "name": "value_bp",
          "type": "integer",
          "required": false,
          "description": "Percent discount in BASIS POINTS, 0-10000 (2500 = 25.00%). Changing it un-ratifies the offer."
        },
        {
          "name": "value_cents",
          "type": "integer",
          "required": false,
          "description": "Fixed discount in CENTS. Changing it un-ratifies the offer."
        },
        {
          "name": "currency",
          "type": "string",
          "required": false,
          "description": "3-letter ISO-4217 code. Changing it un-ratifies the offer."
        },
        {
          "name": "code",
          "type": "string | null",
          "required": false,
          "description": "Discount code as it exists in the merchant's platform. Changing it un-ratifies the offer AND clears code_verified_at, so a human must re-verify it. null removes the code — do this only for an offer that needs none (an automatic discount, a free-shipping threshold), because a code-less offer CANNOT be verified and so cannot go live again until a code is set."
        },
        {
          "name": "min_order_cents",
          "type": "integer | null",
          "required": false,
          "description": "MIRRORED TERM (cents) — fine print only; their platform enforces it. null removes the minimum-spend threshold."
        },
        {
          "name": "max_discount_cents",
          "type": "integer | null",
          "required": false,
          "description": "MIRRORED TERM (cents) — fine print only; their platform enforces it. null removes the cap."
        },
        {
          "name": "max_exposures_per_visitor",
          "type": "integer | null",
          "required": false,
          "description": "How many times WE show it to one visitor — the only cap AXO enforces. null uncaps it."
        },
        {
          "name": "starts_at",
          "type": "string | null",
          "required": false,
          "description": "ISO-8601 start of the validity window. null removes the start date, so it is valid from now."
        },
        {
          "name": "ends_at",
          "type": "string | null",
          "required": false,
          "description": "ISO-8601 end of the validity window; must be after starts_at. null removes the end date, so the offer runs INDEFINITELY until someone pauses it — the only way to express an open-ended promo."
        },
        {
          "name": "stacking",
          "type": "\"exclusive\" | \"stackable\" | \"group\"",
          "required": false,
          "description": "MIRRORED TERM rendered as fine print."
        },
        {
          "name": "tiers",
          "type": "object[]",
          "required": false,
          "description": "Tier ladder for offer_type=tiered (max 10). Changing it un-ratifies the offer."
        },
        {
          "name": "gift_sku",
          "type": "string",
          "required": false,
          "description": "Gift SKU for offer_type=gift. Changing it un-ratifies the offer."
        },
        {
          "name": "eligibility",
          "type": "object",
          "required": false,
          "description": "SegmentPredicate gating who the offer is valid for."
        },
        {
          "name": "redeem_url",
          "type": "string | null",
          "required": false,
          "description": "Where the offer CTA sends the visitor. null removes it."
        },
        {
          "name": "terms_url",
          "type": "string | null",
          "required": false,
          "description": "Full terms page. null removes it."
        },
        {
          "name": "presentation",
          "type": "object",
          "required": false,
          "description": "Default presentation inherited by placements: { headline, body, cta, cta_href, show_code, show_expiry }."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_page_config",
      "title": "Update page config",
      "domain": "personalization",
      "scope": "write",
      "description": "Update a page type configuration.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Revision from the latest read; on conflict reload and reconsider, never drop the guard"
        },
        {
          "name": "config_id",
          "type": "string",
          "required": true,
          "description": "Config ID"
        },
        {
          "name": "url_pattern",
          "type": "string",
          "required": false,
          "description": "Updated URL pattern"
        },
        {
          "name": "selectors",
          "type": "object",
          "required": false,
          "description": "Updated selectors"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_profile",
      "title": "Update profile",
      "domain": "audience",
      "scope": "write",
      "description": "Update a profile segment override or lifetime signals.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Revision from get_profile(for_update=true); on conflict reload and reconsider, never drop the guard"
        },
        {
          "name": "profile_id",
          "type": "string",
          "required": true,
          "description": "Profile ID"
        },
        {
          "name": "segment_id",
          "type": "string | null",
          "required": false,
          "description": "Override segment. The assignment is pinned — automatic classification will not overwrite it. Pass null to release the pin and resume automatic classification. Omit to leave the segment untouched."
        },
        {
          "name": "lifetime_signals",
          "type": "object",
          "required": false,
          "description": "Lifetime signal data"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_segment",
      "title": "Update segment",
      "domain": "audience",
      "scope": "write",
      "description": "Update a segment: rename, edit description, change emoji, visibility, or signal rules.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Copy revision from the latest resource read. A stale revision returns conflict; reload and reconsider the edit, never remove the guard to force it."
        },
        {
          "name": "id",
          "type": "string",
          "required": true,
          "description": "Segment ID"
        },
        {
          "name": "display_name",
          "type": "string",
          "required": false,
          "description": "New display name"
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": "New description"
        },
        {
          "name": "emoji",
          "type": "string",
          "required": false,
          "description": "New emoji"
        },
        {
          "name": "visible",
          "type": "boolean",
          "required": false,
          "description": "Show/hide segment"
        },
        {
          "name": "signal_rules",
          "type": "object",
          "required": false,
          "description": "New signal rules"
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_site_config",
      "title": "Update site config",
      "domain": "site",
      "scope": "write",
      "description": "Update site settings: identity (name/domain), brand, mode, conversion goal(s), zone overlap mode, holdouts, passive_mode, anti-flicker dials, activation-orchestrator rollout, trigger send window and per-person trigger cap. Only the fields you pass are touched. `network_contribution` is an OPT-IN to the cross-site observation graph (the site-blind, PII-free IP→company lane — never fed by profiles/emails/form fills), DEFAULT OFF: pass `true` only with the customer's actual agreement, and `false` to withdraw, which also scrubs already-annotated sessions. Pass `goals` (array) to FULL-REPLACE the typed multi-goal list — primary + optional supporting goals; read the current list via get_site_config first since omitted goals are deleted. `conversion_goal` (single URL string) is the legacy single-goal field; passing it materializes a primary URL-kind goal. Pass one OR the other, not both. `passive_mode: true` locks out first-party PII (no lead forms, no identify/CRM writes) while keeping anonymous behavioral + goal/conversion tracking — use for no-PII pilots. Anti-flicker dials: check get_flicker_health first and rule out binding failures (a wrong selector produces the same visible-swap signature); every dial write echoes the holdout-wait tradeoff in the response. `business_brief` records what the operator told you about their business and grounds every AI copy draft — note its facts/beliefs split, which is load-bearing. Cross-site super-admin callers must pass site_id from get_account_status; site-scoped callers stay on their own site.",
      "params": [
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": "Site display name"
        },
        {
          "name": "domain",
          "type": "string",
          "required": false,
          "description": "Site domain (empty string clears)"
        },
        {
          "name": "mode",
          "type": "\"active\" | \"backtest\"",
          "required": false,
          "description": ""
        },
        {
          "name": "conversion_goal",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "goals",
          "type": "object[]",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_primary",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_accent",
          "type": "string",
          "required": false,
          "description": "CTA button text color; use brand_secondary for the secondary brand color"
        },
        {
          "name": "brand_font",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_radius",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_headline_color",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_body_color",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_bg_color",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_backdrop_opacity",
          "type": "string",
          "required": false,
          "description": "Overlay backdrop opacity 0-1 (e.g. \"0.45\")"
        },
        {
          "name": "brand_cta_radius",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_heading_font",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_cta_font",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_secondary",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_preset",
          "type": "string | null",
          "required": false,
          "description": ""
        },
        {
          "name": "brand_follow_observed",
          "type": "\"brand_primary\" | \"brand_accent\" | \"brand_font\" | \"brand_radius\" | \"brand_headline_color\" | \"brand_body_color\" | \"brand_bg_color\" | \"brand_backdrop_opacity\" | \"brand_cta_radius\" | \"brand_preset\" | \"brand_heading_font\" | \"brand_cta_font\" | \"brand_secondary\"[]",
          "required": false,
          "description": "Remove manual overrides for these fields and use observed values. Other brand fields you pass become protected manual overrides."
        },
        {
          "name": "brand_expected_revision",
          "type": "string | null",
          "required": false,
          "description": "Copy site.brand_identity.management.revision from get_site_config to reject stale edits; null for a site without a managed brand revision"
        },
        {
          "name": "zone_overlap_mode",
          "type": "\"all\" | \"first\"",
          "required": false,
          "description": ""
        },
        {
          "name": "disable_holdout",
          "type": "boolean",
          "required": false,
          "description": ""
        },
        {
          "name": "holdout_pct",
          "type": "integer",
          "required": false,
          "description": "Holdout percentage 0-90 (default 10; 0 = no control group). Presets: 10 learning, 5 validated, 2 sentinel, 0 off. Sticky per visitor: changing it reshuffles part of the control group, the change is recorded and the Scorecard measures only from that date. At 0 the Scorecard withholds its verdict and says \"no control group\"."
        },
        {
          "name": "trigger_holdout_pct",
          "type": "integer | null",
          "required": false,
          "description": "Trigger-specific holdout 0-90, or null to inherit holdout_pct"
        },
        {
          "name": "passive_mode",
          "type": "boolean",
          "required": false,
          "description": ""
        },
        {
          "name": "network_contribution",
          "type": "boolean",
          "required": false,
          "description": "Per-site OPT-IN to the cross-site observation graph. Default OFF — omit to leave unchanged; a missing flag reads as NOT contributing, and only an explicit true opts a site in. Governs ONLY the shared IP-prefix→company-domain lane: a site-blind, PII-free firmographic signal (truncated /24–/48 network prefixes, carrying no site_id, visitor id, full IP, URL, or any form/identity data). Identified profiles, emails, and captured form fills NEVER feed this graph regardless of this flag. Set true ONLY when the customer has actually agreed to pool their observed company traffic — never as a default or a convenience; what they get back today is that companies seen on their site are queued for firmographic enrichment ahead of a lookup, and nothing reads the shared dataset back yet. Turning it on is NOT retroactive: it applies only to traffic collected from that point on. Set false to withdraw; that write also retroactively scrubs already-annotated sessions so a rollup running after the flip cannot harvest pre-flip data. Independent of passive_mode, which suppresses all contribution while it is on regardless of this flag."
        },
        {
          "name": "antiflicker_strategy",
          "type": "\"cookie\" | \"always\" | \"never\"",
          "required": false,
          "description": "Anti-flicker pre-hide strategy. Platform default \"always\" matches Adobe Target / Optimize / VWO."
        },
        {
          "name": "antiflicker_wait_selectors",
          "type": "string[]",
          "required": false,
          "description": "CSS selectors the reveal waits for before un-hiding. Pair the zone's own selector with the tag's apply stamp (e.g. '.hero-section[data-axo-applied]' — the tag sets data-axo-applied on every element it personalizes). CAUTION: a selector that never matches holds the page hidden for the full max_hide_ms on every load — verify the base selector exists on the live page first. \"*\"/\"html\"/\"body\" are rejected."
        },
        {
          "name": "antiflicker_max_hide_ms",
          "type": "number | null",
          "required": false,
          "description": "Safety-timer reveal budget in ms (default 3000, hard cap 8000; null resets to default). TRADEOFF: holdout visitors and no-segment-match visitors wait this full budget before the safety reveal."
        },
        {
          "name": "orchestrator_mode",
          "type": "\"off\" | \"shadow\" | \"enabled\"",
          "required": false,
          "description": "Activation-orchestrator rollout: off = triggers fire independently, shadow = decisions logged but not enforced, enabled = orchestrator allocates trigger fires"
        },
        {
          "name": "cadence_ceiling_per_24h",
          "type": "integer",
          "required": false,
          "description": "Max orchestrated trigger fires per visitor per 24h (default 1)"
        },
        {
          "name": "trigger_send_window_enabled",
          "type": "boolean",
          "required": false,
          "description": "Quiet hours for triggers. When true, a trigger that becomes due outside the send window waits for the next opening in the visitor's local time (their session time zone, else trigger_timezone; UTC only as the last fallback). Turning it on requires trigger_timezone, set in the same call or already stored, because many sessions carry no time zone. A trigger can override this with send_window. Needs migration 354."
        },
        {
          "name": "trigger_send_window_start_hour",
          "type": "integer",
          "required": false,
          "description": "Local hour the send window opens, 0-23 (default 8)."
        },
        {
          "name": "trigger_send_window_end_hour",
          "type": "integer",
          "required": false,
          "description": "Local hour the send window closes, 0-23, exclusive (default 21, so the last send starts before 21:00). A start after the end wraps past midnight. Start and end must differ."
        },
        {
          "name": "trigger_timezone",
          "type": "string | null",
          "required": false,
          "description": "IANA time zone (e.g. \"America/Chicago\") used for the send window when a visitor's session has none. null falls back to UTC."
        },
        {
          "name": "trigger_frequency_cap",
          "type": "integer | null",
          "required": false,
          "description": "Most trigger fires one person may receive across ALL of this site's triggers in trigger_frequency_cap_hours. Held-out fires count too, so the holdout comparison stays fair. null turns the cap off (default). Triggers with frequency_cap_exempt are neither blocked nor counted."
        },
        {
          "name": "trigger_frequency_cap_hours",
          "type": "integer",
          "required": false,
          "description": "Rolling window for trigger_frequency_cap, in hours (default 24)."
        },
        {
          "name": "extra_tracking_params",
          "type": "string[]",
          "required": false,
          "description": "Site-specific query params treated as tracking-only for page identity, ON TOP of the global blocklist (fbclid/gclid/utm_* are always stripped). Declare params this site's stack appends per click/submit without changing page content — e.g. Episerver Forms' __FormGuid/__FormLanguage/__FormSubmissionId, DSP auction ids like cntr_auctionId. Symptom that calls for this: Catalog page counts ballooning with near-duplicate URLs differing only in one param. FULL-REPLACE array, max 50 names (read get_site_config first); [] clears. CAUTION: never list content-bearing params (?page=, ?q=, ?variant=) — their pages would collapse into one Catalog entry."
        },
        {
          "name": "cross_domain_hosts",
          "type": "string[]",
          "required": false,
          "description": "Exact HTTPS hostnames allowed to share this single site visitor identity through one-time link handoffs. Include every participating host explicitly, including the primary host. No wildcards or implied subdomains. Requires consent on each host. FULL-REPLACE; [] disables; otherwise at least two hosts. Separate from ingest_allowed_hosts."
        },
        {
          "name": "ingest_allowed_hosts",
          "type": "string[] | null",
          "required": false,
          "description": "Ingest hostname allowlist. An ARRAY turns enforcement ON: pageviews and scores are recorded only from the site's own domain, these hosts, and subdomains of either; anything else (a staging or integration host carrying the production snippet) is dropped at ingest. null turns it OFF, the default: every host is accepted. [] is NOT a clear, it means own domain only. FULL-REPLACE, max 50 (read get_site_config first). CAUTION: a drop is permanent and silent to the visitor, so list every real host first (a separate checkout or booking domain is the usual miss)."
        },
        {
          "name": "analytics_excluded_geo",
          "type": "object[] | null",
          "required": false,
          "description": "Analytics geo exclusion: a REVERSIBLE reporting filter. Sessions whose location matches any rule are left out of analytics, the per-goal breakdown and segment performance, and the payload reports how many were excluded. Nothing is deleted; removing a rule restores them. Each rule: country (required, ISO alpha-2 such as \"MX\"), optional region (ISO 3166-2 CODE such as \"CMX\", not a name), optional city (case-insensitive substring). NOT applied to the scorecard, lift or optimizer. FULL-REPLACE, max 25; [] or null clears. Use for an office, a QA team or a bot cluster in one place; use ingest_allowed_hosts for staging hosts."
        },
        {
          "name": "loyalty_program",
          "type": "object | null",
          "required": false,
          "description": "Loyalty program settings for cart_gap loyalty sources (#2888). FULL-REPLACE object; null clears it and restores the default recognition rules. Recognized-but-not-signed-in visitors never get amounts, balances, tier names or voucher values, whatever this says."
        },
        {
          "name": "business_brief",
          "type": "object | null",
          "required": false,
          "description": "What the operator told us about their business (#1437), used to ground AI-drafted copy. FULL-REPLACE object — read get_site_config first and pass the complete brief, since anything omitted is deleted; null clears it entirely. Caps: each string <=400 chars, each list <=8 entries, whole object <=6KB.\n\nThe two halves are NOT interchangeable:\n• facts — things we cannot check and have no business checking: what they sell, who buys, their vocabulary, proof points, phrases legal forbids. Composed into every AI copy generation.\n• beliefs — falsifiable claims about behaviour (\"our best leads visit after 5pm\"). STORED AND SHOWN BUT DELIBERATELY WITHHELD FROM EVERY PROMPT until they can be measured. Do not restate a belief as a fact to make it take effect; that is the exact failure this split exists to prevent.\n\nWhen a claim carries a motive we could never verify (\"because they are frustrated at work\"), put that clause in the belief's not_tested field rather than dropping it."
        },
        {
          "name": "site_id",
          "type": "string",
          "required": false,
          "description": "Required for cross-site super-admin sessions: exact target site id from get_account_status. Ignored by site-scoped sessions, which remain locked to their own site."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_trigger",
      "title": "Update trigger",
      "domain": "personalization",
      "scope": "write",
      "description": "Update an existing trigger. Pass only the fields to change.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Revision from the latest read; on conflict reload and reconsider, never drop the guard"
        },
        {
          "name": "trigger_id",
          "type": "string",
          "required": true,
          "description": "Trigger ID"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "enabled",
          "type": "boolean",
          "required": false,
          "description": ""
        },
        {
          "name": "mode",
          "type": "\"immediate\" | \"dormancy\"",
          "required": false,
          "description": ""
        },
        {
          "name": "watch_event",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "cancel_event",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "criteria",
          "type": "object",
          "required": false,
          "description": ""
        },
        {
          "name": "delay_ms",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "window_ms",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "identifier_type",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "destinations",
          "type": "object[]",
          "required": false,
          "description": ""
        },
        {
          "name": "cooldown_per_profile_ms",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "global_rate_limit_per_min",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "optimization_mode",
          "type": "\"axo_bandit\" | \"external\"",
          "required": false,
          "description": "Optimization mode"
        },
        {
          "name": "send_window",
          "type": "object | null",
          "required": false,
          "description": "Per-trigger send window. null inherits the site setting; {\"enabled\": false} sends at any hour (for safety triggers); {\"enabled\": true, \"start_hour\": 9, \"end_hour\": 20} sets this trigger's own local hours. An enabled window needs the site trigger_timezone, and its start and end (with missing hours taken from the site) must differ. Needs migration 354."
        },
        {
          "name": "frequency_cap_exempt",
          "type": "boolean",
          "required": false,
          "description": "When true this trigger ignores the site's per-person cap and its fires do not count toward it. For safety triggers such as a declined card. Default false."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_trigger_template",
      "title": "Update trigger template",
      "domain": "personalization",
      "scope": "write",
      "description": "Update a site-owned trigger payload template.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Copy revision from the latest resource read. On conflict reload and reconsider; never drop the guard to force the edit."
        },
        {
          "name": "template_id",
          "type": "string",
          "required": true,
          "description": "Template ID"
        },
        {
          "name": "name",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "description",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "format",
          "type": "\"json\" | \"html\" | \"form\"",
          "required": false,
          "description": ""
        },
        {
          "name": "body",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "example_destination",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    },
    {
      "name": "update_zone",
      "title": "Update zone",
      "domain": "personalization",
      "scope": "write",
      "description": "Update an existing zone (selector, name, priority, approval, built-for goal, display_rules, or field schema). To add a missing field (e.g. image_url): call get_zones(zone_id) first to read the existing field_schema, then pass the full updated array with the new entry appended.",
      "params": [
        {
          "name": "expected_revision",
          "type": "string",
          "required": false,
          "description": "Copy revision from the latest resource read. A stale revision returns conflict; reload and reconsider the edit, never remove the guard to force it."
        },
        {
          "name": "zone_id",
          "type": "string",
          "required": true,
          "description": "Zone ID"
        },
        {
          "name": "display_name",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "element_selector",
          "type": "string",
          "required": false,
          "description": ""
        },
        {
          "name": "approved",
          "type": "boolean",
          "required": false,
          "description": ""
        },
        {
          "name": "priority",
          "type": "number",
          "required": false,
          "description": ""
        },
        {
          "name": "goal_id",
          "type": "string | null",
          "required": false,
          "description": "Built for goal: id of an active goal of this site (from get_site_config) that this placement is meant to move. The Impact page and get_zone_exposure_report group placements by it. A goal of another site is refused with goal_not_found. It is a reporting label: it never changes what visitors see, makes no new placement version and is allowed during a test. Pass null (or an empty string) to clear it; omit to leave it unchanged. Alone it is saved without expected_revision and also works on a component placement zone. Returns placement_goal_unavailable (503) on a database that does not have the column yet."
        },
        {
          "name": "display_rules",
          "type": "object",
          "required": false,
          "description": "Lightbox display rules — same shape as create_zone.display_rules (incl. audience.country ISO 3166-1 alpha-2 and schedule {days, hours, tz}). Send the full object to overwrite; omit to leave unchanged. Sanitized at the API — invalid country codes and unresolvable timezones are dropped silently, so read the zone back if the targeting matters."
        },
        {
          "name": "field_schema",
          "type": "object[]",
          "required": false,
          "description": "Full replacement field schema — pass the complete array including existing entries you want to keep. Read the current schema first with get_zones(zone_id)."
        },
        {
          "name": "dry_run",
          "type": "boolean",
          "required": false,
          "description": "Preview only: returns the request this tool would send without sending it. Server-side validation is not exercised."
        },
        {
          "name": "idempotency_key",
          "type": "string",
          "required": false,
          "description": "Retry-safe key (8–200 chars). Repeating a call with the same key returns the first result instead of running again."
        }
      ]
    }
  ]
}
