# AXO MCP tool reference > The AXO MCP server lets AI agents operate website personalization end to end. This public reference documents 281 tools; operator and diagnostic tools are available to connected agents at runtime but omitted here. Hosted endpoint: https://axoapp.ai/api/mcp (streamable HTTP). Authenticate with a scoped mcpt_ token minted in the AXO dashboard (Settings → MCP Tokens). Scopes: read < write < admin. Seller workflows ship as the "axo" plugin for Claude (skills pre-call-brief, hot-accounts, explain-exposure and draft-segment-page) bundled with this connector; install steps, the token to mint per skill and the plugin archive are at https://axoapp.ai/docs/reference/mcp#plugin. ## Analytics & lift Read performance, holdout lift, optimizer state, events, and sessions. ### find_stalls `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `trailing_weeks` | integer | no | Trailing weeks to compare this week against, 0 to 3 (default 3). 0 reads only this week: cheaper, and drops the vs_trailing comparison. | ### get_ad_performance `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `days` | integer | no | Window in days (default 30, max 90) | | `platform` | string | no | Filter to one platform, e.g. "google_ads" or "meta_ads" | | `limit` | integer | no | Rows per page (default 25, max 100) | | `cursor` | string | no | Opaque keyset cursor from the previous page's next_cursor | ### get_agent_usage `[read]` AI assistant usage: conversations, messages, tokens, estimated cost. ### get_ai_generations `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `variant_id` | string | yes | content_variants.id to read provenance for. | ### get_analytics `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `days` | number | no | Number of days to look back for windowed metrics (default: 90, max: 365); visitor_mix and significance are always all-time | ### get_backtest `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `days` | number | no | Number of days to look back (default: 90, max: 365) | ### get_component_experiment_results `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `experiment_id` | string | yes | experiment id | | `site_id` | string | no | 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. | ### get_component_performance `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `component_id` | string | yes | component id | | `days` | number | no | days | | `site_id` | string | no | 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. | ### get_content_experiment_results `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `experiment_id` | string | yes | Content experiment id (cxp_…) | ### get_customer_ga4_report `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `integration_id` | string | yes | Connection ID from list_customer_analytics_connections | | `start_date` | string | yes | Start date YYYY-MM-DD | | `end_date` | string | yes | End date YYYY-MM-DD | | `dimensions` | string | no | Comma-separated dimensions; omit for totals | | `metrics` | string | no | Comma-separated GA4 metrics; defaults to sessions,activeUsers | | `limit` | integer | no | Rows per page (default 100) | | `offset` | integer | no | Row offset (default 0) | ### get_customer_gsc_report `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `integration_id` | string | yes | Connection ID from list_customer_analytics_connections | | `start_date` | string | yes | Start date YYYY-MM-DD | | `end_date` | string | yes | End date YYYY-MM-DD | | `dimensions` | string | no | Comma-separated dimensions; omit for totals | | `limit` | integer | no | Rows per page (default 100) | | `offset` | integer | no | Row offset (default 0) | ### get_daypart_performance `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `days` | number | no | Days to look back (clamped to 1-365, default: 90); the rate window still floors at the first recorded conversion | ### get_decision_outcomes `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `since_days` | number | no | How far back to list decisions (default 90, max 365). | | `decision_kind` | string | no | 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). | | `entity_type` | string | no | Filter to one entity type: content_variant, offer, trigger, experiment_proposal. | | `limit` | number | no | Rows per page (default 50, max 200). | | `cursor` | string | no | Opaque cursor from a previous response's next_cursor. | ### get_events `[read]` 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. | Parameter | Type | Required | Description | | --- | --- | --- | --- | | `limit` | number | no | Max events (default 100, max 200) | | `cursor` | string | no | 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. | ### get_form_funnel `[read]` 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