Reference

REST endpoints

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#

Account & usage

MethodPathDescription
GET/v1/meVerify a key and see who it belongs to, a quick connection test
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. Bills
GET/v1/backtests/:id/contextWhy trades won/lost by regime: /context/trades, /context/reanalyse, POST …/context-run, POST …/context/bucket-trades
GET/v1/backtests/:id/overfitOverfitting verdict: Deflated Sharpe + PBO, deflated by the configs tried on the strategy session
POST/v1/condition-insightsForward-outcome base rates for one entry condition, a filter probe, not a backtest
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/sweepRe-run 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
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. Paid
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

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#

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 break-even, 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, 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.