MCP tools for campaigns and page elements
Look up campaign arguments, edit one audience safely, set up and end A/B tests, validate page targeting, and read live performance without publishing your draft.
Use these tools to inspect page elements, build audience-specific website changes, and read campaign results. Configuration reads can show an unpublished draft. Performance reports measure the live campaign.
MCP campaign access and draft changes
All tools accept optional project_id (string). Omission uses the connection's active project or an accessible project the server can resolve. Pass it explicitly when working across projects.
The tools follow the shared MCP authorization and project-selection rules. Grow includes MCP access. Campaign personalization and goals require Scale or above to publish. Creating a draft through MCP does not override that publishing requirement.
Create, update, duplicate, and delete operate on the draft. They save a configuration snapshot, validate the result, and update the draft. None publishes or changes a live page immediately. Resource reads use the draft first, otherwise the published configuration. Shared failures include missing or inaccessible projects, missing entitlement, and configuration validation errors. See publication, backups, and undo before making changes live.
Examples are illustrative, not executed. Requests show the tool-call name and arguments. JSON results show the content, not the MCP transport envelope. Excerpts are labeled.
Campaign write arguments
Creation and update accept the following fields. Creation requires name; update requires campaign_id and at least one changed field.
| Argument | Type and behavior |
|---|---|
name | String. Internal campaign name. |
is_active | Boolean. Creation default: true. |
base_url | String. Campaign base URL. Creation trims it and adds https:// if no HTTP scheme is present. Creation default: null. Update does not perform that normalization. |
testing | Object. The campaign holdback ("Campaign vs holdback"), separate from audience A/B tests. is_enabled is boolean; on creation it defaults to true only when the campaign has audiences (changes), and to false for a campaign without audiences. withhold is the held-back percentage, a whole number from 1 to 100. Creation defaults it to 10. |
goal_ids | Array of goal-ID strings, default [] on creation. Order matters for primary_goal. Starting an A/B test needs at least one goal. |
outcome | any_goal or primary_goal. Creation default any_goal. Legacy campaigns without the field use primary_goal. |
changes | Array of change objects described below. Omission creates an empty campaign or preserves an existing campaign's audiences. |
audience_tests | Update only. Array of A/B test setups, described in audience A/B test setup. Omission preserves existing tests. |
any_goal counts a visitor once when they convert on any attached goal. primary_goal uses the first ID in goal_ids. Use goal tools to create or inspect those goals.
On update, supplied top-level fields replace the corresponding stored fields; omitted fields remain. testing replaces the object rather than deep-merging it, although an omitted or nonnumeric withhold keeps the stored percentage. Save-time repair resets an omitted or nonboolean is_enabled to true, even if testing was previously off. Send both is_enabled and withhold when changing testing. Top-level null arguments are ignored, not a way to clear a field.
Each changes item
| Property | Type and behavior |
|---|---|
id | Existing action ID, used on update to merge into that action. Omit for a new action. |
variant_id | Existing variant ID returned with id. Keep it to distinguish actions whose IDs repeat across variants. "original" is reserved for the original page test's challenger rows. |
version_id | A/B test version: A, B, C, or D. Omit it to edit the audience's own changes (version A). Rows with variant_id: "original" must name a challenger, B–D. |
kind | override for a challenger row that changes or skips one of version A's actions, described below. |
segment_ids | A list of segment IDs or a comma-separated string, such as ["seg_a", "seg_b"] or "seg_a, seg_b". Anything else is rejected. New changes require at least one valid segment ID. Keep IDs in the same order across new changes that should share an audience. Omission on an existing change preserves its targeting; empty or null targeting is rejected. |
selector | CSS-selector string. Use unless supplying targeting. A new change that edits content with neither selector nor targeting is rejected with Each new change that edits the page needs the element it changes: targeting from get-page-elements, or a selector. |
targeting | Smart Match object. Prefer the complete object returned by get-page-elements. |
page | Comma-separated domain/path patterns, such as example.com/pricing, example.com/demo. No URL scheme. Omission on a new action scopes it to the campaign's base_url page (its host and path), or all pages when the campaign has no base_url. Send * to target every page. Omission on an existing action preserves its page rules. |
content | Replacement string, or newline-separated /find/replace/ expressions for partial text changes. |
visibility | show or hide. |
style | Object mapping CSS properties to overrides. |
attr | Object mapping HTML attributes to overrides. |
add_classes, remove_classes | CSS-class strings, separated by commas or spaces. A leading . is ignored. |
A domain and its www. form match each other. Other subdomains must be targeted explicitly. */blog* targets matching paths on any domain. Each page token becomes {domain, path}; a token without a slash gets path *.
The changes array is a replacement list, not an append operation. On update:
- Start with the complete
changesreturned byget-campaign. - Keep every action you want to retain. Unrepresented actions are removed; variants emptied by that removal are removed too. Variants that already had no actions are retained.
- An existing
idplusvariant_idmerges supplied properties into the stored action. Omitted action properties survive. Explicit null removes targeting or modification values; nullselectorbecomes an empty string, and nullpageresets targeting to all domains and paths. - Matching segment sets retain the existing variant ID and name. An unchanged round-trip retains the stored campaign content.
- A change without
idcreates a new action.
Do not reconstruct the update from the presentation-oriented variants field. Its long text can be truncated.
Resolve every segment ID before saving. Unknown segment IDs are rejected. Every audience must target at least one segment; Original page is the unchanged page, not a targetable audience. Empty targeting returns Each audience must target at least one segment. Provide non-empty segment_ids; Original is the unchanged page. The one exception is an original page test: its challenger rows use variant_id: "original" and a version_id of B–D, and carry no segment targeting. New audiences group by the supplied ID order, while matching an existing audience compares the segment set without regard to order.
Audience A/B test setup
An A/B test splits one audience, or the original page, into whole versions: A is the audience's existing changes, and B, C, and D are challengers. For the original page, A (Control) is the unchanged page and has no changes. The editor workflow is described in run an A/B test on a page or audience.
Each audience_tests item is {variant_id, test: {id, state, run?, versions: [{id, name, weight}]}}. Use an existing audience ID, or "original" for the original page.
{variant_id, test: {}}creates a setup with versions A and B at 50/50. It doesn't touchchanges.- To add C or D, send the full
versionslist. Weights are integers from 1 to 99 and must total 100. A test has at most four versions, and must keep A plus at least one challenger. stateandruncan't change here. Lifecycle changes go throughstart-audience-test,restart-audience-test, andend-audience-test, otherwise the request returnsUse the start, restart or end audience test operation to change its lifecycle.A new setup sent with a non-setup state returnsCreate a setup first; use start-audience-test to start it.- Names can change while a test is live. Versions, weights, content, and targeting change only through a restart.
- Deleting a tested audience while its test runs returns
End the test before deleting its audience, or restart the test before changing its lifecycle.
Challenger content goes in changes. A challenger-only element is a normal change row with version_id. A change to one of version A's actions is {variant_id, version_id: "B", kind: "override", action_id, skip: true} to drop that action, or the same row with fields: [{path: ["text"], op: "set", value: "New copy"}] (op is set or remove) to alter it. Send the whole authored changes array from get-campaign, not resolved copies. Original page rows can't use kind: "override", because Control has no actions.
Smart Match object
targeting requires mode: fingerprint or selector. It can contain fallbackSelector and selector strings. A fingerprint object requires text; its nested fields are:
| Field | Schema |
|---|---|
text | Required content string; optional matchType: exact, contains, or startsWith, default exact. |
element | tagName string and candidateTags string array, default []. |
structure | Integer siblingIndex, siblingCount, and depth; string parentChain. |
semantics | Nullable strings id, ariaRole, ariaLabel; nullable object dataAttributes; string array meaningfulClasses, default []. |
content | Integer charCount and wordCount; string textPrefix. |
visual | fontSize: {min, max}, fontWeight: {min}, and positionY: {min, max}, with numeric bounds; boolean isAboveFold. These signals are not returned by the server-side page extractor. |
Preserve the returned fingerprint instead of guessing it. Identical text on two elements still requires two actions, because their structure signals distinguish the elements.
Returned campaign object
Create, update, and duplicate return the saved campaign, including:
id(cpn_…),name,is_active,base_url,goal_ids,outcome, andtesting.original, the original page node. It holds the original pagetestand anydeploymentfrom an ended original page test.- Configuration fields
recipe_code,dimension_id, andcondition; new campaigns use null, null, and true respectively. variants: the stored audiences, each withid,name,rules, andactions, plustestwhen the audience has an A/B test. New audience rules userules.allentries withdimensionIdandsegment. Existing audiences can userules.any. All audiences require segment targeting.- Each action includes its
id, target/selector, page-rule array, andmodifications. Modifications can containtext,findReplaceentries{find, replace},visibility,style,attr, andclasseswithadd/removearrays.
Create and update also add unique_variant_count when it is greater than zero. This is the number of segment combinations across the dimensions used in variant rules, not simply the length of variants.
get-page-elements
Fetch a page's rendered HTML and identify elements available for personalization. Requires url (full HTTP or HTTPS URL). Optional no_cache (boolean, default false) bypasses the 24-hour HTML cache. Optional project_id uses the shared contract.
Returns {url, title, element_count, elements_capped, elements}. element_count is the total found before the 200-element cap; elements_capped indicates truncation. Each element contains uppercase type, content, selector_stable, text_unique, and targeting.
The extractor includes headings, paragraphs, buttons, links, and list items with at least three bytes of text. It skips scripts, styles, noscript, SVG, head, navigation, and footer subtrees. A header can still contain eligible content. Fingerprint parent chains contain at most five ancestors, while depth describes the full depth. Semantic hints omit empty values, skip internal data-rm-* attributes, and keep at most three meaningful classes.
This is read-only with respect to RightMessage configuration, but it makes an external page-fetch request through Firecrawl and can populate its cache. It does not install a snippet or edit the page.
Invalid URLs return Invalid URL format. Provide a full URL including https://. Fetch failures return Failed to fetch page HTML: …, or an error saying the page may be unreachable or Firecrawl may not be configured.
Illustrative request and result:
list-campaigns
List compact campaign summaries. The only argument is optional project_id. No configuration changes occur.
Returns an array of {id, name, is_active, variantCount, goalCount, abTestSummary} and outcome when that field exists. abTestSummary is a string such as 10% control group, or null when testing is off. variantCount counts saved variants, not all possible combinations. Shared access errors apply; an empty configuration returns [].
Illustrative request and result:
get-campaign
Read a campaign and its lossless editable changes. Requires campaign_id (string); accepts optional project_id.
Returns the campaign object, plus changes in the write format above, audience_tests, and original_deployment. Presentation fields add isDefault to variants, pageFormatted to actions, and resolved names alongside known IDs. Modification strings in variants are shortened after 100 bytes and can include phrase annotations. The separate changes array is untruncated and preserves empty JSON objects.
audience_testslists every A/B test as{variant_id, test: {id, state, run?, versions: [{id, name, weight}]}}, with the original page's first undervariant_id: "original".stateissetuporrunning. Copy it withchangesfor a lossless round trip.original_deploymentis null unless an original page test ended withdeploy: true. Then it is{test_id, version_id, name, deployed_at, proven, changes}: the version everyone who sees the original page still gets untilundeploy-original-winnerremoves it.deployed_atis null on deployments made before it was recorded.provenis true only when that version's recorded verdict was Winner. Deploying is a bridge until your website shows the changes itself, not a permanent patch.
Missing ID returns campaign_id is required. Unknown ID returns Campaign '…' not found. No configuration changes occur.
Illustrative request and result excerpt:
create-campaign
Create a campaign in the draft. Requires name and accepts the campaign write arguments plus optional project_id.
Returns the saved campaign object. Without changes, it creates no audiences and its holdback starts off. Each new change needs non-empty, valid segment_ids. Empty targeting and unknown segment IDs reject the save. Missing name returns name is required. Shared save and plan boundaries apply. A campaign without audiences can still A/B test the page itself: add an original page test with update-campaign and audience_tests: [{variant_id: "original", test: {}}].
Illustrative request and result excerpt:
Use the Smart Match targeting from get-page-elements when available; the shorter selector example assumes a known stable ID.
update-campaign
Change a draft campaign. Requires campaign_id (string) and at least one campaign write argument. Optional project_id uses the shared contract.
Returns the saved campaign object. A supplied changes list can remove actions and variants, so retain the complete list you intend to keep. To rename a campaign, omit changes entirely. Unknown ID returns Campaign '…' not found. Missing fields return At least one field to update must be provided. Invalid configuration returns an error rather than a saved result.
Illustrative request and result excerpt:
set-audience-changes
Change one existing audience, or one version of an A/B test, without resending the campaign's complete changes list. The hosted MCP name is set-audience-changes; Righty uses the internal name set_audience_changes. This tool saves a draft, preserves the audience's ALL or ANY segment rules, and leaves other audiences unchanged. It never deletes saved changes or audiences.
| Argument | Type and behavior |
|---|---|
project_id | Optional string under the shared project-selection contract. |
campaign_id | Required string. Existing campaign ID, maximum 128 characters. |
variant_id | Required string. Exact existing audience ID, maximum 128 characters. The audience must have segment targeting. Use "original" to write a challenger for the original page test. |
version_id | Optional: A, B, C, or D; default A. Writes that A/B test version. Challenger edits never change A. For "original", use B–D, because Control is the unchanged page. |
mode | Required: fill or rewrite. Use fill unless the user explicitly asks to replace existing changes. fill skips tested audiences. There is no replace mode. |
changes | Required array of 1–200 objects, totaling at most 128 KiB of JSON. |
changes[].selector | Required CSS-selector string, maximum 4,096 characters. |
changes[].targeting | Optional stored Smart Match object. Preserve the returned object rather than guessing its fields. |
changes[].page | Optional array of 1–50 page-rule objects. Outside an editor context, omission uses the campaign's base_url page; it's required only when the campaign has no base_url. It is not the comma-separated string used by create-campaign and update-campaign. |
changes[].modifications | Required object containing supported stored modification fields, described below. |
Fill or replace existing changes
fill skips an audience-and-element pair when it already has a change on an overlapping page. That includes styling, attributes, classes, and visibility changes, not just text. The check uses the latest saved draft and any supplied editor observations. Selector aliases and Smart Match fingerprints help identify the same target. Page scopes that can't be proven separate are treated as overlapping.
rewrite updates one matching saved change, or creates a new one when none matches. If several saved changes match the target and page, the request is rejected instead of choosing one.
A text-only rewrite patches modifications.text and keeps the existing styling, attributes, classes, and visibility. If you supply other modification fields too, the supplied modifications object replaces the existing object. Include every modification you want to retain in that case. Rewriting preserves the saved change's target and page rules.
Supported fields are:
text: string, at most 65,536 characters.visibility:showorhide.style: object with at most 100 CSS properties; each value is a string up to 4,096 characters.attr: object with at most 100 attributes; each value is a string up to 8,192 characters.classes: object withaddand/orremovestring arrays, each with at most 100 class names of up to 256 characters. An entry can name several classes separated by commas or spaces, and the 100-name limit counts them after splitting.
This tool does not accept the flattened content, add_classes, or remove_classes fields from campaign CRUD, or a findReplace field.
Page rules and validation
Each page-rule object requires domain and path. domain is a hostname or *, normalized to lowercase without a scheme or port. Its input is limited to 253 characters; path is a string up to 8,192 characters.
Optional operator accepts equals, does not equal, contains, does not contain, starts with, ends with, or glob. Without an operator, a path without * matches exactly; a path with * uses the legacy wildcard behavior. Prefer an explicit operator.
With saved editor context, omitting page uses the current editor URL as an exact page rule. Outside that context, omitting page uses the campaign's base_url page as an exact rule. An empty array is invalid and returns Page rules must contain at least one rule. Omit page to use the current editor URL. A missing page outside the editor on a campaign without base_url returns Page rules are required outside the campaign editor when the campaign has no base_url.
When editor observations are available, fill requests on that page reject copy unchanged from the original, including whitespace-only differences. Known elements that can't hold text reject text changes. A request that replaces a container's text and also changes one of its children on an overlapping page is rejected.
Matrix selection conversations cannot write during their opening recap. Later writes must stay within the saved selected cells and exact page. A selected-cell rewrite is rejected if its existing change also covers other pages. Matrix fill requests are similarly limited to their saved empty-cell scope.
To change a hosted page (Pages), name the element as [data-rm-id="…"] and pass the page's page_rule from get-page as page. MCP tools for pages covers page audiences and tests.
Request and result
Illustrative request using IDs from the chosen project:
The result is {campaignId, variantId, variantName, applied, skipped}. applied contains {actionId, label} for each saved change. skipped contains {label, reason} for each skipped element. A fully skipped request doesn't create a change receipt.
For the corresponding editor workflows, see write page copy with Righty, personalize page content, and personalization audiences.
duplicate-campaign
Copy a campaign into the draft. Requires campaign_id (string). Optional new_name defaults to the original name plus (Copy); optional project_id uses the shared contract.
Returns the copied campaign with a fresh campaign ID and fresh variant/action IDs. Goals and segment references remain references to the existing resources. The copy retains the campaign's configuration; it is not a copy of historical performance. Nothing is published.
Missing ID, an unknown campaign, or invalid copied configuration returns an error.
Illustrative request and result excerpt:
delete-campaign
Remove a campaign from the draft. Requires campaign_id (string); accepts optional project_id.
Returns {deleted: true, campaign_id}. It removes the campaign configuration, not the goal resources or previously recorded events. The live campaign is unaffected until publication. A campaign with a live A/B test returns End the live A/B test before deleting this campaign. Missing ID returns campaign_id is required. Unknown ID returns Campaign '…' not found.
Illustrative request and result:
A/B test lifecycle tools
start-audience-test, restart-audience-test, end-audience-test, and undeploy-original-winner change the draft only. Each saves a configuration snapshot and returns {campaign, requires_publish: true}. Visitors see nothing new until publish-config. Publishing is what starts or restarts the test for visitors.
Starting or restarting a test requires Pro. Ending a test and undeploying a winner don't need Pro. If the project moves to a plan without Pro, a running test stops on the live site when the plan changes, or at the next publish at the latest, and stays in the draft; publish-config still publishes the project's other changes. Grow doesn't include campaigns, so campaigns and their tests come off the live site.
All four accept optional project_id and require campaign_id. The three test tools also require audience_id: an audience ID from the campaign's variants, or "original" for the original page test.
| Tool | Extra arguments | What it does to the draft |
|---|---|---|
start-audience-test | None | Starts a test in setup state with a fresh run. |
restart-audience-test | None | Gives a running test a fresh run so its versions, weights, content, or targeting can change. Earlier results stay in history. Visitors keep the published run until you publish. |
end-audience-test | keep_version (required: A, B, C, or D); deploy (optional boolean) | Removes the test. For an audience, the kept version becomes the audience's changes. For the original page, nothing is kept unless deploy is true. Results stay in history. |
undeploy-original-winner | None | Removes the original_deployment winner. Use it once your website shows those changes itself. |
deploy: true applies only to audience_id: "original" with a keep_version of B, C, or D. It keeps showing that version to everyone who sees the original page until undeploy-original-winner, while you update your website.
Without Pro, start and restart are refused before anything changes: Starting an A/B test needs the Pro plan. The test setup stays in the draft and ending a test is always allowed; upgrade the project to Pro (Settings › Billing) to start it. No snapshot is saved.
On Pro, start and restart refuse the request until the test is ready. The error joins every blocker:
Choose a campaign goal before starting a test.Add at least one challenger version.Version B must differ from A.(one per identical challenger)
Other refusals:
This operation requires a setup test.(start on a running test) orThis operation requires a running test.(restart on a setup test).This audience has no test.andUnknown audience ….Only the original page deploys a winner. Ending an audience test keeps the version as its changes.(deploy: trueon an audience).Control is the original page, so there is nothing to deploy.(deploy: truewithkeep_version: "A").The original page has no deployed winner.(undeploy with nothing deployed).keep_version must be A, B, C, or D.,campaign_id is required.,audience_id is required., andCampaign not found: ….
Illustrative request and result excerpt:
For when to end a test and what Deploy means, see end a test and deploy the winner.
get-campaign-performance
Read live campaign results for an inclusive local-date range, plus every audience and original page A/B test. This tool does not measure draft-only changes and does not alter a test.
| Argument | Type and behavior |
|---|---|
date_from | Required string: YYYY-MM-DD or ISO 8601 timestamp. The date is taken in timezone. |
date_to | Required string in the same format. Includes that local date. Must not precede date_from; maximum 90 inclusive days. |
timezone | IANA timezone string, default UTC. |
campaign_id | Optional string to filter returned campaign rows. |
project_id | Optional string under the shared access contract. |
Range figures count unique visitors. A conversion must follow exposure in the same arm. Visitors exposed to both arms are excluded. The selected range's descriptive lift is not a fresh experiment decision.
Response states
A cold or stale computation can return:
Call again with the same arguments after the stated delay. This queues or reads reporting work; it does not publish. audienceTests is already filled in while campaign results compute.
Other responses have status, period ({start, end, timezone} or null), computedAt (timestamp or null), and stale (boolean). Non-ready states include capped, not_live, and unavailable; they add reason and totals. Missing data is not zero.
A ready response adds:
| Field | Contract |
|---|---|
headline | Portfolio comparison: status, basis, relativeLift, observedLift, campaignCount, basisCounts, visitors, reason, display, and text. basis is holdback, proven, mixed, or null. basisCounts counts the campaigns in the headline by basis; its baseline count is always 0. display has value and label strings. The headline is the visitor-weighted mean of controlled lifts only (holdback and proven); baseline lifts stay on their campaigns and never enter it. reason not_controlled means only baseline lifts were available. |
experimentCounts | Counts keyed by experiment state. This is separate from the date-range measurement. |
campaigns | Array of campaign result objects described below. |
notLive | Array of {id, name, reason} for draft_only or inactive campaigns. A draft_goals entry also contains goalIds. |
totals | visitorsReached and goalConversions measurement objects, not bare numbers. |
audienceTests | Array of A/B test results, described in A/B test results. |
Filtering by campaign_id filters campaigns, notLive, audienceTests, and the totals; the portfolio headline and experiment counts are assembled before that filter.
Each campaign result has id, name, testingEnabled, holdbackPercent, daysOfData, exposedVisitorsPerDay, visitors: {personalized, holdback}, excludedBothArmVisitors, status, statusLabel, statusReason, measuredFrom, goals, outcome, comparison, experiment, and revenue.
goals is an array; outcome is a goal-shaped object or null. Each contains id, name, primary, personalized, holdback, lift, relativeLift, ci95, pValue, status, statusLabel, and statusReason. Both arms contain visitors, converters, and nullable conversionRate. Rates and relativeLift are proportions, not percentages. ci95 is {lower, upper} or null. Null lift means it cannot be measured, not no effect.
comparison is null or {basis, label, lift, relativeLift, rate, reference}:
holdbackcompares personalized visitors with the control group in the selected range. With testing on,comparisonis null until both groups have at least 10 converters.provenis a prior A/B result retained after rollout, not a remeasurement of this range. Itsrateandreferenceare null.baselinecompares same-page visitors who did not see the campaign, weighted to the personalized group's engagement and known-contact mix. It is not a controlled test.
When present, reference has visitors, converters, and conversionRate. Per-goal status text still describes holdback diagnostics even when the campaign comparison uses another basis.
experiment is null or the separately persisted decision: state, label, versionNumber, startedAt, seriesStartWeek, provenance, metricId, lookNumber, cutoffAt, decidedAt, arms, observedLift, decidedLift, decidedVisitors, haltReason, and proven. Timestamps, metrics, arm results, and lift values can be null when there is not enough decision-grade data. Experiment arms use enrollment and converter counts rather than the range's exposed-visitor totals. proven, when present, contains state, label, versionNumber, startedAt, endedAt, decidedAt, metricId, observedLift, decidedLift, decidedVisitors, and unchanged.
Experiment state is collecting, winner, loser, inconclusive, halted, not_decision_grade, or not_measurable; those are also the experimentCounts keys. When available, arms.personalized and arms.holdback contain enrolled, converters, and nullable pageviews. decidedVisitors is the combined enrolled-visitor count at the decision, not a visitor list. haltReason is srm, contamination, or null. experiment describes the campaign holdback only; A/B test results are in audienceTests.
Campaign revenue
revenue is revenue per visitor by arm from the connected payment source (Connect Stripe). It is null when no payment source is connected. Otherwise it has a status:
computingwithretryAfterSeconds: call again with the same arguments later.unavailablewithreason.ready, which addssource,matching,excludedPeople,identified,minBuyers,minVisitors, andcurrencies.
Revenue counts each identified visitor's payments after their first exposure in their arm. Visitors never identified by email count as $0 in both arms. People exposed in both arms are left out and counted in excludedPeople. matching is complete, or capped when the campaign had too many visitors to match; a capped result has no currencies.
Each currencies item has currency, personalized, holdback, and difference. Each arm has visitors, buyers, net, and perVisitor. perVisitor is null while that arm has fewer than minBuyers (5) buyers or minVisitors (30) visitors. difference is personalized minus held back per visitor, as {perVisitor, low, high} with a 95% range, and is null unless both arms pass those thresholds. Currencies are never added together.
identified is null or {personalized, holdback, differs}: each arm's percentage of visitors with an email. differs: true means the arms were identified at different rates, so matched revenue can differ for reasons other than buying.
Campaign revenue is descriptive only. It never changes a comparison, a test result, or the experiment state.
A/B test results
Each audienceTests item is one run of an audience or original page test: testId, campaignId, audienceId (original for the original page), run, status, startedAt, endedAt, weeksRunning, lookNumber, nextCheckAt, haltReason, missedChecks, cannotDecideReason, versions, and heldBackVisitors. Verdicts are checked weekly; nextCheckAt is the next weekly check while the test runs, otherwise null.
status is one of:
running: collecting weekly checks.ready_to_end: every challenger has a verdict.cannot_decide:cannotDecideReasonismissed_checksorlate_weeks.paused_health: results are on hold.haltReasonisallocation,contamination, orunconfirmed_publish.unconfirmed_publishmeans RightMessage couldn't confirm when the last publish went live, so results are on hold and version counts are withheld, not zero. It isn't a split or traffic problem.paused_campaign_off: the campaign is off.ended.
Each versions item has id, name, weight, visitors, conversions, rate, diffFromA, verdict, likelyRange ({low, high} or null), conversionValue, valuePerVisitor, and decidedLookNumber. verdict is baseline for A, then collecting, winner, loser, inconclusive, cannot_decide, or no_verdict. no_verdict means the test ended before that version reached a verdict; it is not still collecting. conversionValue comes from goal values and never decides a winner. How to read these is covered in read A/B test results.
Each totals measurement uses one of three shapes:
- Ready:
{status: "ready", value: number, computedAt: string, stale: boolean}. - Computing:
{status: "computing", retryAfterSeconds: number}. - Unavailable:
{status: "unavailable", reason: string, explanation: string, availableFrom: string|null}. There is no numericvaluein this state.
Errors and example
Invalid timezone returns timezone must be a valid IANA timezone. Too many days returns The date range must be no more than 90 inclusive days. Invalid or reversed dates return an error describing the accepted date formats. A specific campaign absent from both live and draft/inactive entries returns a not-found error. Other reporting failures return Failed to fetch campaign performance for project ….
Illustrative request and result excerpt from a ready response: