Reference

REST endpoints

The backtesting API: run backtests and read results over HTTP.

Base URL: https://www.texttoquant.com/api. All endpoints require authentication and return the standard { success, data } envelope. The whole surface is described in OpenAPI 3.1 at GET /v1/openapi.json (public). Generate typed clients straight from it.

Endpoints#

Some endpoints need a plan above the one an API key starts on (API keys are on Pro and above). The tables mark them: Power+ means Power, Quant and Enterprise; Enterprise means Enterprise only. A call your plan does not cover is refused before anything is billed.

Account & usage

MethodPathDescription
GET/v1/meVerify a key and see what it can do: owner, scopes, plan, and whether /v1/trading is reachable
GET/v1/usagePlan tier and backtest credits: limit, used, remaining, period end
GET/v1/me/notification-channelsWhich alert channels will deliver: Telegram linked? email?

Parse & run

MethodPathDescription
POST/v1/parsePlain English → structured query. Send { "query": "…" } (+ optional customIndicatorHints); get a parsedQuery. Plan gated (LLM)
POST/v1/backtestsRun a backtest from a parsedQuery. Supports Idempotency-Key and savedIndicatorMapping. Bills 1
GET/v1/backtestsYour run history, newest first, filterable & sortable (see below)
GET/v1/backtests/:idOne run in full: summary metrics, grade, edit lineage, exact parameters
GET/v1/backtests/:id/statusCheap progress poll: completed, running (stage, percent), failed, cancelled. Use after a 202
POST/v1/backtests/:id/cancelCancel a run that is still queued and refund its credit. A run already computing is not stopped (returns running). Never refunds a completed run
POST/v1/backtests/:id/sweepRobustness suite (async → poll GET …/:jobId): /sweep, /grid, /walk-forward, /joint-sweep, /multi-asset, variants and screens. Bills, Power+
GET/v1/backtests/:id/contextWhy trades won/lost by regime: /context/trades, /context/reanalyse, POST …/context-run, POST …/context/bucket-trades. Power+
GET/v1/backtests/:id/overfitOverfitting verdict: Deflated Sharpe + PBO, deflated by the configs tried on the strategy session. Power+
POST/v1/condition-insightsForward outcome base rates for one entry condition, a filter probe, not a backtest. Power+
GET/v1/galleryShowcase strategies to start from, ranked by sharpe, return, dsr. Each card carries author: {handle, kind} (where kind is username, email or anonymous) or null
POST/v1/portfolio/*Portfolio endpoints (the rows below). With an API key they need the Enterprise portfolio builder. Power and Quant run portfolios only through a connected AI agent (MCP), not with a key
POST/v1/portfolio/sweepRerun a whole book across 2-5 values of one knob. Bills K×N, Idempotency-Key supported, poll GET …/:jobId, POST …/:jobId/cancel refunds unrun books
GET/v1/portfolio/runs/:id/analysisA completed book's analysis: metrics, grade, attribution, benchmark, Monte Carlo, walk forward, out of sample
POST/v1/portfolio/parseSplit a multi asset prompt into per asset strategies
POST/v1/portfolio/executeRun a shared capital portfolio (async → poll GET …/:jobId). Bills 1 per asset
GET/v1/portfolio/execute/:jobIdPoll an async portfolio run
POST/v1/portfolio/runs/:id/shareMint a portfolio run's public share link (DELETE the same path revokes it)

Data & results

MethodPathDescription
GET/v1/runs/:runId/equityRaw datasets: equity, trades, candles, monte-carlo, markers, condition logs (paginated)
GET/v1/runs/:runId/indicators/:namePlotted values of one indicator on a run
POST/v1/backtests/:id/shareMint the public share link (DELETE the same path revokes it)
GET/v1/backtests/:id/chartFresh chart payload (series + markers) for a saved run
GET/v1/backtests/:id/sweep/optionsThe sweepable knobs available for a run
GET/v1/strategies/:id/iterationsEdit/version history of a strategy session
PATCH/v1/backtests/:id/tagsReplace a run's organization tags
PATCH/v1/strategies/iterations/:id/labelRename one iteration
PATCH/v1/strategies/:id/session-labelRename a strategy session

Custom indicators

MethodPathDescription
GET/v1/indicatorsYour saved custom indicators (name, source, columns, range)
GET/v1/indicators/:nameOne saved indicator in full, including its Pine source
DELETE/v1/indicators/:nameDelete a saved indicator
POST/v1/indicatorsCompile a Pine script against real data and save it: { "name", "pine_code" }. Plan gated (Pine)
POST/v1/indicators/validateCompile check a Pine script, no run, no save
POST/v1/indicators/lintOffline Pine diagnostics (quota free)
POST/v1/indicators/previewCompile and run a Pine indicator against real data (async → poll GET …/preview/:jobId)
POST/v1/export/pineA parsedQuery, or an existing backtestId, → a TradingView Pine v6 strategy() script. Power+
POST/v1/rephraseRewrite a query into clearer, parser friendly phrasing (metered; identical repeats free)

Market screener (crypto)

MethodPathDescription
GET/v1/screener/scanThe tradable crypto universe with per token strength scores, price, 24h move and volume (cache fresh)
GET/v1/screener/rspsMarket regime verdict per timeframe: RUN, REVIEW, SKIP
GET/v1/screener/token-statsPer token forward outcome base rates for a tf + signal condition
GET/v1/screener/sectorsCrypto sector performance (optional date)
GET/v1/screener/rsps/matrixPairwise dominance for the top k tokens
GET/v1/screener/saved-scansYour saved screener filters: POST saves, DELETE /:id removes
GET/v1/screener/scan-alertsYour scan alerts: POST creates, DELETE /:id removes; notified by Telegram/email. Every plan holds alerts: Free 3, Pro 25, Power 150, Quant and Enterprise unlimited
GET/v1/screener/custom-benchmarkCorrelation + beta of the universe vs a benchmark you choose
GET/v1/screener/token-multitfOne token's recent OHLCV + multi TF screener rows (capped)
POST/v1/screener/profilerStart the Perfect Token Profiler (async → poll GET …/profiler/:jobId)

Webhooks

MethodPathDescription
POST/v1/webhooksRegister an HTTPS receiver for backtest.completed / backtest.failed. GET lists, DELETE /:id removes
POST/v1/webhooks/:id/testFire one signed webhook.test event to verify your receiver

Live trading & portfolio

A separate surface under /v1/trading/*: positions, orders, fills, closed trades, P&L, risk state, and controls that only ever reduce risk. It needs its own opt in key scopes and a live trading plan, so it has its own page: Trading & portfolio API.

GET /v1/backtests: query parameters#

Mix and match freely. Anything invalid comes back as a 400 invalid_query that names the bad parameter. Nothing is ever silently ignored.

ParameterType (default)Notes
limitint (20)Page size, 1-100
offsetint (0)Rows to skip (offset paging)
sortenum (created_at)created_at, total_return, win_rate, total_trades, sharpe_ratio, max_drawdown, profit_factor, execution_time_ms
orderenum (desc)asc or desc
assetlist (none)One symbol or comma list, e.g. BTCUSDT,ETHUSDT
timeframelist (none)One value or comma list, e.g. 4h,1d
taglist (none)One tag or comma list of your tags
searchstring (none)Free text over your original query text (≤ 200 chars)
from / toISO date (none)Inclusive created_at window
statusenum (completed)completed, failed, running, pending, cancelled
include_statsbool (off)Aggregate stats block (off by default for API keys)
include_facetsbool (off)Distinct assets/timeframes, for filter menus
text
# newest first (default)
/v1/backtests?limit=20

# best Sharpe on BTC or ETH, 4h, first half of 2025
/v1/backtests?asset=BTCUSDT,ETHUSDT&timeframe=4h&sort=sharpe_ratio&order=desc&from=2025-01-01&to=2025-06-30

# one tag, highest return first
/v1/backtests?tag=breakout&sort=total_return&order=desc

REST quick start#

Three calls, end to end (parse, run, then read your history):

bash
# 1. parse plain English
curl -X POST https://www.texttoquant.com/api/v1/parse \
  -H "Authorization: Bearer ttq_..." -H "Content-Type: application/json" \
  -d '{"query":"Buy BTCUSDT on the 4h when RSI(14) crosses above 30, sell at 6% profit or 3% stop loss, last 2 years"}'

# 2. run it (bills one backtest)
curl -X POST https://www.texttoquant.com/api/v1/backtests \
  -H "Authorization: Bearer ttq_..." -H "Content-Type: application/json" \
  -d '{"parsedQuery": <the object from step 1>}'

# 3. read your results
curl "https://www.texttoquant.com/api/v1/backtests?limit=10" \
  -H "Authorization: Bearer ttq_..."

A successful run#

POST /v1/backtests returns summary metrics and a grade. Percentages are already scaled: 42.3 means 42.3%.

json
{
  "success": true,
  "data": {
    "analysisId": "a1b2c3d4-e5f6-…",
    "query": "Buy BTCUSDT on the 4h when RSI(14) …",
    "backtestResults": {
      "summary": {
        "totalReturnPct": 42.3,
        "winRate": 60.4,
        "sharpeRatio": 1.82,
        "maxDrawdownPct": 11.7,
        "profitFactor": 1.9,
        "totalTrades": 48
      },
      "grading": { "grade": "B+", "score": 78 }
    }
  }
}

Probability scan#

Enterprise only: on other plans the field is absent. GET /v1/backtests/:id carries the full signal base rate at metrics.probabilityScan: the entry trigger measured across every time it fired, including the ones the run could not trade because a position was already open.

json
{
  "metrics": {
    "probabilityScan": {
      "totalSignals": 647,
      "resolvedSignals": 631,
      "pendingSignals": 16,
      "successRate": 27.7,
      "edge": {
        "breakevenRatePct": 26.1,
        "edgePts": 1.6,
        "expectancyPct": 0.026,
        "payoffRatio": 2.83,
        "ci95": { "low": 24.3, "high": 31.2 },
        "verdict": "inconclusive",
        "minSampleForVerdict": 30
      },
      "execution": {
        "takenSignals": 544,
        "skippedSignals": 87,
        "skippedEdgePts": -1.4,
        "modelFidelity": { "compared": 544, "agreementPct": 91.2, "medianAbsDeltaPts": 0.31 }
      },
      "successDefinition": "exit_targets",
      "unmodelledExits": ["indicator_signal"],
      "signalCount": 647
    }
  }
}

Reading it correctly matters more than reading it at all:

  • successRate divides by resolvedSignals, never the total. A pending signal is unknown, not unsuccessful.
  • Never quote the rate without edge.breakevenRatePct. 27% is excellent at a 3:1 payoff and fatal at 1:1; edge.edgePts is the difference and the actual claim.
  • edge.verdict is decided by the interval, not the point estimate: edge only when ci95.low clears breakeven, and inconclusive below minSampleForVerdict regardless.
  • execution.skippedEdgePts is only a finding when the two intervals separate. They usually overlap, and then the sample cannot say whether the position filter helped.
  • unmodelledExits lists exit types the scan did not simulate; execution.modelFidelity measures what that actually cost against the real trades.

Everything here is gross of fees and position independent, a diagnostic of the trigger, not account P&L. The per signal rows are not in this payload; fetch them from the run's chart-markers dataset.

Absent on some runs

probabilityScan is omitted on runs saved before 2026-08-22, and on heavy (chunked) backtests, which stream the series in slices and cannot run the scan.

Cancel a queued run#

Start a run without waiting (wait: false) and you get a 202 with an analysisId to poll. Change your mind before it starts computing and you can cancel it, and the credit is refunded:

bash
# start async → 202 { "analysisId": "…", "status": "running" }
curl -X POST https://www.texttoquant.com/api/v1/backtests \
  -H "Authorization: Bearer ttq_..." -H "Content-Type: application/json" \
  -d '{"parsedQuery": <the object from /v1/parse>, "wait": false}'

# cancel it, refunds the credit only if the run is still queued
curl -X POST https://www.texttoquant.com/api/v1/backtests/<analysisId>/cancel \
  -H "Authorization: Bearer ttq_..."

The response says which case applied:

json
{ "success": true,
  "data": { "analysisId": "…", "status": "cancelled", "refunded": true, "reason": "queued_removed" } }

status is cancelled (removed + refunded: true), running (already computing, not stopped, no refund), completed (already saved, nothing to cancel), or not_found (no cancelable job for you). A run that has already started keeps going server side and lands in your history.

Custom indicators over the API#

Strategies can reference your saved custom indicators (from the Pine editor, CSV uploads, or POST /v1/indicators) in three steps:

bash
# 1. discover your saved indicator names
GET /v1/indicators

# 2. parse with hints so conditions are tagged custom
POST /v1/parse
{ "query": "Buy BTCUSDT 1d when my_osc crosses above 0, from 2023-01-01 to 2025-01-01",
  "customIndicatorHints": ["my_osc"] }

# 3. run with the name mapping
POST /v1/backtests
{ "parsedQuery": { }, "savedIndicatorMapping": { "my_osc": "my_osc" } }

Webhooks: push instead of poll#

Register a receiver once and get an HTTPS POST the moment a run finishes (or fails). Registration returns the signing secret once; every delivery is signed so you can verify it came from us.

text
# register (max 3 active; https + public host only)
POST /v1/webhooks
{ "url": "https://yourapp.com/ttq-hook", "events": ["backtest.completed"] }
→ { "id": "…", "secret": "whsec_…" }   # store the secret NOW

# every delivery is signed:
#   X-TTQ-Signature: t=<unix>,v1=<hex hmac-sha256(secret, `${t}.${rawBody}`)>
# verify: recompute the HMAC over `${t}.${rawBody}` and compare constant-time

# payload
{ "id": "evt_…", "event": "backtest.completed",
  "data": { "analysisId": "…", "status": "completed", "resultUrl": "/v1/backtests/…" } }

Deliveries retry 3× (0s / 30s / 120s). A receiver failing 20 times in a row is auto disabled. Reregister to resume.

Prefer an AI agent? See the MCP server.