---
title: "Poll a backtest job"
method: GET
path: "/v1/backtest/{job_id}"
tags: ["Backtest"]
---

# Poll a backtest job

`GET /v1/backtest/{job_id}`

## Request

- Base URL: `https://crowdintel.xyz/api`
- URL: `https://crowdintel.xyz/api/v1/backtest/{job_id}`
- Auth: HTTP bearer

**Min tier: `free`.** Returns the job's current `status` (`queued` → `running` → `done` | `error`), the validated params it was enqueued with, and — once `done` — the full `result` (summary metrics, equity curve, per-trade rows, documented assumptions).

Ownership is enforced against the Clerk user that owns the presenting key. A job id belonging to another account returns `404`, not `403`, so the endpoint never confirms that an id exists.

## Path parameters

- `job_id` integer, required

## Response `200`

Job state (plus `result` when finished).

- BacktestJob
  - `job_id` integer, required
  - `status` 'queued' | 'running' | 'done' | 'error', required
  - `created_at` string, date-time, required
  - `started_at` string, date-time, nullable
  - `finished_at` string, date-time, nullable
  - `params` BacktestRunRequest, required: Backtest parameters. Every field is optional; unknown fields are rejected with 400.
    - `signal` 'whale' | 'insider' | 'all': Which detector's signals to replay. `all` = whale ∪ insider.
    - `filters` object
      - `min_score` number
      - `max_score` number
      - `category` string
      - `min_odds` number: Minimum entry odds (share price 0-1).
      - `max_odds` number
      - `min_bet_value` number: Minimum USD value of the originating fill.
    - `period` object: Resolution-date window. Span capped at 1,095 days; `from` must be on or before `to`.
      - `from` string: ISO-8601 date (`2025-01-01`) or date-time with offset.
      - `to` string: ISO-8601 date or date-time with offset.
    - `stake_rule` object
      - `kind` 'flat' | 'fractional'
      - `size` number: flat: USD per signal. Default 100.
      - `fraction` number: fractional: fraction of current equity per signal. Default 0.05.
      - `max_fraction` number: fractional: hard cap on fraction. Default 0.1.
      - `scale_by_score` boolean: Scale stake linearly by signal score (0.5x at 50 → 1.5x at 100). Default false.
    - `fees` object
      - `fee_bps` number: Entry fee on notional, basis points. Default 200 (2%).
      - `slippage_bps` number: Entry-price slippage against the follower, basis points. Default 0.
    - `initial_capital` number: Starting bankroll (USD) for the equity curve. Default 10000.
  - `result` BacktestResult, required
    - `summary` object
      - `roi` number: Total return on initial capital (fraction).
      - `max_drawdown` number: Worst peak-to-trough on the equity curve (fraction, 0-1).
      - `sharpe` number: Per-trade Sharpe (mean/stdev of trade returns) — NOT annualized.
      - `hit_rate` number
      - `total_trades` integer
      - `wins` integer
      - `losses` integer
      - `initial_capital` number
      - `final_equity` number
      - `net_pnl` number
      - `avg_return_per_trade` number
    - `equity_curve` object[]
      - `t` string, date-time: Resolution timestamp of the settling trade.
      - `equity` number
      - `pnl` number
    - `trades` object[]
      - `fired_at` string, date-time
      - `resolved_at` string, date-time
      - `source` 'whale' | 'insider'
      - `score` number
      - `category` string, nullable
      - `entry_odds` number
      - `stake` number
      - `outcome` 'WIN' | 'LOSS'
      - `pnl` number
      - `return_pct` number: Per-trade return on stake (pnl / stake).
    - `assumptions` object: Plain-language statement of the model each result rests on, plus the effective post-defaulting params.
      - `signal_source` string
      - `payout_model` string
      - `stake_sizing` string
      - `fee_model` string
      - `slippage_fill` string
      - `survivorship` string
      - `resolution_timing` string
      - `effective_params` object: Echo of the resolved parameters actually used.
  - `error` string, nullable, required: Failure message; null unless `status` is `error`.

## Other responses

- `400`: Malformed parameters or a rejected guardrail (e.g. unfiltered trades query, cluster blowup, window > 90 days).
- `401`: Missing, invalid, or revoked API key. Returned before the rate limiter runs, so it carries no `X-RateLimit-*` headers.
- `403`: Key valid but its tier is below this endpoint's minimum. Returned before the rate limiter runs, so it carries no `X-RateLimit-*` headers.
- `404`: The requested resource was not found.
- `429`: Hourly or monthly rate limit exceeded.

---

[API](https://skmtc.dev/crowdintel/apis/crowdintel-api.md) · [All operations](https://skmtc.dev/crowdintel/apis/crowdintel-api/llms.txt) · [OpenAPI document](https://skmtc.dev/crowdintel/apis/crowdintel-api/revisions/7059cfe9320d?raw)
