DeepResearch
DeepResearch is one of WebAgent's typed APIs. You give it a topic; it runs multiple rounds of retrieval, cross-checks sources, and produces a Markdown research report with citations and confidence.
Unlike DoAnything: DoAnything has an open-ended output shape — the agent decides what to return; DeepResearch has a fixed output shape — always a research report — so it can offer a field-stable result contract (final_md + citations + confidence). When you know you want a report, use this API.
When to use it
- The output you want is clearly a research report, not the result of an open-ended task.
- You need traceable citations and measurable coverage, not just a summary.
- The task can run for a few minutes to tens of minutes — DeepResearch is a long-running job.
When you want "run a batch of keywords and get structured hits" rather than a written report, use WebSearch. When you want an open-ended task, use DoAnything.
Run a research job
HTTP endpoint:
POST /v1/projects/{pid}/deep_research/runs
Authorization: Bearer wa_...Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
topic | string | Yes | Research topic |
depth | string | No | light / standard / deep — controls the number of retrieval rounds and cost. Defaults to standard |
output_format | string | No | Report shape. Defaults to report |
target_audience | string | No | Who the report is written for; affects the depth of the prose |
require_outline_approval | bool | No | When true, pauses at the outline stage for your approval — see below. Defaults to true |
max_duration_minutes | int | No | Duration backstop |
domain_whitelist / domain_blacklist | string[] | No | Restrict / exclude retrieval domains |
callback_url | string | No | URL called back on a terminal state |
For the full schema, see CreateResearchRequest in the OpenAPI spec.
Example:
from web_agent.v1 import Client
async with Client(api_key="wa_...", project_id="proj_demo") as client:
run = await client.deep_research.run(
topic="The 2026 open-source vector database landscape",
depth="standard",
require_outline_approval=False,
)
print(run["run_id"], run["status"])Phases
A research run moves through these phases in order. While status is still running, phase tells you where it is:
| phase | Meaning |
|---|---|
brief | Parses the topic, settles the research goal |
plan | Plans the retrieval paths |
hitl_outline | Outline is ready, waiting for your approval (appears only when require_outline_approval=true) |
gather | Multiple rounds of retrieval, extracting sources |
crosscheck | Cross-checks across sources |
synthesize | Drafts the report with citations and confidence |
Outline approval (human in the loop)
When require_outline_approval=true, the run pauses at the hitl_outline phase and waits for you to confirm the outline before continuing. This lets you correct the direction before any retrieval cost is spent.
run = await client.deep_research.run(topic="...", require_outline_approval=True)
# run is paused at hitl_outline; approve or adjust the outline via intervene
await client.deep_research.intervene(run["run_id"], response={"approved": True})When you don't need this step, set require_outline_approval=False and the run proceeds straight to a terminal state.
Result
Once a run reaches a terminal state, the result field (ResearchResultPayload) contains:
| Field | Description |
|---|---|
final_md | The research report body, Markdown |
final_artifact_id | The id of the report as an artifact; use the artifacts endpoints to fetch the original file |
citations_count | Number of citations |
confidence_summary | Confidence summary |
partial_sections | Sections already produced when the run did not finish |
Fetch an artifact:
artifacts = await client.deep_research.list_artifacts(run["run_id"])
blob = await client.deep_research.get_artifact(run["run_id"], artifacts[0]["id"])Async and long-running jobs
DeepResearch is synchronous by default, but the deep tier can run for a long time. When you need it not to block, use async mode and subscribe to the event stream to follow along:
run = await client.deep_research.run_async(topic="...", depth="deep")
async for event in client.deep_research.events.stream(run["run_id"]):
print(event.type, event.data)The event stream endpoint …/deep_research/runs/{run_id}/events follows the SSE conventions: events carry a numeric id, and after a disconnect you resume with Last-Event-ID.
Other lifecycle operations: cancel (cancel), feedback (rate the result), send_followup (add requirements on top of an existing report), refine (re-run based on feedback).
Next steps
- WebSearch — use this when you want structured hits, not a written report
- DoAnything — use DoAnything for open-ended tasks
- Pricing & quota — how
depthaffects cost - API Overview — shared conventions; the full field set is in the OpenAPI spec