---
title: "Cancel Backtest"
method: POST
path: "/v1/backtests/{job_id}/cancel"
tags: ["backtests"]
---

# Cancel Backtest

`POST /v1/backtests/{job_id}/cancel`

## Request

- Base URL: `https://api.usekeel.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.usekeel.io/v1/backtests/{job_id}/cancel`
- Auth: none declared

Cancel a backtest (Q-1220, consume-at-scale O.21 / doc 10 §3).

The durable request is a status, and which status depends on who holds
the run:

* **QUEUED** -- no worker holds it. The row goes straight to CANCELLED
  here, with every cancellation stamp; the SQS message still arrives at a
  worker, whose atomic claim (``WHERE status = 'QUEUED'``) refuses it --
  that refusal IS the "re-check at claim". The compute-seconds spend
  entry is zeroed (nothing ran); the run unit stays consumed (a
  cancellation is the user's act -- the worker's cancelled arm makes the
  same choice, and the founder ruling on it is doc 30 §4's).
* **RUNNING** -- a worker is executing. The row becomes CANCEL_REQUESTED
  and the worker honours it at its next phase boundary
  (backtest-worker/src/cancellation.py): the response says
  ``cancel_requested``, the client polls, and the row reaches CANCELLED
  within one phase -- or completes anyway if the request landed after
  the worker's last checkpoint (``cancel_requested_at`` is kept as the
  evidence either way).
* **CANCEL_REQUESTED** -- already asked. Idempotent: the current row.
* anything else -- terminal. 409 ``BACKTEST_ALREADY_TERMINAL``.

Every write is guarded on the status it read, so a race with the worker
(a claim landing between the read and the QUEUED write, a completion
landing before the RUNNING write) re-reads and falls through to the
truthful branch instead of overwriting a terminal row.

## Path parameters

- `job_id` string, required

## Headers

- `Authorization` string, nullable
- `X-Org-Id` string, nullable

## Response `200`

Successful Response

- BacktestResponse
  - `id` string, required
  - `strategy_id` string, required
  - `status` string, required
  - `commit_id` string, nullable
  - `sequence_number` integer, nullable
  - `commit_message` string, nullable
  - `source_hash` string, nullable
  - `data_fingerprint` string, nullable
  - `start_date` string, nullable
  - `end_date` string, nullable
  - `engine` string, nullable
  - `backtest_config` object, nullable
  - `experiment_id` string, nullable
  - `trial_id` string, nullable
  - `progress` number, nullable
  - `metrics` object, nullable
  - `queued_at` string, nullable
  - `started_at` string, nullable
  - `completed_at` string, nullable
  - `error_message` string, nullable
  - `execution_time` number, nullable
  - `cancel_requested_at` string, nullable
  - `cancelled_at` string, nullable
  - `remaining` object, nullable
  - `quota` object[], nullable
  - `warnings` object[], nullable
  - `org_id` string, nullable
  - `strategy_name` string, nullable
  - `window_policy` string, nullable
  - `coverage_notes` object[], nullable
  - `window` object, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/usekeel/apis/keel-api.md) · [All operations](https://skmtc.dev/usekeel/apis/keel-api/llms.txt) · [OpenAPI document](https://skmtc.dev/usekeel/apis/keel-api/revisions/3191ca10b0ab?raw)
