Reward API + MCP
Developer reference for the randomized-canary reward loop — verdict contract, REST endpoints, MCP tools, cadence, and abstention semantics.
Developer reference for the randomized-canary reward loop. Auth by project key on every request.
Verdict contract
Every response from the Reward API — REST and MCP — uses the same shape:
{
"experiment_id": "3fa85f64-…",
"lift": 0.043, // treatment − control conversion rate; null while abstaining for power
"confidence": 0.97, // 1 − running-min p-value (always-valid; null while abstaining)
"ci": [0.011, 0.074], // always-valid confidence interval; null while abstaining
"verdict": "keep", // keep | rollback | iterate | abstain
"abstain_reason": null, // "power" | "non_identifiable" | "interacting" | null
"value_at_risk": -3100, // ±$/day when value_per_conversion is configured; else null
"cadence": "day", // "day" | "fast" | null (engine-selected)
"power_state": {
"clock": "open", // "open" = still running; "decided" = verdict is final
"decided": false
},
"guardrail_breaches": [] // names of guardrail metrics the treatment significantly harmed
}Verdict semantics
| Verdict | Meaning |
|---|---|
keep | mSPRT fired positive; treatment is significantly better. Ship it. |
rollback | mSPRT fired negative; treatment is significantly worse. Revert. |
iterate | Target lifted but a guardrail metric regressed. Revise the change. |
abstain | Not enough signal yet — or the regime is non-identifiable. See abstain_reason. |
abstain is first-class, not a failure. power means the mSPRT needs more traffic; non_identifiable means the design is aliased (VIF > 10); interacting means the observational regime is non-causal (Boundary B4 — a confident guess here would be a lie).
Cadence
The engine selects cadence from traffic and effect size:
- day — closes for ~20 % lift at 24 k sessions/day.
- fast — closes for ≥ 50 % lift at higher traffic.
Both cadences run concurrently when supported. Thin-traffic routes report abstain with abstain_reason: "power" until the required sessions accumulate.
REST API
Base URL: https://ingest.trueclara.com (or your TRUECLARA_API_URL).
Auth header: x-trueclara-project-key: <your-project-key>.
POST /v1/reward/register
Register a code change as a canary experiment.
Request
{
"deploy_id": "dep_abc123",
"treatment_commit": "a1b2c3d",
"target_metric": "checkout_conversion"
}All fields are optional — pass what your CI step has. target_metric can be a string (metric name) or an object { name, value_per_conversion } to enable ±$/day pricing.
Response — 201
{ "experiment_id": "3fa85f64-…" }GET /v1/reward/{experiment_id}
Poll the current verdict contract for an experiment.
Response — 200 — the verdict contract above.
Poll until power_state.decided === true (or verdict !== "abstain" with abstain_reason !== "power"). The mSPRT is always-valid: peeking does not inflate type-I error (measured 0.017 vs 0.45 naive under continuous peeking).
MCP server
The MCP server wraps the REST API as tools, for use with Claude Desktop, custom agents, or any MCP-compatible host.
Install
npx @trueclara/mcpRequired environment variables:
TRUECLARA_API_URL=https://ingest.trueclara.com
TRUECLARA_PROJECT_KEY=tc_sk_…
Connect (Claude Desktop claude_desktop_config.json)
{
"mcpServers": {
"trueclara": {
"command": "npx",
"args": ["@trueclara/mcp"],
"env": {
"TRUECLARA_API_URL": "https://ingest.trueclara.com",
"TRUECLARA_PROJECT_KEY": "tc_sk_…"
}
}
}
}Transport: stdio. The server name is trueclara-reward.
Tools
register_experiment
Register a change and get an experiment_id.
// Input
{
"deploy_id": "dep_abc123", // optional
"treatment_commit": "a1b2c3d", // optional
"target_metric": "checkout_conversion" // optional
}
// Output
{ "experiment_id": "3fa85f64-…" }get_verdict
Poll the current verdict contract.
// Input
{ "experiment_id": "3fa85f64-…" }
// Output — verdict contract (see above)subscribe
Single poll of the verdict contract — equivalent to get_verdict in this version. To implement poll-until-resolved, call subscribe repeatedly until power_state.decided === true or verdict !== "abstain" with abstain_reason !== "power".
// Input
{ "experiment_id": "3fa85f64-…" }
// Output — verdict contractTypical agent loop
# 1. Register after CI deploy
result = mcp.call("register_experiment", {
"treatment_commit": sha,
"target_metric": "checkout_conversion",
})
experiment_id = result["experiment_id"]
# 2. Poll until decided
while True:
contract = mcp.call("get_verdict", {"experiment_id": experiment_id})
if contract["power_state"]["decided"]:
break
time.sleep(3600) # day-cadence: check hourly
# 3. Act on verdict
match contract["verdict"]:
case "keep": ship()
case "rollback": revert()
case "iterate": file_issue(contract["guardrail_breaches"])
case "abstain": log(f"abstain: {contract['abstain_reason']}")Notes for integrators
abstainon every poll is expected while the mSPRT is accumulating traffic. Do not treat it as an error.guardrail_breachesis non-empty when the treatment helped the target metric but harmed a guardrail. The verdict is downgraded fromkeeptoiterateautomatically — do not ship akeepverdict that carries non-emptyguardrail_breaches(the API enforces this, but the field is surfaced for transparency).value_at_riskisnullunlessvalue_per_conversionis set on the target metric (in project settings → Value Routes). When set, it estimates±$/daybased on the control arm's baseline conversion rate and the measured lift.- Project key scope: the project key (
TRUECLARA_PROJECT_KEY) scopes all experiments to one project. Use a separate key per project.

