---
title: "Create a run"
method: POST
path: "/v1/runs"
tags: ["runs"]
---

# Create a run

`POST /v1/runs`

## Request

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

Validates the request, checks the tier against the plan, the workspace concurrency limit and the account spend cap, fixes the price, writes the D1 index row, and starts the research-run Workflow.

## Headers

- `idempotency-key` string

## Request body

- CreateRunBody
  - `query` string, required
  - `tier` 'light' | 'premier': The tier to run at: `light` ($9, one focused question or a narrow topic) or `premier` ($49, shown as "Deep" - a broader question across several research sections). Omitted, it is `light`. `brief`, `full`, `dissertation` and `auto` are not offered and are refused with 400 `tier_not_offered`.
  - `clarify` 'ask' | 'auto' | 'off': What to do about an ambiguous question, decided BEFORE anything is reserved or charged. `ask` assesses the question and, when it earns it, holds the run at `clarifying` with at most three questions for a person to answer at `POST /v1/runs/{id}/clarify`; left unanswered for 30 minutes it starts by itself on the stated assumptions (or closes without charge if it cannot be admitted then). `auto` assesses but never waits: the run starts immediately on those assumptions, which its report then names. Under `ask` and `auto` a query with nothing to research in it (a greeting, a test string) is held at `clarifying` and asked what to research; unanswered, it closes without charge. `off` is a hard bypass: no assessment and no interview of any kind — the run starts at once on the query as written, and a query with nothing to research in it is refused with 400 `query_not_research` instead of being held. Omitted, a console run defaults to `ask` and every other channel to `auto`, unless the workspace sets `clarify_default`.
  - `levers` RunLevers
    - `register` 'teach' | 'survey' | 'analyze' | 'advocate'
    - `inference_depth` 'surface' | 'standard' | 'deep': Deprecated. Still accepted and stored on the run, but the research planner sets its own depth for each question and no longer reads this value. Omit it.
    - `domain_notes` string
    - `response_format` string
    - `required_section_headings` string[]
    - `citation_style` 'numbered' | 'wikilink': Deprecated and ignored. Still accepted so existing callers keep working, but no longer stored on the run: every report uses the same citation markers whatever this says. Omit it.
    - `purpose` 'decide' | 'understand' | 'brief_someone': What the report is for. `decide` opens with the answer and what it means for the decision, sets out the trade-offs and closes with a recommendation; `understand` explains the subject and its mechanisms without pushing a recommendation; `brief_someone` makes the opening summary a stand-alone brief you can forward. It changes how the report is organised and summarised, never what it researches, claims or cites, and it does not change the price, the sources or the run time. Omitted, the report is organised as before.
    - `report_language` string: The language the report is written in, as a language tag (`en`, `zh`, `ja`, `ko`, `fr`, ...) or a bilingual pair (`zh+en`). Omitted, the report follows the language the question asks for, or the language it is written in. Headings and the labels the pipeline adds are localised for English, Chinese, Japanese and Korean, and are in English for other languages.
    - `reader_expertise` 'beginner' | 'intermediate' | 'expert': Who the report is written for. `beginner` defines technical terms and acronyms in plain words and explains the background a newcomer needs; `intermediate` uses the field’s standard terms and defines only specialist ones; `expert` uses the field’s terminology without definitions and spends the words on specifics. It changes how the report explains things, never what it researches, claims or cites, and it does not change the price, the sources or the run time. Omitted, the report is written for an informed non-specialist, as before.
  - `profile` 'value' | 'speed' | 'quality'
  - `budget` RunBudget
    - `fetch_usd_max` number: Deprecated. Still accepted, and still applied as a ceiling on paid page fetching and search for this run, but a low value can leave a run short of sources and stop it before it finishes. Must be greater than 0. The tier already bounds this spend and the price does not change with it. Omit it.
    - `sources_max` integer: Deprecated. Still accepted, and still applied as a cap on how many sources this run reads, up to the tier ceiling. A value below the tier’s source floor stops the run at the source gate, and the price does not change with it. Omit it.
  - `vault` RunVaultOptions
    - `reuse` union
      - boolean
      - 'all'
    - `tags` string[]
    - `reuse_tags` string[]: Restrict vault-first reuse to notes carrying ALL of these tags. Set by the server for a project run (`project:<id>`). An unfiled run reuses nothing unless `reuse_scope` is `workspace`, which clears this list.
    - `reuse_scope` 'off' | 'project' | 'workspace': Which slice of the vault this run may reuse evidence from, before any fetching. `workspace` reads the whole workspace vault; a candidate is only accepted when it clears a relevance, topic-overlap and freshness gate, and workspace reuse can never supply more than a quarter of the run's source floor. `project` restricts reuse to the run's own project and requires `project_id`. `off` disables vault-first reuse entirely — every source is fetched fresh. Omitted, a filed run defaults to `project` and an unfiled run to `off`: an unfiled run reads the workspace vault only when `workspace` is named here (`vault.reuse: true` alone does not opt in). Either way the run writes its notes to the workspace vault, and filing it under a project later moves them into that project. This is a shorthand over `vault.reuse` / `vault.reuse_tags`, which it overrides.
  - `project_id` string: File this run under a project in the same workspace. The run’s notes are tagged `project:<id>` and vault reuse is scoped to that tag. Omit for an unfiled run.
  - `webhook_url` string, uri
  - `metadata` object

## Response `202`

Run queued

- RunCreated
  - `id` string, required
  - `status` 'queued' | 'clarifying' | 'aborted', required: `clarifying` when the question was ambiguous enough to be worth asking about, or was not a research question at all. The run exists at its final id and is waiting at `POST /v1/runs/{id}/clarify`; nothing has been reserved or charged, so `price_cents` is null. `aborted` is only ever answered by `POST /v1/runs/{id}/clarify`, for an interview that ran out of rounds without finding a research question: that run started nothing, reserved nothing and claimed no included run.
  - `tier` 'auto' | 'light' | 'brief' | 'full' | 'premier' | 'dissertation', required
  - `price_cents` integer, nullable, required
  - `events_url` string, required
  - `created_at` string, required
  - `clarification` Clarification
    - `status` 'asking' | 'answered' | 'assumed', required
    - `round` integer, required
    - `score` integer
    - `questions` ClarificationQuestion[], required
      - `axis` string, required: The named dimension this question is about (`scope`, `time_window`, `geography`, …). Answer by axis, not by position.
      - `question` string, required
      - `assumption` string, required: What the run will do if this question is not answered. Shown behind "You decide" and in the skip-all confirmation, and printed in the report’s Assumptions paragraph.
      - `evidence` string, nullable, required: The span of the customer’s own question that prompted this one, when it could be quoted verbatim.
      - `suggested_answers` string[], required
    - `answers` ClarificationAnswer[], required
      - `axis` string, required
      - `question` string
      - `answer` string, required: The literal `you_decide` when the customer left this to us; the assumption then records the decision.
      - `you_decide` boolean
      - `answered_at` string
    - `assumptions` string[], required: Every default this run is proceeding on — verbatim the sentences its report prints.
    - `expires_at` string, nullable, required: When an unanswered interview times out; what happens then is `on_timeout`. Null once the interview is settled.
    - `on_timeout` 'start' | 'close': Present while `status` is `asking`: what happens at `expires_at` if nobody answers. `start` admits the run on its stated assumptions (or closes it without charge if it cannot be admitted then — spend cap, no payment method); `close` ends it without starting and without charge, which is what an interview that found nothing to research does.
    - `resolution` 'answered' | 'skipped' | 'timeout' | 'auto' | 'off' | 'unassessed' | 'null', nullable, required
    - `note` string: A sentence about the interview rather than about the question: that the ambiguity assessment could not be made (`resolution: "unassessed"`), that nothing has started while we ask what to research, or that the time-box closed the run without charge.

## Other responses

- `400`: Invalid request
- `401`: Authentication failed
- `402`: Spend cap exceeded
- `403`: Insufficient scope or plan
- `404`: Not found
- `409`: Idempotency-Key reused with a different body
- `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)
