Skip to content

FreeNo signup or email

Check a backtest.
Keep the receipt.

Send the returns your backtest produced and find out whether the result could be luck: how much the Sharpe ratio shrinks once every version you tried is counted, how likely the backtest is to be overfit, and how long a live record must run to prove it. Each answer comes with a receipt anyone can recheck.

  1. Fastest

    From an AI assistant

    Add the hosted server to Claude, Cursor or VS Code. Nothing to install.

    claude mcp add --transport http canli https://canlicapital.com/mcp
    Setup for every assistant ↘
  2. Any language

    From your own code

    Get a free key, then call the API from Python, JavaScript or curl.

    Get a free API key ↘
  3. No code

    In the browser

    Type your numbers into the free calculators.

    Open the calculators ↗

Every response includes its scope and limits under our honesty pledge. Public record snapshots need no key.

Quickstart

Three steps, in the order you need them. Every block below is copy-ready, and the key you get in step one drops straight into the rest.

  1. 1. Get a key

    No signup, no email. GET /api/v1/validate/status is the one-line check that the service is up before you start.

    Or from a terminal, in the language you have open:

    curl

    curl -X POST https://canlicapital.com/api/v1/keys \
      -H "Content-Type: application/json" \
      -d '{"label":"quickstart-curl"}'

    Python

    import json
    import urllib.request
    
    body = {"label":"quickstart-python"}
    req = urllib.request.Request("https://canlicapital.com/api/v1/keys", data=json.dumps(body).encode("utf-8"), method="POST")
    req.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(req) as response:
        print(response.read().decode("utf-8"))

    JavaScript

    const body = {"label":"quickstart-js"};
    const response = await fetch("https://canlicapital.com/api/v1/keys", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    console.log(await response.json());

    The key is returned once. Only its hash is kept, so store it now.

  2. 2. Validate your own numbers

    Probabilistic and deflated Sharpe from the seven contract inputs, or from a return series plus the trials behind it.

    Mode 1: the seven contract inputs

    curl

    curl -X POST https://canlicapital.com/api/v1/validate/deflated-sharpe \
      -H "Authorization: Bearer $CANLI_KEY" \
      -H "Content-Type: application/json" \
      -d '{"observed_sharpe_annualized":1.5,"observations":730,"periods_per_year":365,"skew":-0.5,"non_excess_kurtosis":5,"effective_independent_trials":229,"cross_trial_sharpe_sd_annualized":0.57}'

    Python

    import json
    import urllib.request
    
    body = {"observed_sharpe_annualized":1.5,"observations":730,"periods_per_year":365,"skew":-0.5,"non_excess_kurtosis":5,"effective_independent_trials":229,"cross_trial_sharpe_sd_annualized":0.57}
    req = urllib.request.Request("https://canlicapital.com/api/v1/validate/deflated-sharpe", data=json.dumps(body).encode("utf-8"), method="POST")
    req.add_header("Content-Type", "application/json")
    req.add_header("Authorization", "Bearer $CANLI_KEY")
    with urllib.request.urlopen(req) as response:
        print(response.read().decode("utf-8"))

    JavaScript

    const body = {"observed_sharpe_annualized":1.5,"observations":730,"periods_per_year":365,"skew":-0.5,"non_excess_kurtosis":5,"effective_independent_trials":229,"cross_trial_sharpe_sd_annualized":0.57};
    const response = await fetch("https://canlicapital.com/api/v1/validate/deflated-sharpe", {
      method: "POST",
      headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
      body: JSON.stringify(body),
    });
    console.log(await response.json());

    Mode 2: a return series plus the trials behind it

    curl

    curl -X POST https://canlicapital.com/api/v1/validate/deflated-sharpe \
      -H "Authorization: Bearer $CANLI_KEY" \
      -H "Content-Type: application/json" \
      -d '{"returns":[0.004,-0.002,0.007,0.001,-0.003,0.005,0.002,-0.001],"periods_per_year":252,"effective_independent_trials":30,"cross_trial_sharpe_sd_annualized":0.5}'

    Python

    import json
    import urllib.request
    
    body = {"returns":[0.004,-0.002,0.007,0.001,-0.003,0.005,0.002,-0.001],"periods_per_year":252,"effective_independent_trials":30,"cross_trial_sharpe_sd_annualized":0.5}
    req = urllib.request.Request("https://canlicapital.com/api/v1/validate/deflated-sharpe", data=json.dumps(body).encode("utf-8"), method="POST")
    req.add_header("Content-Type", "application/json")
    req.add_header("Authorization", "Bearer $CANLI_KEY")
    with urllib.request.urlopen(req) as response:
        print(response.read().decode("utf-8"))

    JavaScript

    const body = {"returns":[0.004,-0.002,0.007,0.001,-0.003,0.005,0.002,-0.001],"periods_per_year":252,"effective_independent_trials":30,"cross_trial_sharpe_sd_annualized":0.5};
    const response = await fetch("https://canlicapital.com/api/v1/validate/deflated-sharpe", {
      method: "POST",
      headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
      body: JSON.stringify(body),
    });
    console.log(await response.json());

    The other three validators are documented below.

  3. 3. Fetch the receipt

    Every verdict carries receipt.url. It is immutable and cacheable forever, so anyone, not only the caller, can fetch and recompute it.

    curl https://canlicapital.com/api/v1/receipts/{id}

    Every receipt is signed with Ed25519 by the key published at /.well-known/canli-receipt-keys.json, and has a certificate page, canlicapital.com/audit/{id}: the result, what it does not establish, the source files that computed it, and the signature, checked on every view. Its response carries badge_url, certificate_url and embed_markdown: a badge showing only the formula version and the receipt id prefix, never a pass or fail mark, linking to the certificate. Drop the embed straight into a README:

    [![Canli receipt](https://canlicapital.com/api/v1/receipts/{id}/badge.svg)](https://canlicapital.com/audit/{id})

Connect the MCP server

This API is also an MCP server, so a coding assistant can call the routes on this page as tools instead of writing requests by hand. Every result it returns keeps, beside the number, the sentences that say what it cannot be used to claim and the receipt that records it, so the assistant never sees a number alone. It also reads the company reference: one tool returns a company's SEC-reported financial history with each value's filing, unit and source hash.

It can also do the work before the check: backtest a rule over a whole grid of settings and count every version it ran, stress the returns against crashes and stuck positions, sum up a long price series in about a hundred words, and check a trading plan against a broker's limits and market impact. These four compute on the server itself and store nothing.

Claude Desktop: open the downloaded .mcpb file to install. The API key field is optional; leave it empty and the get_key tool issues one for the session.

Security and privacy: what the server does on your machine, what the hosted endpoint does with your key, and what the API stores (a hash of your input, never the input).

Listed on npm, MCP Registry, cursor.directory, Glama, Source.

VS Code

code --add-mcp '{"name":"canli-validation","type":"http","url":"https://canlicapital.com/mcp"}'

Hosted, no install: https://canlicapital.com/mcp

claude mcp add --transport http canli https://canlicapital.com/mcp

Clients that take a server URL (Claude.ai connectors, ChatGPT, Cursor) can use the hosted endpoint directly. Without a key it runs on a shared anonymous quota; send your own key as Authorization: Bearer for yours.

Private local mode: your series never leaves your machine

claude mcp add canli-local --env CANLI_LOCAL=1 -- npx -y canli-validation-mcp

Claude Code

claude mcp add canli -- npx -y canli-validation-mcp

Claude Desktop

{
  "mcpServers": {
    "canli": {
      "command": "npx",
      "args": ["-y", "canli-validation-mcp"]
    }
  }
}

How well agents use it

Each model was given these tools and 24 fixed questions (deflated Sharpe, track record length, breadth, overfitting, company financials by ticker, and what a result does not establish), each asked 3 times. An answer counts when it matches ground truth computed from the same checked code.

ModelCorrect answersRight tool firstRuns
Claude Haiku 4.597.2%98.6%72
Claude Sonnet 5100%100%72
GPT-5.4 mini98.6%100%72

Tasks written by us, run against the server in private local mode. The per-run records also hold tokens and time, which depend on each provider's tokenizer and serving, so they compare runs of one model only. The artifact, the method and every miss.

Inspect the MCP implementation and contribute an integration. If the tools help your research, star the repository to help others discover it.

canli-validation-mcp on npm, with the full tool list and what each one does not establish.

More MCP servers

Each server does one job with a small tool list, so the tools an assistant re-reads on every turn stay cheap. Add only the ones you need; they run side by side.

canli-fundamentals-mcp: SEC fundamentals point in time

MCP server for SEC company fundamentals point in time: what was first reported, what was known on any date, and every later restatement, each value with the filing behind it. Computed locally from a hash-checked SEC snapshot. Tools: known_as_of, history, restatements, vintages, list_concepts, find_company, cross_section.

Claude Code

claude mcp add canli-fundamentals -- npx -y canli-fundamentals-mcp

Hosted, no install: https://canlicapital.com/mcp/fundamentals

claude mcp add --transport http canli-fundamentals https://canlicapital.com/mcp/fundamentals

npm, MCP Registry, Source.

canli-research-mcp: the open research record

MCP server for Canli Capital's open research record: every paper, killed candidate, trial count, the live paper record and its verification chain, each with its own limits. Tools: search_research, list_topics, get_paper, trial_ledger, live_record, chain_head.

Claude Code

claude mcp add canli-research -- npx -y canli-research-mcp

Hosted, no install: https://canlicapital.com/mcp/research

claude mcp add --transport http canli-research https://canlicapital.com/mcp/research

npm, MCP Registry, Source.

Local paper execution workflow

The private, Unreleased execution source has local sizing, pre-trade cost and limit checks, shortfall measurements and signed paper journals. Supply the scenario and explicitly enable journal writes in a prepared private home. It connects to no broker and places no orders.

Read the source-backed workflow and local setup. Hosted release contracts above remain separate.

Read endpoints, no key

PathWhat it returns
GET /api/v1/chain/headThe head of the signed append-only transparency chain, and the public key needed to verify it.
GET /api/v1/recordThe full record as an instance of the canli.paper-evidence.v0 open standard, including what it does not establish.
GET /api/v1/researchEvery published research document and topic, including failures and feasibility nulls.
GET /api/v1/sleevesPer-sleeve state. Note that the research curves are not on a common date grid and cannot be combined.
GET /api/v1/statusCurrent state of the paper record: capital kind, period, observation count, validation status and broker reconciliation.
GET /api/v1/trials/summaryThe trial union used as the multiple-testing denominator, plus the kill counts.

Validation endpoints, free key

Send your own numbers; get back the arithmetic this house runs on itself. Nothing you send is stored. Each verdict returns a receipt anyone can recompute.

RouteWhat it does
POST /api/v1/keys/revokePermanently revoke the bearer key without consuming validation quota. Already admitted requests may finish; public receipts remain available.
POST /api/v1/keysIssue a free key. Returned once; only its hash is stored.
POST /api/v1/validate/deflated-sharpeProbabilistic and deflated Sharpe from the seven contract inputs, or from a return series plus the trials behind it.
POST /api/v1/validate/overfittingProbability of backtest overfitting by CSCV over the returns of every variant tried.
POST /api/v1/validate/paper-evidenceConformance of a performance record against the canli.paper-evidence.v0 standard.
POST /api/v1/validate/breadthBook Sharpe ceiling from per-sleeve quality and average pairwise correlation, and the sleeves a target needs. (book: the set of sleeves run together)
POST /api/v1/validate/track-recordMinimum track record length for a Sharpe to clear a benchmark at a confidence level, and the probabilistic Sharpe of a record of a given length.
POST /api/v1/validate/backtest-lengthMinimum backtest length for the best of N independent trials not to reach a target Sharpe by luck, and the trials a backtest's years allow.
POST /api/v1/validate/haircut-sharpeHaircut Sharpe ratio for multiple testing: the p-value of a Sharpe found among several tests, adjusted by Bonferroni, for independent tests, and with the other tests' Sharpe ratios by Holm and BHY.
POST /api/v1/validate/luck-trialsLuck-equivalent trials: how many skill-less strategies a search would have had to try for its best to reach the observed Sharpe by luck, and, with a trial count, the chance that it did.
POST /api/v1/validate/reality-checkData-snooping tests on every variant a search tried: Hansen's SPA, White's Reality Check and Romano and Wolf's StepM on one seeded stationary bootstrap. Did the best variant beat the benchmark by more than the best of K would by luck, and which variants did?
GET /api/v1/validate/statusService and store status with the quota constants in force.
GET /api/v1/receipts/{id}A stored verdict by content-hash id. Immutable.
GET /api/v1/receipts/{id}/badge.svgAn embeddable SVG badge for a receipt: the formula version and the receipt id prefix only, never a pass or fail mark.

First, issue a key

curl

curl -X POST https://canlicapital.com/api/v1/keys \
  -H "Content-Type: application/json" \
  -d '{"label":"quickstart-curl"}'

Python

import json
import urllib.request

body = {"label":"quickstart-python"}
req = urllib.request.Request("https://canlicapital.com/api/v1/keys", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"label":"quickstart-js"};
const response = await fetch("https://canlicapital.com/api/v1/keys", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(body),
});
console.log(await response.json());

The key is returned once. Only its hash is kept.

Then validate

POST /api/v1/validate/deflated-sharpe

Probabilistic and deflated Sharpe from the seven contract inputs, or from a return series plus the trials behind it.

Mode 1: the seven contract inputs

curl

curl -X POST https://canlicapital.com/api/v1/validate/deflated-sharpe \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"observed_sharpe_annualized":1.5,"observations":730,"periods_per_year":365,"skew":-0.5,"non_excess_kurtosis":5,"effective_independent_trials":229,"cross_trial_sharpe_sd_annualized":0.57}'

Python

import json
import urllib.request

body = {"observed_sharpe_annualized":1.5,"observations":730,"periods_per_year":365,"skew":-0.5,"non_excess_kurtosis":5,"effective_independent_trials":229,"cross_trial_sharpe_sd_annualized":0.57}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/deflated-sharpe", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"observed_sharpe_annualized":1.5,"observations":730,"periods_per_year":365,"skew":-0.5,"non_excess_kurtosis":5,"effective_independent_trials":229,"cross_trial_sharpe_sd_annualized":0.57};
const response = await fetch("https://canlicapital.com/api/v1/validate/deflated-sharpe", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

Mode 2: a return series plus the trials behind it

curl

curl -X POST https://canlicapital.com/api/v1/validate/deflated-sharpe \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"returns":[0.004,-0.002,0.007,0.001,-0.003,0.005,0.002,-0.001],"periods_per_year":252,"effective_independent_trials":30,"cross_trial_sharpe_sd_annualized":0.5}'

Python

import json
import urllib.request

body = {"returns":[0.004,-0.002,0.007,0.001,-0.003,0.005,0.002,-0.001],"periods_per_year":252,"effective_independent_trials":30,"cross_trial_sharpe_sd_annualized":0.5}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/deflated-sharpe", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"returns":[0.004,-0.002,0.007,0.001,-0.003,0.005,0.002,-0.001],"periods_per_year":252,"effective_independent_trials":30,"cross_trial_sharpe_sd_annualized":0.5};
const response = await fetch("https://canlicapital.com/api/v1/validate/deflated-sharpe", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

Try it in the browser, no key required

POST /api/v1/validate/overfitting

Probability of backtest overfitting by CSCV over the returns of every variant tried.

curl

curl -X POST https://canlicapital.com/api/v1/validate/overfitting \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"matrix":[[0.01,0.002,-0.004],[-0.003,0.001,0.006],[0.004,-0.002,0.001],[0.002,0.003,-0.001]],"n_splits":4}'

Python

import json
import urllib.request

body = {"matrix":[[0.01,0.002,-0.004],[-0.003,0.001,0.006],[0.004,-0.002,0.001],[0.002,0.003,-0.001]],"n_splits":4}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/overfitting", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"matrix":[[0.01,0.002,-0.004],[-0.003,0.001,0.006],[0.004,-0.002,0.001],[0.002,0.003,-0.001]],"n_splits":4};
const response = await fetch("https://canlicapital.com/api/v1/validate/overfitting", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

Try it in the browser, no key required

POST /api/v1/validate/paper-evidence

Conformance of a performance record against the canli.paper-evidence.v0 standard.

curl

curl -X POST https://canlicapital.com/api/v1/validate/paper-evidence \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"record":{"schema":"canli.paper-evidence.v0"}}'

Python

import json
import urllib.request

body = {"record":{"schema":"canli.paper-evidence.v0"}}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/paper-evidence", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"record":{"schema":"canli.paper-evidence.v0"}};
const response = await fetch("https://canlicapital.com/api/v1/validate/paper-evidence", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

POST /api/v1/validate/breadth

Book Sharpe ceiling from per-sleeve quality and average pairwise correlation, and the sleeves a target needs.

curl

curl -X POST https://canlicapital.com/api/v1/validate/breadth \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"sleeve_sharpe":0.5,"average_pairwise_correlation":0.05,"sleeves":4,"target":1.5}'

Python

import json
import urllib.request

body = {"sleeve_sharpe":0.5,"average_pairwise_correlation":0.05,"sleeves":4,"target":1.5}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/breadth", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"sleeve_sharpe":0.5,"average_pairwise_correlation":0.05,"sleeves":4,"target":1.5};
const response = await fetch("https://canlicapital.com/api/v1/validate/breadth", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

Try it in the browser, no key required

POST /api/v1/validate/track-record

Minimum track record length for a Sharpe to clear a benchmark at a confidence level, and the probabilistic Sharpe of a record of a given length.

curl

curl -X POST https://canlicapital.com/api/v1/validate/track-record \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"observed_sharpe_annualized":2,"benchmark_sharpe_annualized":1,"periods_per_year":252,"skew":0,"non_excess_kurtosis":3,"observations":504}'

Python

import json
import urllib.request

body = {"observed_sharpe_annualized":2,"benchmark_sharpe_annualized":1,"periods_per_year":252,"skew":0,"non_excess_kurtosis":3,"observations":504}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/track-record", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"observed_sharpe_annualized":2,"benchmark_sharpe_annualized":1,"periods_per_year":252,"skew":0,"non_excess_kurtosis":3,"observations":504};
const response = await fetch("https://canlicapital.com/api/v1/validate/track-record", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

POST /api/v1/validate/backtest-length

Minimum backtest length for the best of N independent trials not to reach a target Sharpe by luck, and the trials a backtest's years allow.

curl

curl -X POST https://canlicapital.com/api/v1/validate/backtest-length \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"effective_independent_trials":45,"backtest_years":5,"target_sharpe_annualized":1}'

Python

import json
import urllib.request

body = {"effective_independent_trials":45,"backtest_years":5,"target_sharpe_annualized":1}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/backtest-length", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"effective_independent_trials":45,"backtest_years":5,"target_sharpe_annualized":1};
const response = await fetch("https://canlicapital.com/api/v1/validate/backtest-length", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

POST /api/v1/validate/haircut-sharpe

Haircut Sharpe ratio for multiple testing: the p-value of a Sharpe found among several tests, adjusted by Bonferroni, for independent tests, and with the other tests' Sharpe ratios by Holm and BHY.

curl

curl -X POST https://canlicapital.com/api/v1/validate/haircut-sharpe \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"observed_sharpe_annualized":1,"periods_per_year":12,"observations":120,"tests":100,"autocorrelation":0.1}'

Python

import json
import urllib.request

body = {"observed_sharpe_annualized":1,"periods_per_year":12,"observations":120,"tests":100,"autocorrelation":0.1}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/haircut-sharpe", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"observed_sharpe_annualized":1,"periods_per_year":12,"observations":120,"tests":100,"autocorrelation":0.1};
const response = await fetch("https://canlicapital.com/api/v1/validate/haircut-sharpe", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

POST /api/v1/validate/luck-trials

Luck-equivalent trials: how many skill-less strategies a search would have had to try for its best to reach the observed Sharpe by luck, and, with a trial count, the chance that it did.

curl

curl -X POST https://canlicapital.com/api/v1/validate/luck-trials \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"observed_sharpe_annualized":1.5,"periods_per_year":252,"observations":756,"effective_independent_trials":200,"skew":-0.4,"autocorrelation":0.05}'

Python

import json
import urllib.request

body = {"observed_sharpe_annualized":1.5,"periods_per_year":252,"observations":756,"effective_independent_trials":200,"skew":-0.4,"autocorrelation":0.05}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/luck-trials", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"observed_sharpe_annualized":1.5,"periods_per_year":252,"observations":756,"effective_independent_trials":200,"skew":-0.4,"autocorrelation":0.05};
const response = await fetch("https://canlicapital.com/api/v1/validate/luck-trials", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

POST /api/v1/validate/reality-check

Data-snooping tests on every variant a search tried: Hansen's SPA, White's Reality Check and Romano and Wolf's StepM on one seeded stationary bootstrap. Did the best variant beat the benchmark by more than the best of K would by luck, and which variants did?

curl

curl -X POST https://canlicapital.com/api/v1/validate/reality-check \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"matrix":[[0.0045,0.0065,-0.0005],[0.0073,-0.0031,-0.0042],[-0.0002,-0.0055,0.0058],[-0.0049,0.0051,-0.0002],[0.0008,0.0038,-0.0046],[0.0041,-0.0064,0.0023],[0.0006,-0.0016,0.0012],[0.002,0.007,0.0015],[0.0068,-0.0007,-0.0062],[0.0024,-0.0066,0.0044],[-0.0051,0.003,0.002],[-0.0018,0.0055,-0.0036],[0.0049,-0.0047,-0.0004],[0.003,-0.0037,0.0011],[0.0006,0.0058,0.0041],[0.0047,0.0016,-0.0068],[0.0042,-0.006,0.0021],[-0.0036,0.0004,0.003],[-0.0041,0.0055,-0.0012],[0.0041,-0.0022,-0.0022],[0.0056,-0.0044,-0.0006],[0.0009,0.0034,0.0062],[0.0021,0.0029,-0.0056],[0.0046,-0.0039,-0.0003],[-0.0011,-0.0014,0.0022],[-0.0051,0.0039,0.0015],[0.0019,0.0001,-0.0024],[0.0073,-0.0034,-0.0031],[0.0027,0.0008,0.0069],[-0.0001,0.0026,-0.0033]]}'

Python

import json
import urllib.request

body = {"matrix":[[0.0045,0.0065,-0.0005],[0.0073,-0.0031,-0.0042],[-0.0002,-0.0055,0.0058],[-0.0049,0.0051,-0.0002],[0.0008,0.0038,-0.0046],[0.0041,-0.0064,0.0023],[0.0006,-0.0016,0.0012],[0.002,0.007,0.0015],[0.0068,-0.0007,-0.0062],[0.0024,-0.0066,0.0044],[-0.0051,0.003,0.002],[-0.0018,0.0055,-0.0036],[0.0049,-0.0047,-0.0004],[0.003,-0.0037,0.0011],[0.0006,0.0058,0.0041],[0.0047,0.0016,-0.0068],[0.0042,-0.006,0.0021],[-0.0036,0.0004,0.003],[-0.0041,0.0055,-0.0012],[0.0041,-0.0022,-0.0022],[0.0056,-0.0044,-0.0006],[0.0009,0.0034,0.0062],[0.0021,0.0029,-0.0056],[0.0046,-0.0039,-0.0003],[-0.0011,-0.0014,0.0022],[-0.0051,0.0039,0.0015],[0.0019,0.0001,-0.0024],[0.0073,-0.0034,-0.0031],[0.0027,0.0008,0.0069],[-0.0001,0.0026,-0.0033]]}
req = urllib.request.Request("https://canlicapital.com/api/v1/validate/reality-check", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {"matrix":[[0.0045,0.0065,-0.0005],[0.0073,-0.0031,-0.0042],[-0.0002,-0.0055,0.0058],[-0.0049,0.0051,-0.0002],[0.0008,0.0038,-0.0046],[0.0041,-0.0064,0.0023],[0.0006,-0.0016,0.0012],[0.002,0.007,0.0015],[0.0068,-0.0007,-0.0062],[0.0024,-0.0066,0.0044],[-0.0051,0.003,0.002],[-0.0018,0.0055,-0.0036],[0.0049,-0.0047,-0.0004],[0.003,-0.0037,0.0011],[0.0006,0.0058,0.0041],[0.0047,0.0016,-0.0068],[0.0042,-0.006,0.0021],[-0.0036,0.0004,0.003],[-0.0041,0.0055,-0.0012],[0.0041,-0.0022,-0.0022],[0.0056,-0.0044,-0.0006],[0.0009,0.0034,0.0062],[0.0021,0.0029,-0.0056],[0.0046,-0.0039,-0.0003],[-0.0011,-0.0014,0.0022],[-0.0051,0.0039,0.0015],[0.0019,0.0001,-0.0024],[0.0073,-0.0034,-0.0031],[0.0027,0.0008,0.0069],[-0.0001,0.0026,-0.0033]]};
const response = await fetch("https://canlicapital.com/api/v1/validate/reality-check", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

Revoke a key you are finished with

This disables the bearer key permanently. It does not issue a replacement or delete public receipts. Check that the response says revoked: true; an unavailable service does not confirm revocation.

curl

curl -X POST https://canlicapital.com/api/v1/keys/revoke \
  -H "Authorization: Bearer $CANLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

Python

import json
import urllib.request

body = {}
req = urllib.request.Request("https://canlicapital.com/api/v1/keys/revoke", data=json.dumps(body).encode("utf-8"), method="POST")
req.add_header("Content-Type", "application/json")
req.add_header("Authorization", "Bearer $CANLI_KEY")
with urllib.request.urlopen(req) as response:
    print(response.read().decode("utf-8"))

JavaScript

const body = {};
const response = await fetch("https://canlicapital.com/api/v1/keys/revoke", {
  method: "POST",
  headers: { "Content-Type": "application/json", "Authorization": "Bearer $CANLI_KEY" },
  body: JSON.stringify(body),
});
console.log(await response.json());

Then cite the receipt

Every verdict carries receipt.url. GET /api/v1/receipts/{id} returns the stored output, the input hash, and the content hash of every core and contract that computed it. The id is the first twenty-four hex characters of the content hash over the canonical JSON of endpoint, input hash, output and bindings, so a third party can check it without trusting this server.

Quotas, public and enforced from one source

LimitValue
Validations per key per UTC day1000
Keys per client per UTC day5
Request body, bytes1048576
Observations per series or rows per matrix20000
Variants per matrix200
CSCV combinations evaluated2000

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429 carries Retry-After.

What happens to a key after you have it

What a verdict does not establish

The envelope

Every response has the same shape. The two fields worth reading first are the two an API usually omits.

claim_class

What kind of claim this is: observed, model estimated, a published document, a cryptographic record. Merging these is how a simulated figure acquires the authority of a measured one.

limits

What this response cannot be used to say. The build refuses to emit an endpoint that declares none.

sources

Every artifact the response was built from, with its content hash and a URL, so a consumer can recompute rather than trust.

generated_at

The freshness of every figure inside. These are snapshots, not a stream.

Limits of the whole API

Found a problem? The governed review route is the fastest way to get it fixed and recorded.

Contribute

Improve the tools you use.

Reproduce a result, fix a tool or improve its documentation. Contributor rewards include higher hosted API quotas and early access to new MCP tools; the program is in development.

Explore contribution routes and reward status ↗