Skip to content
Hosted in Europe. Your test data is stored in the European Union.Hosted and stored in EuropeLearn more

Manage your tests through the API.

The management API, the MCP server for assistants, Slack alerts and signed webhooks, and the server helper package.

Manage Probant from a server or an assistant

The management API does from a server what the interface does on screen: list, create, and edit drafts, launch, pause, resume, stop with a decision, roll out a variant, manage metrics, and read results. Same rules, same messages: a change the editor rejects is rejected by the API too, and a test plan is recalculated at launch, just as on the “Plan and launch” screen.

The same operations are available as tools on an MCP server, so an assistant can manage your tests.

Authentication and roles

Create a management key in Settings, “Integrations” tab (workspace owner). Send it in the Authorization: Bearer sk_… header, from a server or an assistant, never from a web page: a request that carries an origin is rejected.

  • Read: list tests and metrics, read a test and its results.
  • Read and write: also create, edit, launch, pause, stop, and roll out.

A key never does more than the person who created it: if their role is lowered, so is the key’s, and the key stops working if they leave the workspace. Back-office secret keys only work with the orders API.

Limit: 120 requests per minute per key, 240 per IP address; beyond that, a 429 response with Retry-After.

OpenAPI 3.1 description: GET /api/v1/openapi.json, for a client generator or an API testing tool.

Reference

GET /api/v1/experiments List tests

The workspace’s tests, newest first, with their status and exposed visitors. Key: any management key (read access is enough).

  • ?status=: Only return tests with this status.

POST /api/v1/experiments Create a draft

Same rules as the editor: two to five variants, the original “a” with no changes, and sanitized changes (one rejected change rejects the whole draft). Key: “read and write”, or a custom role that grants the “Prepare tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
namestringTest name.
hypothesis · optionalstring“If…, then…, because…”.
zone · optionalstringSite zone: in exclusive mode (the default), two tests in the same zone don’t see the same visitor.
urlPattern · optionalstringTargeted pages: “/products/*”, or a full URL.
devices · optional"mobile" | "tablet" | "desktop"[]Targeted devices; empty or omitted: all.
previewUrl · optionalstringPublic page the variants were built on.
variantsobject[]

GET /api/v1/experiments/{key} Get a test

Hypothesis, targeting, variants and their changes, primary metric, the test plan locked at launch, and the latest decision. Key: any management key (read access is enough).

PATCH /api/v1/experiments/{key} Update a draft

Omitted fields keep their value. A launched test can no longer be changed (409). Key: “read and write”, or a custom role that grants the “Prepare tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
name · optionalstringTest name.
hypothesis · optionalstring“If…, then…, because…”.
zone · optionalstringSite zone: in exclusive mode (the default), two tests in the same zone don’t see the same visitor.
urlPattern · optionalstringTargeted pages: “/products/*”, or a full URL.
devices · optional"mobile" | "tablet" | "desktop"[]Targeted devices; empty or omitted: all.
previewUrl · optionalstringPublic page the variants were built on.
variants · optionalobject[]

POST /api/v1/experiments/{key}/launch Launch a draft

The engine recalculates the test plan (visitors per variant, duration, checkpoints) from the declared traffic, as on the “Plan and duration” screen. Rejected when the plan’s quota is reached (402) or when the zone has no room left (409). An optimization test draft launches with POST /experiments/{key}/bandit/launch (409 here). guardrails: keys of the guardrail metrics, locked with the test plan, following the screen’s rules; a switchback or geo test rejects speed and retention metrics and, for geo tests, amount metrics (422). Such a test also launches here: looks sets its checkpoints (one reading, at the end, by default); dailyVisitors is still required but isn’t used (nothing is computed in visitors). Key: “read and write”, or a custom role that grants the “Launch and stop tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
metric · optionalstringKey of the primary metric; if omitted, “commande” (order), created if needed, as in the interface.
baselineRate · optionalnumberThe metric’s baseline rate (0.032 for 3.2%), for a rate metric.
baselineMean · optionalnumberBaseline mean per visitor, for a mean metric.
baselineSd · optionalnumberBaseline standard deviation per visitor, for a mean metric.
dailyVisitorsnumberMeasurable visitors per day on the targeted pages.
mde · optional0.02 | 0.05 | 0.08 | 0.1 | 0.15 | 0.2Relative effect to detect (0.05 = 5%); required for the “superiority” objective (the default).
looks · optional1 | 2 | 3 | 4 | 5Checkpoints; 3 by default (1, at the end, for a switchback or geo test). An objective with a margin always has a single look, at the end.
objective · optional"superiority" | "non-inferiority" | "equivalence"Decision objective: “superiority” (the default) looks for a better variant; “non-inferiority” shows it isn’t worse than the original by more than the margin; “equivalence” shows the difference stays within ± the margin (TOST). Never a “winner” with a margin.
margin · optional0.01 | 0.02 | 0.03 | 0.05 | 0.1Relative margin m (0.02 = 2%), required with “non-inferiority” or “equivalence”; none is set by default. Sealed with the test plan.
share · optional1 | 0.75 | 0.5 | 0.25 | 0.1Share of the zone’s traffic; 1 by default.
overlap · optional"exclusive" | "parallel"Concurrent tests: “exclusive” (the default) shares the zone without overlap; “parallel” gives the test its own layer, on all the traffic of its pages, and its interactions with other tests are checked.
after · optionalstring[]Keys of launched tests this one lets go first: their visitors don’t see it, and the test plan counts the share left for it. Required (all launched tests) if the workspace allows one test per visitor.
group · optionalstringNamed exclusion group, in exclusive mode: the test shares this group’s traffic (tests from different zones can join it); if omitted, its zone’s traffic.
guardrails · optionalstring[]Keys of guardrail metrics, each with its direction and limit (see /metrics): a proven degradation beyond the limit stops the test on its own, with the reason. The primary metric can’t also be a guardrail. Switchback or geo test: no speed or retention metrics, and only rates for a geo test.
interactions · optionalbooleanMultivariate test: announce second-order interactions in the test plan (counted in the Holm correction; requires many more visitors). Ignored for an A/B test.

POST /api/v1/experiments/{key}/pause Pause

Visitors see the original; the test resumes on the same traffic slice with the same assignments. Key: “read and write”, or a custom role that grants the “Launch and stop tests” permission; never more than the permissions of the person who created the key.

POST /api/v1/experiments/{key}/resume Resume

Resumes a paused test. Key: “read and write”, or a custom role that grants the “Launch and stop tests” permission; never more than the permissions of the person who created the key.

POST /api/v1/experiments/{key}/stop Stop with a decision

The decision and the stop are recorded together. A stopped test can’t restart. Key: “read and write”, or a custom role that grants the “Launch and stop tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
decision"ship" | "keep-control" | "abandon"Roll out a variant, keep the original, or abandon.
variant · optionalstringVariant to roll out, with “ship”.
note · optionalstringWhy: recorded with the decision.

POST /api/v1/experiments/{key}/rollout Roll out a variant

Gradual rollout in steps, with the workspace’s guardrails monitored continuously and automatic rollback. Records the decision to roll out. Key: “read and write”, or a custom role that grants the “Roll out the winning variant” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
variantstringVariant to roll out (not the original).
steps · optionalnumber[]Increasing steps as fractions, the last one at 1; 0.25, 0.5, 1 by default.

GET /api/v1/experiments/{key}/results Get results

Per metric (primary, then secondary metrics and workspace guardrails) and per variant: visitors, converters, and sums, then the relative difference, range, p-value, and verdict computed by the engine. decisionAuthorized says whether the test plan allows a conclusion (sample reached or boundary crossed). When the workspace has turned on the Bayesian reading, bayesian adds it for the primary metric (BayesianReading schema): probability that the variant does better, credible interval, and expected loss, with a zero-centered prior drawn from the workspace’s stopped tests; it doesn’t decide anything. A windowed metric (retention, or a CRM conversion: window.kind “deferred”) only counts visitors whose window has closed; window.pendingVisitors says how many are still waiting. When the traffic split is abnormal (srm.isMismatch), results are masked as in the app: effect and value are null, the verdict is blocked, masked gives the reason, and split includes the diagnosis (visitors per variant, browser and device, Holm-corrected checks, and the segment at fault when the imbalance is concentrated there). Counts are kept. Key: any management key (read access is enough).

  • ?format=: “csv”: the same report as CSV (“;” separator, decimal comma).

POST /api/v1/bandits Create an optimization test

Optimization test (multi-armed bandit) draft, with the same fields as a draft plus bandit (minimum share, learning, per-device shares). It maximizes conversions, or an amount per visitor, during the test; it doesn’t prove that one variant is better. Launch it with POST /experiments/{key}/bandit/launch, under the same rules as an A/B test. Key: “read and write”, or a custom role that grants the “Prepare tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
namestringTest name.
hypothesis · optionalstring“If…, then…, because…”.
zone · optionalstringSite zone: in exclusive mode (the default), two tests in the same zone don’t see the same visitor.
urlPattern · optionalstringTargeted pages: “/products/*”, or a full URL.
devices · optional"mobile" | "tablet" | "desktop"[]Targeted devices; empty or omitted: all.
previewUrl · optionalstringPublic page the variants were built on.
variantsobject[]
bandit · optionalobject

POST /api/v1/experiments/{key}/bandit Turn a draft into an optimization test

A draft’s type and settings; a launched test keeps its type (409). Key: “read and write”, or a custom role that grants the “Prepare tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
floor · optionalnumberMinimum share of each variant (0.1 = 10%, 10% by default), so learning continues on all of them; at most 1/K with K variants.
minVisitors · optionalintegerVisitors per variant, across all epochs, before the first reallocation (learning with equal shares); 1,000 by default.
notifyFloor · optionalbooleanAlert when a variant stays at its floor for seven days; true by default.
context · optionalobjectContextual bandit: “device” gives shares per device (mobile, tablet, desktop), each with its own learning and floor; null, the same shares for everyone (the default). The device is read in the browser: the server helper and feature flags only assign visitors from the first epoch.

POST /api/v1/experiments/{key}/bandit/launch Launch an optimization test

Launches an optimization draft: the metric to maximize (a rate, or an amount per visitor such as revenue, capped at the percentile set on the metric), the share of the zone, concurrent tests, and, if needed, its settings (otherwise the draft’s). Same rules as an A/B test launched through the API: rejected when the workspace requires another member to approve each launch (409; the request is made in Probant), when the plan’s quota is reached (402), when the zone has no room left, or for an A/B test (409). The first epoch, with equal shares, is sealed with the test plan. It maximizes conversions during the test; it doesn’t prove that one variant is better. Key: “read and write”, or a custom role that grants the “Launch and stop tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
metric · optionalstringKey of the metric to maximize: a conversion rate, or an amount per visitor (revenue), capped at the percentile set on the metric; if omitted, “commande” (order), created if needed, as in the interface.
share · optional1 | 0.75 | 0.5 | 0.25 | 0.1Share of the zone’s traffic; 1 by default.
overlap · optional"exclusive" | "parallel"Concurrent tests: “exclusive” (the default) shares the zone without overlap; “parallel” gives the test its own layer.
floor · optionalnumberMinimum share of each variant (0.1 = 10%, 10% by default), so learning continues on all of them; at most 1/K with K variants.
minVisitors · optionalintegerVisitors per variant, across all epochs, before the first reallocation (learning with equal shares); 1,000 by default.
notifyFloor · optionalbooleanAlert when a variant stays at its floor for seven days; true by default.
context · optionalobjectContextual bandit: “device” gives shares per device (mobile, tablet, desktop), each with its own learning and floor; null, the same shares for everyone (the default). The device is read in the browser: the server helper and feature flags only assign visitors from the first epoch.

GET /api/v1/experiments/{key}/bandit Get an optimization test

Dated epochs and their shares, the current share, each variant’s difference estimated epoch by epoch and then combined, an always-valid range, conversions gained compared with equal shares, and the traffic split checked per epoch. For a contextual bandit, the same reading per device (reading.byDevice), and the overall reading by epoch × device strata. Never a winner. A/B results (/results) for an optimization test return 409: their naive comparison would be biased. Key: any management key (read access is enough).

POST /api/v1/experiments/{key}/unit Choose what a draft randomizes

Visitors (A/B test), time slots (switchback: a price, stock) or geographic zones (French departments, regions, or countries: shipping fees). Departments require department-level geolocation, which isn’t always available; without it, 422 with the explanation. Exactly two versions for time slots and zones; a launched test keeps its unit (409). Launch in Probant, under the same rules as an A/B test. Key: “read and write”, or a custom role that grants the “Prepare tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
unit"visitor" | "switchback" | "zones"What gets randomized: `visitor` (A/B test), `switchback` (alternating time slots), or `zones` (geographic zones).
windowHours · optionalnumberSwitchback: length of a time slot, in hours (24 by default).
days · optionalnumberSwitchback: test duration, in days (28 by default).
burnInMinutes · optionalnumberSwitchback: start of each time slot left out of the reading after a switch, in minutes (30 by default).
level · optional"department" | "region" | "country"Geographic zones: French departments (“FR-75”, only if department-level geolocation is available; otherwise 422 with the explanation), ISO 3166-2 regions (the default), or countries.
weeks · optionalnumberGeographic zones: test duration, in weeks (4 by default).

GET /api/v1/experiments/{key}/unit Get a switchback or geo test

The schedule of time slots or the randomized zones, locked at launch (a department-level test also says where the department comes from, departmentSource); the state (draft, waiting, collecting, read); once there are enough pairs, the traffic-weighted paired difference, ranges (Student, Fieller for the relative effect), a permutation test, and the signature. No effect figure until the state is read. A/B results (/results) for such a test return 409: read per visitor, they would give ranges that are too narrow. guardrails: guardrails read at the latest checkpoint reached, on the same pairs (relative range at the checkpoint’s level, breached when the whole range exceeds the limit: the test stops on its own); decision: the latest recorded decision, automatic for a guardrail stop, with its reason in note. target: the target relative effect, the plan’s minimum detectable effect, locked at launch (null without history); reading.futility: at a checkpoint before the last, the chance of reaching a conclusion by the end if the true difference were this effect, and futile below the threshold. This is a recommendation: nothing stops on its own for futility. Key: any management key (read access is enough).

GET /api/v1/metrics List metrics

The workspace’s catalog: type, event or goal, role, and guardrail rule. Key: any management key (read access is enough).

POST /api/v1/metrics Create a metric

An event sent by probant.track(…), or a no-code goal (click, form submission, page visit). Key: “read and write”, or a custom role that grants the “Create metrics” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
namestring
key · optionalstringDerived from the name if omitted.
kind · optional"proportion" | "mean"Rate (proportion) or mean per visitor; a no-code goal is a rate.
eventName · optionalstringName passed to probant.track(…); defaults to the key.
valueField · optionalstringField summed for a mean; “value” by default.
goal · optionalobjectNo-code goal: click, form submission, or page visit.
role · optional"primary" | "secondary" | "guardrail"
description · optionalstring
direction · optional"increase-is-bad" | "decrease-is-bad"Direction that counts as a degradation, for a guardrail.
guardrailLimit · optionalnumberWorst tolerated degradation, as a fraction (0.05 = 5%).
retention · optionalobjectRetention metric: share of exposed visitors who come back, or send the event, between day N and day M after their first exposure. A visitor exposed less than M days ago is left out of the calculation. null: a regular metric.
deferred · optionalobjectCRM conversion (POST /api/v1/conversions, “eventName” key): qualified lead, closed deal. Rate, or amount per visitor (kind “mean”). A visitor exposed less than N days ago is left out of the calculation. null: a site metric.

GET /api/v1/metrics/{key} Get a metric

One metric from the catalog. Key: any management key (read access is enough).

PATCH /api/v1/metrics/{key} Update a metric

What a metric measures (type, event, goal) can no longer change once a launched test decides on it (409). Key: “read and write”, or a custom role that grants the “Edit and delete metrics” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
name · optionalstring
key · optionalstringDerived from the name if omitted.
kind · optional"proportion" | "mean"Rate (proportion) or mean per visitor; a no-code goal is a rate.
eventName · optionalstringName passed to probant.track(…); defaults to the key.
valueField · optionalstringField summed for a mean; “value” by default.
goal · optionalobjectNo-code goal: click, form submission, or page visit.
role · optional"primary" | "secondary" | "guardrail"
description · optionalstring
direction · optional"increase-is-bad" | "decrease-is-bad"Direction that counts as a degradation, for a guardrail.
guardrailLimit · optionalnumberWorst tolerated degradation, as a fraction (0.05 = 5%).
retention · optionalobjectRetention metric: share of exposed visitors who come back, or send the event, between day N and day M after their first exposure. A visitor exposed less than M days ago is left out of the calculation. null: a regular metric.
deferred · optionalobjectCRM conversion (POST /api/v1/conversions, “eventName” key): qualified lead, closed deal. Rate, or amount per visitor (kind “mean”). A visitor exposed less than N days ago is left out of the calculation. null: a site metric.

DELETE /api/v1/metrics/{key} Delete a metric

Rejected while a test uses it as its primary metric (409). Key: “read and write”, or a custom role that grants the “Edit and delete metrics” permission; never more than the permissions of the person who created the key.

POST /api/v1/multivariate-experiments Create a multivariate test

Multivariate test draft: 2 to 4 sections, each with 2 to 4 levels (the first is the original), and up to 16 combinations. Probant generates the full factorial design; each combination is a regular variant whose changes combine those of its levels. Same checks and sanitizing as the editor. Launch it with POST /experiments/{key}/launch: the factorial plan is computed from the workspace’s measured traffic (declared traffic otherwise), with a single look at the end, and interactions announces second-order interactions. Key: “read and write”, or a custom role that grants the “Prepare tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
namestringTest name.
hypothesis · optionalstring“If…, then…, because…”.
zone · optionalstringSite zone: in exclusive mode (the default), two tests in the same zone don’t see the same visitor.
urlPattern · optionalstringTargeted pages: “/products/*”, or a full URL.
sectionsobject[]2 to 4 sections; up to 16 combinations, generated by Probant, each served as a variant.

GET /api/v1/experiments/{key}/sections Get a multivariate test by section

Main effects of each section (marginal mean of each level, difference from the original, range), second-order interactions (difference of differences), Holm-adjusted p-values on the comparisons announced in the test plan, and a recommendation. Without a conclusive interaction, the recommendation follows the main effects. 409 for an A/B test. Key: any management key (read access is enough).

GET /api/v1/experiments/{key}/integration Build a variant into the site

A variant’s changes rewritten as code the site keeps, without the Probant script: a CSS stylesheet (styles, states, hidden elements, variant CSS) and a standalone, idempotent script (text, sanitized content, blocks, attributes, images, moves, and variant JavaScript as is). The script reproduces the test’s targeting (pages, devices, audience); when the test only targeted some visitors, it also carries the stylesheet and applies it to them only (css is then null), and warnings lists the audience criteria it can’t check (country, attributes). Also includes the files to put in assets/ and the layout/theme.liquid snippet for Shopify, instructions, and what would be better rewritten in the theme (fragile selectors included). Without variant, the one from the latest “roll out” decision. Once the code is live, stop the test or roll its rollout back to the original. Key: “read and write”, or a custom role that grants the “Roll out the winning variant” permission; never more than the permissions of the person who created the key.

  • ?variant=: Key of the variant to build in (“b”); defaults to the one from the “roll out” decision.
  • ?format=: json (default), or a single file to save as is.

GET /api/v1/widgets List widget templates

Banner, countdown, pop-up, badge, reassurance, action bar, delivery date, social proof, promo code, free shipping: name, description, behavior loaded on demand (countdown, pop-up, delivery date, social proof, promo code, free shipping; an action bar that clicks a button on the page), parameters (JSON schema), and a complete example. Key: any management key (read access is enough).

POST /api/v1/widgets Generate a widget’s changes

A widget’s regular changes (an insert with sanitized HTML, then style changes applied through the CSSOM), with its warnings (contrast, past deadline) and its readable description. Nothing is saved: add them to a variant’s changes. Key: any management key (read access is enough).

FieldTypeDescription
widgetobjectTemplate (“banner”, “countdown”, “popin”, “badge”, “reassurance”, “actionbar”, “delivery”, “proof”, “promo”, “freeshipping”, “survey”) and its parameters; see GET /api/v1/widgets.
target · optionalobjectElement near which to insert a widget in the page (“inline” placement, badge, reassurance, social proof): a stable CSS selector and a position.

POST /api/v1/experiments/{key}/widgets Add a widget to a draft

Generates the widget and adds it to the end of the variant’s changes, under a draft’s rules (sanitization, up to 50 changes per variant, audit log). A launched test can no longer be changed (409). Key: “read and write”, or a custom role that grants the “Prepare tests” permission; never more than the permissions of the person who created the key.

FieldTypeDescription
widgetobjectTemplate (“banner”, “countdown”, “popin”, “badge”, “reassurance”, “actionbar”, “delivery”, “proof”, “promo”, “freeshipping”, “survey”) and its parameters; see GET /api/v1/widgets.
target · optionalobjectElement near which to insert a widget in the page (“inline” placement, badge, reassurance, social proof): a stable CSS selector and a position.
variantstringVariant that receives the widget (not the original, “a”).

Examples

Create a draft

Create a draft
curl -X POST https://getprobant.com/api/v1/experiments \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Estimated delivery date",
    "zone": "Product page",
    "urlPattern": "/products/*",
    "variants": [
      { "key": "a", "name": "Original", "changes": [] },
      { "key": "b", "name": "Estimated date", "changes": [
        { "kind": "insert", "selector": "[data-add-to-cart]", "position": "afterend",
          "html": "<p>Delivered within 2 business days</p>" }
      ] }
    ]
  }'

The first variant is the original, a, with no changes. Changes (insert, text, style, attribute, hide) go through the same sanitization as the editor’s.

Launch

Launch a test
curl -X POST https://getprobant.com/api/v1/experiments/estimated-delivery-date/launch \
  -H "Authorization: Bearer sk_…" \
  -H "Content-Type: application/json" \
  -d '{ "metric": "commande", "baselineRate": 0.032, "dailyVisitors": 4000, "mde": 0.05 }'

The declared baseline rate and traffic are used to calculate the test plan (visitors per variant, duration, checkpoints); a ready-made plan sent with the request isn’t used.

Read results

Read results
curl https://getprobant.com/api/v1/experiments/estimated-delivery-date/results \
  -H "Authorization: Bearer sk_…"

# The same report as CSV (“;” separator, decimal comma)
curl "https://getprobant.com/api/v1/experiments/estimated-delivery-date/results?format=csv" \
  -H "Authorization: Bearer sk_…" -o report.csv

decisionAuthorized is sample when the planned sample size is reached, look when a checkpoint boundary is crossed, and null otherwise: before that, a confidence interval that excludes 0 doesn’t allow a conclusion.

Responses and errors

CodeMeaning
200OK.
201Created.
202Accepted: feature flag change submitted as a request, or exposures received.
400Unreadable body.
401Missing, unknown, or revoked key.
402Plan quota reached: no new tests start this month.
403The key lacks this permission (read-only, orders key, or its creator left the workspace), or the call came from a web page.
404Unknown test, metric, or feature flag in this workspace.
409Conflict with the current state (test already launched or stopped, zone full, key taken…).
413Body too large (512 KB max).
422Input rejected by the interface’s rules; the message says which one.
429Rate limit: 120 requests per minute per key.
503Service temporarily unavailable.

MCP server

Address: /api/v1/mcp, HTTP transport (“Streamable HTTP”), stateless. There are two ways to connect.

Without a key, through OAuth: in Claude, ChatGPT, or any assistant that connects this way, just add the address. The assistant opens Probant, where you sign in and choose the workspace and the access level (read, or read and write), never more than your own role. The assistant then appears in Settings → Integrations, where you can disconnect it. With Claude Code:

Add the MCP server to Claude Code with OAuth
claude mcp add --transport http probant https://getprobant.com/api/v1/mcp

With a management key, for a script or an assistant that doesn’t support OAuth:

Add the MCP server to Claude Code
claude mcp add --transport http probant https://getprobant.com/api/v1/mcp \
  --header "Authorization: Bearer sk_…"

For a client configured with a file:

MCP configuration file
{
  "mcpServers": {
    "probant": {
      "type": "http",
      "url": "https://getprobant.com/api/v1/mcp",
      "headers": { "Authorization": "Bearer sk_…" }
    }
  }
}

Launching, stopping, and rolling out can’t be undone. Called without confirm: true, these tools do nothing and return a preview (plan and duration, decision, steps and guardrails) to show the person; each one asks the assistant to call it again with confirm: true only after their explicit approval.

ToolWhat it does
list_experimentsThe workspace’s tests, newest first, with their status and exposed visitors.
get_experimentOne test: hypothesis, targeting, variants and their changes, metric, test plan, and latest decision.
get_resultsA test’s results, computed by the statistics engine: visitors and conversions per variant, relative difference and range, verdict, traffic split check (SRM), and checkpoints. decisionAuthorized says whether the test plan allows a conclusion. If the workspace has turned it on, bayesian adds a Bayesian reading (probability of doing better, credible interval, expected loss) that doesn’t decide anything. A windowed metric (retention, or a CRM conversion: window.kind “deferred”) only counts visitors whose window has closed; window.pendingVisitors says how many are still waiting. Abnormal traffic split (srm.isMismatch): differences are masked (masked), and split gives the diagnosis by browser and device; don’t conclude anything until the cause is found.
get_cohortAfter a test stops, its exposed visitors are followed at day 30, 90, 180, and 365: net revenue collected, MRR, and still subscribed, with each variant’s difference and its simultaneous range. verdict says whether the decision made is reversed (reversed), holds (confirmed), is neither confirmed nor contradicted (consistent), or is waiting for a reading to close (pending). This is a descriptive estimate, not a new decision: never present it as a new winner.
get_integration_codeCode that reproduces a variant without the Probant script: a CSS stylesheet, a standalone and idempotent script, assets/ files and a layout/theme.liquid snippet for Shopify, instructions, and changes that would be better rewritten in the theme. The script reproduces the test’s targeting (pages, devices, audience); warnings name the audience criteria it can’t check (country, attributes): tell the person. Without variant, the one from the “roll out” decision. Hand-written JavaScript is copied as is: tell the person. Remind them to stop the test (or roll its rollout back to the original) once the code is live.
create_draftCreates a draft test, as from the editor: variants (“a” = the original with no changes), sanitized changes, and targeting. A variant can carry a CSS stylesheet and a JavaScript script written by the person (kind “css” or “js”): don’t write JavaScript yourself; reuse the code they wrote and reviewed. Nothing is shown to visitors before launch.
update_draftUpdates a draft; omitted fields keep their value. A launched test can no longer be changed.
list_widget_templatesBanner, countdown, pop-up, badge, reassurance, action bar, delivery date, social proof, promo code, free shipping, and survey: expected parameters (JSON schema), a full example, and whether the template has a behavior (countdown, pop-up, delivery date, social proof, promo code, free shipping, and survey), loaded on demand on the site.
render_widgetA widget’s changes (an insert with sanitized HTML, then its styles), with its warnings (contrast, past deadline) and a readable description. Saves nothing: add them to a variant’s changes with update_draft, or use add_widget_to_draft.
add_widget_to_draftAdds a widget at the end of the changes of a draft variant (not the original), with the editor’s rules. A launched test can no longer be changed. Show the widget’s description to the person.
launch_experimentLaunches a draft: the test plan (visitors, duration) is recalculated from the declared traffic, and visitors start seeing the variants (an optimization test launches with launch_bandit). Use the “non-inferiority” or “equivalence” objective with a margin (margin) to show that a variant isn’t worse, or is equivalent: a single look, never a winner. Irreversible: without confirm: true, it only returns the test plan. Requires confirmation.
pause_experimentPauses a running test: visitors see the original. It resumes on the same traffic slice with the same assignments.
resume_experimentResumes a paused test.
stop_experimentStops a test and records the decision: roll out a variant (ship), keep the original (keep-control), or abandon. A stopped test can’t restart. Irreversible: without confirm: true, it does nothing. Requires confirmation.
deploy_variantGradually rolls out a variant of a running or stopped test (steps of 25, 50, and 100% by default), with guardrails monitored and automatic rollback. Records the decision to roll out. Irreversible: without confirm: true, it only returns the steps and guardrails. Requires confirmation.
create_banditCreates a draft optimization test (multi-armed bandit): the same fields as a draft, plus the minimum share per variant, the equal-share learning phase, and, if needed, per-device shares (context: "device"). It maximizes conversions (or an amount per visitor) during the test (short promotion, headline); it doesn’t prove that one variant is better. Launch it with launch_bandit.
launch_banditLaunches an optimization draft: the metric to maximize (a rate, or an amount per visitor such as revenue), the share of the zone, and, if needed, its settings (otherwise the draft’s). Same rules as an A/B test: approval by another member when the workspace requires it, quota, and room in the zone. Irreversible: without confirm: true, it only returns what would be launched. Requires confirmation.
get_banditDated epochs and their shares, each variant’s current share, each variant’s difference from the original estimated epoch by epoch and then combined (the naive comparison is biased when shares change), an always-valid range, conversions (or amount) gained compared with equal shares, and the traffic split checked per epoch. Never declare a winner: conclusion is “better” or “worse” only if the range excludes zero, and it remains an indication to confirm with an A/B test.
get_unit_testA test whose randomized unit is the time slot (switchback: a price, stock) or the geographic zone (French departments, regions, countries): the schedule or zones locked at launch, the state, then the paired difference, ranges, and a permutation test. Until the state is “read”, there’s no effect figure: don’t conclude anything. “winner” or “loser” only if the signature says so. A department-level test carries `departmentSource`: if `available` is false, no one is assigned, and `explanation` says why. Guardrails (`guardrails`) are read at the latest checkpoint reached: “breached” stops the test on its own, and `decision.automatic` says so, with the reason in `decision.note`.
list_metricsThe workspace’s metrics: type, event, role (primary, secondary, guardrail), and guardrail rule.
create_metricCreates a metric: an event sent by probant.track(…), a no-code goal (click, form, page), or a CRM conversion (deferred.windowDays: qualified lead, closed deal).
update_metricUpdates a metric, identified by metricKey. What it measures can no longer change once a launched test decides on it.
list_flagsThe workspace’s feature flags: state (in preparation, on, off), type, last change, and pending request.
get_flagOne feature flag: variations, “off” value, specific IDs, rules, default rule, and pending request.
create_flagCreates a feature flag in preparation, served to no one until it’s turned on.
update_flagUpdates the name, description, or full definition. A change to what’s served is applied, or submitted as a request if the workspace requires approval. A rollout percentage can only go up.
activate_flagTurns on a feature flag: its rules decide what visitors see. Without confirm: true, it only returns what would be served. Requires confirmation.
kill_flagEmergency kill switch: the “off” value is served everywhere within 60 s. Without confirm: true, it does nothing. Requires confirmation.
list_audiencesAudiences pushed by the workspace’s CDP: key (the one used by the “imported audience” criterion), name, source, member count, and last update received. Never the members themselves.
get_audienceAn imported audience’s name, source, size (member count), and the date of the last update received.
create_audienceCreates an empty audience that waits for members sent by the CDP (API or Segment). Its key is the CDP’s key.
rename_audienceChanges the name shown in Probant; the key, which tests target, doesn’t change.
delete_audienceDeletes the audience and all its members; rejected while a test that hasn’t stopped or a personalization that hasn’t ended targets it. Irreversible: without confirm: true, it does nothing. Requires confirmation.
create_multivariate_testCreates a draft multivariate test: 2 to 4 sections (headline, photo, badge…), each with 2 to 4 levels where the first is the original, and up to 16 combinations. Probant generates the combinations, served as variants. Two levels from different sections must not change the same element: launch rejects it. Launch it with launch_experiment (interactions: true to announce interactions).
get_sectionsMain effects of each section, second-order interactions, and the recommendation for a multivariate test, Holm-corrected on the announced comparisons. Don’t declare a best combination beyond the recommendation: without a conclusive interaction, it follows the main effects.
list_automationsThe workspace’s automations (conversion drop, post-launch check, overdue test, weekly report): on or off, settings, and the latest run and its findings.
run_automationRuns an automation now, like the daily morning task, and returns its findings (figures computed by the engine). Without send, nothing is sent; with send: true, justified alerts go to the workspace’s destinations, once per finding.
configure_automationTurns an automation on or off, or changes its settings. When it’s turned on, if no destination receives its alerts yet, all of the workspace’s destinations subscribe to it.
analyze_pageReads a public page like the editor does (no cookies, public hosts only) and returns its summary: title, headings, buttons, forms, collapsed sections, and markers present or missing (shipping, reviews, payment…), along with the workspace’s zones, themes, and ideas already noted. The basis for add_ideas.
add_ideasSaves ideas as “To evaluate”: title, zone, themes, effort (S, M, L), observation (what the page shows, quoted in “”), hypothesis (“If…, then…, because…”). Don’t include any gain or duration figures: an idea that states one is rejected, and Probant returns, for each idea, the expected effect and the test duration computed by its engine.

Alerts: Slack and signed webhooks

In Settings, “Integrations” tab, the owner adds a Slack incoming webhook or an HTTPS address to receive alerts, and chooses which ones. Each alert is sent only once.

  • experiment.decision: The test plan allows a conclusion: planned sample size reached or a checkpoint boundary crossed.
  • experiment.srm: Visitors aren’t split between variants as planned (SRM). Results are on hold.
  • rollout.rolled_back: A rollout went back to the original on its own because a guardrail exceeded its limit.
  • rollout.advanced: A rollout set to advance automatically moved to the next step: minimum duration elapsed with no guardrail crossed and, for 100%, every range within its limit.
  • quota.reached: This month’s tested visitors reached the plan quota. No new test can start.
  • experiment.interaction: Two overlapping tests interact: the effect of one changes with the variant of the other.
  • experiment.fading: The lift of a running test weakens clearly after the first days. A novelty effect may be inflating the result.
  • flag.killed: A feature flag was turned off in an emergency. Its “off” value is served everywhere at the next SDK refresh.
  • bandit.floor: In an optimization test, a variant has received only its minimum share for seven days. Consider stopping it or switching to a rollout.
  • experiment.display: A change in a running test isn’t applied for part of the exposed visitors, from day one (wrong selector, different page) or after a site change (theme update, edited page).
  • automation.drop: Automation: yesterday, a conversion rate or tracked traffic fell more than the previous week’s variation explains.
  • automation.launch-check: Automation: one day after a test launches, it checks exposures, traffic split, how each variant displays, and conversions.
  • automation.stale: Automation: a running test has passed the duration in its test plan without reaching its planned sample size.
  • automation.report: Automation: every week, your running tests, their progress, and the decisions of the past seven days.
  • automation.checkpoint: Automation: a running test reaches a checkpoint in its test plan. The alert says whether the boundary is crossed (you can conclude) or the test should continue.
  • automation.guardrails: Automation: every morning, the guardrails of running rollouts are checked at the current step. It alerts you when they’re shown to be within limits, or when one leans the wrong way.
  • automation.segment-srm: Automation: the visitor split between variants departs from the plan for a device, new or returning visitors, or a traffic source, after correcting for multiple comparisons.
  • automation.quota: Automation: this month’s tested visitors pass the share of the plan you chose, before the quota blocks new tests.
  • automation.flag-debt: Automation: once a week, the feature flags your code can drop (same value for everyone, turned off, test ended, no longer evaluated).
  • automation.survey: Automation: every morning, the number of open-ended answers your test surveys received the day before, to read in Probant (the alert contains no answer text).

A webhook receives a JSON POST, signed with the secret shown once when you add the destination:

Example alert received by a webhook
POST https://example.com/webhooks/probant
Content-Type: application/json
Probant-Event: experiment.decision
Probant-Delivery: 0b6f…
Probant-Signature: t=1794299400,v1=5d1c…

{
  "id": "0b6f…",
  "type": "experiment.decision",
  "occurredAt": "2026-11-10T08:30:00.000Z",
  "workspace": { "slug": "maison-lune", "name": "Maison Lune" },
  "title": "“Estimated delivery date”: B beats the original.",
  "text": "Estimated lift +6.1% on “Order”…",
  "url": "https://getprobant.com/experiences/estimated-delivery-date",
  "data": { "experiment": { "key": "…", "name": "…" }, "verdict": "winner", "effect": { "relative": 0.061, "lower": 0.012, "upper": 0.11 } }
}

The signature is an HMAC-SHA256 of “timestamp.raw body.” Verify it in constant time, and reject a timestamp older than 5 minutes:

Verify an alert’s signature
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyProbant(secret: string, header: string, rawBody: string): boolean {
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // too old: replayed?
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(expected, given);
}

Probant sends only to public HTTPS addresses, doesn’t follow redirects, and waits at most 5 seconds for a response. A failed delivery isn’t retried: the “Integrations” tab shows the error, and “Send a test” checks the destination.

Server helper package

Server-side assignment (assignRequest) is available as a dependency-free package, @probant/edge, for Next.js, Cloudflare Workers, or any server that speaks fetch. Neither this package nor its PHP, Python, Ruby, and Go equivalents are published on their registries yet: copy the server helper into your project, as described in the installation documentation.

Next.js proxy example with the package
// proxy.ts (Next.js 16), at the root of your site
import { NextResponse, type NextRequest } from "next/server";
import { assignRequest, assignedVariants, createConfigLoader } from "@probant/edge";

const loadConfig = createConfigLoader({ key: process.env.NEXT_PUBLIC_PROBANT_KEY!, endpoint: "https://getprobant.com" });

export async function proxy(request: NextRequest) {
  const config = await loadConfig();
  if (!config) return NextResponse.next();
  const { setCookie, assignments } = assignRequest({
    cookieHeader: request.headers.get("cookie"),
    url: request.url,
    userAgent: request.headers.get("user-agent"),
    config,
    consent: request.cookies.get("consent")?.value === "yes",
  });
  const headers = new Headers(request.headers);
  headers.set("x-probant-variants", JSON.stringify(assignedVariants(assignments)));
  const response = NextResponse.next({ request: { headers } });
  if (setCookie) response.headers.append("set-cookie", setCookie);
  return response;
}

createConfigLoader keeps the configuration in memory for one minute and serves the previous one if Probant doesn’t respond, so your site doesn’t wait for Probant.

The same helper exists in PHP, Python, Ruby, and Go, with no dependencies and the same configuration cache. Each package is checked against the reference implementation on thousands of shared cases: same visitor, same variant as the script.

Conclusive Or not yet…

Create your workspace, or explore the demo first with data from a fictional store.

  • Open demo, no sign-up
  • Free to start, no overage charges
  • Database and compute in Amsterdam, with Scaleway