---
title: "File a run under a project (or unfile it)"
method: PATCH
path: "/v1/runs/{id}"
tags: ["runs"]
---

# File a run under a project (or unfile it)

`PATCH /v1/runs/{id}`

## Request

- Base URL: `https://api.hyperresearch.ai`
- URL: `https://api.hyperresearch.ai/v1/runs/{id}`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `hr_session`

Sets `project_id`; `null` unfiles the run. Allowed while the run is `queued` or terminal; a run that is running, paused or blocked is a 409 `run_not_filable`. Filing a run also RETAGS the notes it wrote: `project:<id>` is added (and the previous project's tag removed), so the run's evidence appears under the project's Sources and Vault and is reusable by that project's later runs. `notes_retagged` on the response says how many notes moved; `0` means the run wrote none, or the vault could not be reached — the run itself is filed either way.

## Path parameters

- `id` string, required

## Request body

- PatchRunBody
  - `project_id` string, nullable, required

## Response `200`

Run

- object
  - `id` string, required
  - `project_id` string, nullable, required
  - `query_preview` string, nullable, required
  - `display_title` string, nullable, required
  - `status` string, required
  - `tier` string, required
  - `profile` string, required
  - `price_cents` integer, required
  - `billed` boolean, required
  - `included` boolean, required: True when an included (trial) run covered this run, so it metered at $0. The server’s answer to "Included in trial" — never inferred from `price_cents`. Alias of `included_in_trial`.
  - `included_in_trial` boolean, required: Same value as `included`, spelled the way the console reads it.
  - `trial_tier` string, nullable, required: Which included-run allowance was claimed (`light`/`full`), or null.
  - `complimentary` boolean: True when an operator grant comped this run (0041). The same field and the same value `GET /v1/runs/{id}` publishes, so a list row can print "Complimentary" beside the price.
  - `charged_cents` integer, nullable, required: What the billing ledger metered for this run, refunds included. `0` for a trial-covered run; `null` when no ledger row exists yet — which is NOT the same as zero.
  - `sources_fetched` integer, nullable, required: The same number the run detail reports in `progress.sources_fetched`. Null until the run has reported one. Also in `counters`.
  - `notes_written` integer, nullable, required
  - `counters` RunCounters, required: Everything a list row counts, in one place, cached from the RunAgent: the same numbers `GET /v1/runs/:id` reports in `progress`, a few seconds behind at most, so a list card never has to fetch the run to show them (E2E2-13/E2E2-24). `sources_fetched`/`notes_written` above are the same numbers, kept flat for existing clients.
    - `sources_fetched` integer, nullable, required: Sources this run KEPT; the same number `progress.sources_fetched` and the `counters` stream event report (E2E4-02).
    - `notes_written` integer, nullable, required
    - `candidates_seen` integer, nullable, required: Candidates considered, kept or not. Always >= `sources_fetched` when both are known.
    - `fetch_attempts` integer, nullable, required: Physical fetch attempts — requests that actually went out. A vault lookup is not one, so this can be lower than `candidates_seen` (E2E7-02).
    - `sources_listed` integer, nullable, required: DEPRECATED spelling of `candidates_seen`, emitted in step with it. Read `candidates_seen`.
    - `steps_done` integer, nullable, required: Steps finished. Step progress lives in the run’s Durable Object and this list will not open one per row (E2E2-24), so the RunAgent writes it through to the control row on every step transition (0026). NULL for a run that has not reported one yet — a client that finds it null can still derive the scale from the tier.
    - `steps_total` integer, nullable, required: Steps this run’s tier executes, from the same write-through. NULL when the run has not reported one yet.
  - `step_id` string, nullable, required: The step this run is on, cached from the RunAgent; null when it is not running.
  - `step_name` string, nullable, required: That step in human words (the shared step → name map), or null.
  - `step_detail` string, nullable: The unit inside that step, in customer words: "Chapter 2 of 4 · Depth investigation", "Writing section 3 of 9". The same string `GET /v1/runs/{id}` reports as `step.detail`, cached from the RunAgent (0035). Null on a flat run and once the step stops.
  - `chapters_total` integer, nullable: How many chapters this run's plan has. A count of the PLAN, so it outlives the run and a finished Deep row still says how many chapters the customer got. Null means the run is not chaptered (or settled before the column existed) — never zero.
  - `report_words` integer, nullable, required: Word count of the finished report; null while there is no report.
  - `blocked_on` string, nullable, required: One customer-safe sentence saying what a `blocked` run is waiting for a person to do, and null on every other status. The same value `GET /v1/runs/{id}` reports, so a list row can prompt for a resume without fetching the run.
  - `blocked_code` string, nullable: The machine gate id behind `blocked_on` — `step:<id>:<check>`, `verify:<check>`, `fetch_budget`, `model_budget` or `provider:<name>`. Branch on this; print `blocked_on`.
  - `wait_reason` 'provider_billing' | 'provider_rate_limit' | 'provider_outage' | 'platform_capacity' | 'platform_transient' | 'vault_unavailable' | 'null', nullable: Why a `blocked` run is waiting on US rather than on a person: it is clearing the condition by itself and will carry on with no action from the caller, and nothing has been charged. Present only while a run is waiting; absent or null on every other run, including a block that does need the customer to act. Branch on its PRESENCE, not on the class.
  - `recovering` object, nullable: Present, non-null, while a `running`/`blocked` run is being RECOVERED: our infrastructure interrupted it and we are resuming it from where it stopped, on the same run id, at no extra charge (Deep automatic recovery). Absent or null on every other run. While set, `wait_reason` is null and `blocked_on` is not a gate: the run needs nothing from anyone. Branch on its PRESENCE.
    - `attempt` integer, nullable, required: Which automatic recovery this is (1, 2, 3), or null while a stalled run is being handed over and the attempt is not yet claimed.
  - `failure_reason` string, nullable, required: Why this run ended badly, in one customer-safe sentence; null for a healthy run. For a `blocked` run this is `blocked_on`, matching the run status view. The raw text is `failure_detail` on the run detail, owner/admin only.
  - `aborted_by` string, nullable, required: The user id that asked for the abort, or null for a run nobody stopped, a run stopped by an unattributed API key, or one stopped before this field existed. Published so a client can say "Stopped by you." on its own terms rather than inferring it from the sentence; `failure_reason` is already narrowed for the caller.
  - `failure_request_id` string, nullable, required
  - `channel` string, required
  - `dry_run` boolean, required: True when this run was a DRY RUN: the orchestration was real but the model provider and the fetch/search vendors were mocked, so the report is synthetic and the price is $0. Server truth (the run’s `dry_run` channel), never inferred from a zero price — a trial run and a fixture run are also $0 and are not dry. Always false in production, where dry runs cannot be created.
  - `created_at` string, required
  - `started_at` string, nullable, required
  - `finished_at` string, nullable, required
  - `duration_ms` integer, nullable, required: Wall time from start (or creation) to finish; null while the run is unfinished. Settled in the same statement that writes `finished_at`, and carried on the terminal stream event, so a watching page and a reloaded one never differ (E2E8-13).
  - `version_of` string, nullable, required: The newest run asking the same question, or null when this run IS that run. Label a row “Earlier version” if and only if this is set. Grouped by normalised question text, or by an explicit rerun parent — never by project (E2E8-10).
  - `version_group` string: Stable key for the version group, so a client can bucket a page in one pass.
  - `receipt_summary` ReceiptSummary, nullable, required: What this report’s own verification receipt came to, in four counts — so a row or a card can show that a report needs a second look without opening it (E2E10-07). Null means no receipt is known for this run (it did not produce one, or it finished before the counts were stored); null is NOT a pass.
    - `passed` integer, required: Checks that ran and met their bar. A warning is NOT a pass (E2E8-01).
    - `warning` integer, required: Checks that ran and recorded a finding below our own target. Informational; see `attention` for what a reader should act on.
    - `failed` integer, required: Checks that ran and did not meet their bar.
    - `skipped` integer, required: Checks with nothing to judge ("not applicable"). Counted apart from the three above.
    - `attention` integer: Findings a reader should act on before relying on the report: cited statements that say more than their sources support (above 3 and 3% of those checked), a chapter without its full research pass, a retracted source, or final checks that did not run. Absent on a summary cached before this field existed.
  - `notes_retagged` integer, required: Vault notes moved to the new project scope by this call.

## Other responses

- `400`: Invalid request
- `401`: Authentication failed
- `403`: Insufficient scope or plan
- `404`: Not found
- `409`: The run is mid-flight, or the target project is archived
- `429`: Rate limited or over quota
- `500`: Internal error

---

[API](https://skmtc.dev/hyperresearch/apis/hyperresearch-cloud-api.md) · [All operations](https://skmtc.dev/hyperresearch/apis/hyperresearch-cloud-api/llms.txt) · [OpenAPI document](https://skmtc.dev/hyperresearch/apis/hyperresearch-cloud-api/revisions/726ef831ebf4?raw)
