Skip to content

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:

http
POST /v1/projects/{pid}/deep_research/runs
Authorization: Bearer wa_...

Request fields:

FieldTypeRequiredDescription
topicstringYesResearch topic
depthstringNolight / standard / deep — controls the number of retrieval rounds and cost. Defaults to standard
output_formatstringNoReport shape. Defaults to report
target_audiencestringNoWho the report is written for; affects the depth of the prose
require_outline_approvalboolNoWhen true, pauses at the outline stage for your approval — see below. Defaults to true
max_duration_minutesintNoDuration backstop
domain_whitelist / domain_blackliststring[]NoRestrict / exclude retrieval domains
callback_urlstringNoURL called back on a terminal state

For the full schema, see CreateResearchRequest in the OpenAPI spec.

Example:

python
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:

phaseMeaning
briefParses the topic, settles the research goal
planPlans the retrieval paths
hitl_outlineOutline is ready, waiting for your approval (appears only when require_outline_approval=true)
gatherMultiple rounds of retrieval, extracting sources
crosscheckCross-checks across sources
synthesizeDrafts 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.

python
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:

FieldDescription
final_mdThe research report body, Markdown
final_artifact_idThe id of the report as an artifact; use the artifacts endpoints to fetch the original file
citations_countNumber of citations
confidence_summaryConfidence summary
partial_sectionsSections already produced when the run did not finish

Fetch an artifact:

python
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:

python
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 depth affects cost
  • API Overview — shared conventions; the full field set is in the OpenAPI spec