The same judge behind the web form is available as a JSON endpoint and as an MCP tool for AI agents. Every call costs one credit; a failed judge run refunds it.
Create a key under API keys. Keys look like qh_ + 32 characters. Send them as X-API-Key on the REST endpoint and as Authorization: Bearer on MCP. Keys are stored hashed and can be revoked at any time.
POST https://quant-harness.vercel.app/api/v1/verdict — JSON in, verdict out. The request body is exactly what harness.service accepts; only returns is required, freq defaults to 1d.
# body.json: {"returns": [[0.001, -0.002, ...], ...], "freq": "1d", "title": "strategy A"}
# rows = configs, >= 100 periods each; optional: "trades", "n_trials", "costs_bps", "thresholds", "cpcv"
curl -s -X POST https://quant-harness.vercel.app/api/v1/verdict \
-H "X-API-Key: qh_YOUR_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @body.json
# -> {"id": "...", "verdict": "PASS" | "KILL", "checks": {...}, "metrics": {...}, "report_url": "...", "credits_left": 4}{
"returns": [
[
0.0012,
-0.0031,
0.0007,
…
],
[
0.0004,
0.001,
-0.0002,
…
]
],
"trades": [
[
1,
0,
1,
…
],
[
1,
1,
0,
…
]
],
"freq": "1d",
"start": "2024-01-02T00:00:00Z",
"n_trials": 120,
"costs_bps": 5,
"thresholds": {
"min_trades": 200,
"min_oos_sharpe": 0.7,
"max_drawdown": 0.2,
"min_dsr": 0.95
},
"cpcv": {
"n_groups": 10,
"k_test": 2,
"purge": 1,
"embargo": 5
},
"title": "momentum family, 2021-2025"
}{
"id": "0c4b9c0e-…",
"verdict": "KILL",
"checks": {
"trades_ok": true,
"oos_sharpe_ok": false,
"drawdown_ok": true,
"dsr_ok": false
},
"metrics": {
"oos_sharpe_annualized": -0.169,
"worst_path_sharpe_annualized": -0.983,
"oos_max_drawdown": 0.215,
"psr_vs_zero": 0.414,
"deflated_sharpe_ratio": 0.055,
"pbo": 0.429,
"n_paths": 9,
"n_configs_tried": 120,
"oos_bars": 600,
"approx_oos_trades": 200,
"ann_factor": 365
},
"assumptions": [
"costs_bps=5.0: returns treated as gross; 5.0 bps x trades subtracted on every bar before judging."
],
"evidence": {
"confidence": 0.95,
"n_trials": 6,
"n_bars": 600,
"ann_factor": 365,
"noise_benchmark_sharpe_annualized": 1.078,
"min_detectable_sharpe_annualized": 2.363,
"observed_sharpe_annualized": -0.169,
"bars_needed": null,
"years_needed": null,
"status": "below_noise",
"note": "The observed annualized Sharpe -0.17 does not exceed the expected best of 6 trials (1.08); no sample length would certify it at DSR >= 0.95."
},
"report_url": "https://quant-harness.vercel.app/reports/0c4b9c0e-…?t=…",
"credits_left": 4
}report_url opens without a login (the share token is in the URL). verdict is PASS only when all four checks are true.
evidence (0.4.0+) is the evidence budget: the smallest annualized Sharpe this sample size and trial count could certify, the bars needed at the observed Sharpe (null = unreachable) and a status of sufficient, insufficient or below_noise; it explains the Deflated Sharpe gate and never changes the verdict.
import json, urllib.request
import numpy as np
APP = "https://quant-harness.vercel.app"
KEY = "qh_YOUR_API_KEY"
R = np.random.default_rng(0).normal(0.0, 0.01, (6, 600)) # 6 configs x 600 daily bars of noise
T = np.zeros_like(R); T[:, ::3] = 1 # one trade every 3 bars
body = {"returns": R.tolist(), "trades": T.tolist(), "freq": "1d",
"costs_bps": 5.0, "n_trials": 6, "title": "noise check"}
req = urllib.request.Request(
f"{APP}/api/v1/verdict",
data=json.dumps(body).encode(),
headers={"X-API-Key": KEY, "Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=180) as resp: # judge can take up to 2 minutes
out = json.load(resp)
print(out["verdict"], out["metrics"]["deflated_sharpe_ratio"], out["report_url"])
# 401/402/413/422/502 raise urllib.error.HTTPError; the body is JSON with "error" and "message".| Field | Type | Meaning |
|---|---|---|
returns | number[][] or number[] | Per-period fractional returns (0.01 = 1%), rows = configs. At least 100 periods; at most 200 configs × 50,000 periods, body ≤ 20 MB. |
trades | same shape | Position changes per bar. Enables the cost-sensitivity table; otherwise assume_trades_per_bar trades per bar are assumed. |
assume_trades_per_bar | number ≥ 0 | Turnover assumed on every bar when trades is omitted (default 1.0). Ignored when trades is supplied. |
freq | string | Bar spacing: 1m, 15m, 1h, 4h, 1d, 1w (m = minutes). Drives annualization. |
start | ISO date | Timestamp of the first bar (UTC). |
n_trials | int | Total configs you tried, if more than the rows sent. The Deflated Sharpe is corrected by max(n_trials, rows). |
costs_bps | number | Round-trip cost per unit of turnover; returns are then treated as gross. |
thresholds, cpcv | object | Gates and CPCV geometry; defaults are the calibrated ones shown above. |
title | string ≤ 200 | Label for your history and the report. |
| Status | error | When |
|---|---|---|
| 401 | unauthorized | Missing, malformed or revoked key. |
| 402 | insufficient_credits | Balance is zero; the body carries buy_url. Nothing is charged. |
| 413 | too_many_configs / too_many_periods | Over the limits above. Checked before any credit is spent. |
| 413 | body_too_large | Body over 20 MB. Not charged. |
| 422 | invalid_input / invalid_json | Ragged matrix, NaN/inf, fewer than 100 periods, bad freq or start, unknown fields; or a body that is not JSON. Not charged. |
| 422 / 413 | judge_rejected | The judge itself refused the input (e.g. a zero-variance series); detail carries its reasons. The credit is refunded. |
| 502 | judge_unavailable | The judge timed out or failed. The credit is refunded automatically. |
A credit is deducted only when the judge returns a verdict. Successful responses include credits_left.
https://quant-harness.vercel.app/api/mcp is a Streamable HTTP MCP server with one tool, judge_returns, taking the same fields as the REST body and returning the verdict, metrics and report_url. Authenticate with Authorization: Bearer qh_…. Each tool call spends one credit.
Cursor: .cursor/mcp.json. Claude Code: .mcp.json in the project, or the command below.
{
"mcpServers": {
"quant-harness": {
"url": "https://quant-harness.vercel.app/api/mcp",
"headers": {
"Authorization": "Bearer qh_YOUR_API_KEY"
}
}
}
}claude mcp add --transport http quant-harness https://quant-harness.vercel.app/api/mcp \
--header "Authorization: Bearer qh_YOUR_API_KEY"Claude Desktop launches local processes, so bridge the remote server with mcp-remote in claude_desktop_config.json.
{
"mcpServers": {
"quant-harness": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://quant-harness.vercel.app/api/mcp",
"--header",
"Authorization: Bearer qh_YOUR_API_KEY"
]
}
}
}Then ask the agent: “Judge these returns with quant-harness: freq 1d, 40 trials, 5 bps costs.” It calls judge_returns and gets the verdict plus a report link it can show you.
harness.audit.audit_returns), CLI qh-audit and the FastAPI service this product wraps.quant-harness is statistical software. A PASS means the supplied numbers cleared pre-registered gates under the stated assumptions; it is not investment advice and says nothing about future returns.