---
title: "Start a backtest of a policy version"
method: POST
path: "/api/policy_version/{version}/backtest"
tags: ["Policy Backtests"]
---

# Start a backtest of a policy version

`POST /api/policy_version/{version}/backtest`

## Request

- Base URL: `https://app.herondata.io`
- URL: `https://app.herondata.io/api/policy_version/{version}/backtest`
- Auth: API key in header `x-api-key`

Compare a policy version over historical deals and return immediately with the run's id to poll. The baseline defaults to the account's active version. `as_evaluated` compares each deal with its newest stored section evaluations. A run whose workflow could not be started comes back with `status` failed and the reason in `error`.

## Path parameters

- `version` integer, required

## Request body

- BacktestCreateSchema
  - `baseline` 'active' | 'as_evaluated': Compare with the active policy or each deal's stored evaluations
  - `check_filter` BacktestCheckFilter
    - `check_identity` string
    - `result` 'passed' | 'failed' | 'review_required' | 'missing_data' | 'execution_error' | 'skipped', required
    - `section_heron_id` string, required
  - `description` string, nullable: Free text describing what this run is testing
  - `end_user_heron_ids` string[], nullable: Specific deals to run, in request order; cannot be combined with selection filters
  - `limit` integer, nullable: Deals to include, at most 4000 (default 200)
  - `source_filter` BacktestSourceFilter
    - `names` string[]: Source names sent through the API, matched case-insensitively
    - `sender_addresses` string[]: Addresses that sent the deal's first email, matched case-insensitively
    - `sender_domains` string[]: Domains that sent the deal's first email, like broker.com
  - `window_days` integer, nullable: How far back to look for deals; the account's configured window when omitted
  - `window_end` string, date-time, nullable: Exclusive deal creation bound; requires window_start and is clamped to now when in the future
  - `window_start` string, date-time, nullable: Inclusive deal creation bound; requires window_end and cannot be combined with window_days
  - `with_policy_result` string[], nullable: Only deals whose last policy result was one of ['failed', 'missing_data', 'passed', 'review_required']; omit for all

## Response `200`

The run was recorded; its `status` says whether the workflow started

- object
  - `backtest` BacktestSchema
    - `baseline_mode` 'active' | 'as_evaluated'
    - `baseline_version` integer: Version number the run compared against
    - `candidate_version` integer: Version number the run tested
    - `created` string, date-time
    - `deal_count` integer, nullable: Deals the run claimed at setup
    - `deal_set_params` DealSetParams
      - `check_filter` DealSetCheckFilter
        - `check_identity` string
        - `result` string
        - `section_heron_id` string
      - `end_user_heron_ids` string[]
      - `limit` integer
      - `source_filter` DealSetSourceFilter
        - `names` string[]
        - `sender_addresses` string[]
        - `sender_domains` string[]
      - `window_end` string: ISO 8601, exclusive
      - `window_start` string: ISO 8601, inclusive
      - `with_policy_result` string[], nullable
    - `description` string, nullable
    - `error` string, nullable: Why the run failed, null unless it did
    - `finished_at` string, date-time, nullable
    - `heron_id` string
    - `skipped_end_user_heron_ids` SkippedDeal[]
      - `end_user_heron_id` string
      - `reason` 'not_found' | 'not_backtestable_status'
    - `stale` boolean: The version has been edited since this run was asked for
    - `started_at` string, date-time, nullable
    - `status` 'pending' | 'running' | 'complete' | 'failed'
    - `summary` BacktestSummary
      - `changed_checks` BacktestCheckChange[]: Checks behind the deals whose outcome changed, most-affected first; empty until the run finishes
        - `check_identity` string: What the check reads, which is how it is named
        - `check_name` string, nullable
        - `deal_count` integer: Deals whose outcome changed with this check moving
        - `section_name` string
      - `crm_queue_pairs` BacktestCrmQueuePair[]: Deals per (baseline, candidate) CRM queue pair, most first
        - `baseline` CrmQueueStatus
          - `crm_integration_heron_id` string
          - `status_id` string, nullable: The status the sync step sends, which the CRM may not apply (e.g. under a priority order)
          - `status_name` string, nullable
        - `candidate` CrmQueueStatus
          - `crm_integration_heron_id` string
          - `status_id` string, nullable: The status the sync step sends, which the CRM may not apply (e.g. under a priority order)
          - `status_name` string, nullable
        - `deal_count` integer
      - `crm_queue_result_counts` object: Deals per crm_queue_result, every bucket present even at zero
      - `recorded_deal_count` integer: Deals recorded so far; against deal_count this is the progress
      - `result_counts` object: Deals per evaluation_result, every bucket present even at zero
    - `triggered_by` string

## Other responses

- `404`: Policy version not found
- `422`: No active version to compare against
- `429`: The environment already has as many backtests running as it may

## Changes

- **2026-11-03** `bd8008e49534`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/herondata/apis/heron-data-api/changes/api/policy_version/:version/backtest/post.md)

---

[API](https://skmtc.dev/herondata/apis/heron-data-api.md) - [All operations](https://skmtc.dev/herondata/apis/heron-data-api/llms.txt) - [OpenAPI document](https://skmtc.dev/herondata/apis/heron-data-api/revisions/1ec936e17fef?raw)
