> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fuseai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Turn a Deep Research run into a targeted campaign

> Start a Fuse Deep Research run from one prompt, follow it live, then turn its report into campaign knowledge and its results into an enriched outreach list.

Some markets are hard to reach through a people database: local businesses, niche segments, or companies defined by something they did rather than their size or industry. Deep Research sends an AI agent to the open web and Fuse's [data providers](/research/providers) with a plain-English brief. It returns a written report and, when it finds them, a list of the people or companies that match. This workflow starts a run, follows its progress, turns the report into knowledge your AI campaigns write from, and turns the list into contacts you can reach.

```mermaid theme={null}
%%{init: {"flowchart": {"rankSpacing": 20, "nodeSpacing": 20}}}%%
flowchart TD
  A[Research prompt] -->|runId| B[Run and events]
  B -->|resultListId| C[Results list]
  B -->|reportMarkdown| D[Knowledge hub page]
  C -->|listId| E[AI campaign]
  D -->|grounds| E
```

## Before you start

| You need | Why |
| - | - |
| An API key with the `research`, `lists` and `campaigns` [scopes](/concepts/scopes), plus `search` if the results are companies | The run, the results list and the campaign each sit on a different surface. |
| Provider keys (optional) | Most providers run on Fuse's own accounts with no setup. A few, such as Apollo, need your own key, added in the app under **Settings → Provider Keys**. A run can only use the providers available to your workspace. |
| Your team's knowledge hub, finished building | The report becomes a page on it. See [Knowledge hubs](/guides/knowledge-hubs). |
| Credits | 20 per provider step on Fuse's keys, capped by `effort` at 1,000, 2,000 or 5,000, and 5 per step on your own key, on top of the cap. |

## Step by step

<Steps>
  <Step title="Check which providers the run can use">
    A run chooses its own providers, so there is nothing to pick per run. `GET /business/research/providers` shows which ones your runs can reach:

    ```bash theme={null}
    curl https://api.tryfuse.ai/api/v1/business/research/providers -u "$FUSE_API_KEY:"
    ```

    ```json theme={null}
    {
      "providers": [
        { "id": "apollo", "label": "Apollo", "keyMode": "byok-required", "hasUserKey": false, "source": "none", "allowedInRuns": true },
        { "id": "zerobounce", "label": "ZeroBounce", "keyMode": "platform", "hasUserKey": false, "source": "platform", "allowedInRuns": true }
      ]
    }
    ```

    A provider with `source: "none"` needs your own key before runs can use it. Adding keys for the ones that cover your market gives the run more sources to draw on.
  </Step>

  <Step title="Start the run">
    Describe who you want, what makes them a fit, and the fields you need back. Send an `X-Idempotency-Key` header so a retry returns the same run instead of starting and paying for a second one:

    ```bash theme={null}
    curl -X POST https://api.tryfuse.ai/api/v1/business/research/runs \
      -u "$FUSE_API_KEY:" \
      -H "Content-Type: application/json" \
      -H "X-Idempotency-Key: texas-dental-groups-2026-10" \
      -d '{
        "prompt": "Find independent dental groups in Texas with five or more clinics that opened a new location in 2026. For each, give the website, the number of clinics, the city of the newest clinic, and the name and LinkedIn profile of the CEO or COO.",
        "effort": "medium"
      }'
    ```

    ```json theme={null}
    { "run": { "runId": "68a205cc9c41d20014b40888", "status": "queued" } }
    ```

    Prompts can run to 2,000 characters. `effort` caps what the run spends on Fuse's provider keys: `low` (1,000), `medium` (2,000, the default) or `high` (5,000). Steps on your own keys are billed on top. Asking for named people and their LinkedIn profiles makes the results list directly usable.
  </Step>

  <Step title="Follow the run as it works">
    A run usually takes 5 to 20 minutes, and it never pauses for approval: it moves from `queued` to `running` and then finishes. Its events show what it is doing. Pass the last `seq` you saw as `sinceSeq` to get only new events:

    ```bash theme={null}
    curl -G https://api.tryfuse.ai/api/v1/business/research/runs/68a205cc9c41d20014b40888/events \
      -u "$FUSE_API_KEY:" \
      --data-urlencode "sinceSeq=2"
    ```

    ```json theme={null}
    {
      "runId": "68a205cc9c41d20014b40888",
      "events": [
        { "seq": 3, "type": "run.status_changed", "payload": { "from": "queued", "to": "running" }, "ts": "2026-10-02T10:00:41.000Z" },
        { "seq": 4, "type": "tool.started", "payload": { "tool": "web_search", "input": { "query": "Texas dental group new clinic 2026" } }, "ts": "2026-10-02T10:01:05.000Z" },
        { "seq": 5, "type": "progress", "payload": { "message": "Reviewing 31 candidate groups" }, "ts": "2026-10-02T10:02:10.000Z" }
      ],
      "nextSinceSeq": null,
      "status": "running"
    }
    ```

    Stop when `status` is `completed`, `failed` or `cancelled`. If the events show the run heading the wrong way, cancel it early with `POST /business/research/runs/{runId}/cancel`: provider steps already completed stay billed, and the rest of the reservation is released.
  </Step>

  <Step title="Read the report and the results list">
    ```bash theme={null}
    curl https://api.tryfuse.ai/api/v1/business/research/runs/68a205cc9c41d20014b40888 -u "$FUSE_API_KEY:"
    ```

    ```json theme={null}
    {
      "run": {
        "runId": "68a205cc9c41d20014b40888",
        "status": "completed",
        "effort": "medium",
        "spend": { "reservedCredits": 0, "settledCredits": 890, "capCredits": 2000 },
        "resultListId": "68a205cc9c41d20014b40999",
        "reportMarkdown": "# Texas dental groups opening new clinics\n\n14 groups matched...",
        "errorCode": null
      }
    }
    ```

    `reportMarkdown` is the written report. `resultListId` is the list of people or companies the run surfaced, and it is `null` when the research produced a report only. Read the list with `GET /business/lists/{listId}` and check its `entityType`.
  </Step>

  <Step title="Turn the report into campaign knowledge">
    Add the report to your knowledge hub as a page, so AI campaigns can draw on what the research found:

    ```bash theme={null}
    curl -X POST https://api.tryfuse.ai/api/v1/business/knowledge-hubs/68a1f8509c41d20014b3ee41/pages \
      -u "$FUSE_API_KEY:" \
      -H "Content-Type: application/json" \
      -d '{
        "title": "Research: Texas dental groups opening new clinics",
        "category": "icp-use-cases",
        "content": "# Texas dental groups opening new clinics\n\n14 groups matched..."
      }'
    ```

    The page is indexed before the call answers, and later rebuilds of the hub never overwrite it. Content can run to 60,000 characters, and the hub must have finished building. Its owner can add pages, and so can anyone on your team when it is the team's shared hub.
  </Step>

  <Step title="Turn the results into reachable contacts">
    What you do next depends on the kind of list:

    * **A contact list:** find work emails with `POST /business/lists/{listId}/enrich`, `mode: "email"` and a `limit`, then poll `GET /business/jobs/{jobId}` with the `jobId` from the `202` until `status` is `completed`. See [Working with lists](/guides/working-with-lists#enrich-the-contacts-you-have).
    * **A company list:** read its domains with `GET /business/lists/{listId}/filter-values?field=domain`, then find the people you sell to with a people search on `job_company_website`, saved with `enrich: "email"`. [Funding round outreach](/workflows/funding-round-outreach) shows that step in full.
  </Step>

  <Step title="Launch a campaign grounded in the research">
    Create an AI campaign on the contact list. Leave `knowledgeHubId` out and the campaign is grounded on your team's hub, which now holds the report. Pass it only for a hub you own: any other id answers `404 KNOWLEDGE_HUB_NOT_FOUND`.

    ```bash theme={null}
    curl -X POST https://api.tryfuse.ai/api/v1/business/campaigns \
      -u "$FUSE_API_KEY:" \
      -H "Content-Type: application/json" \
      -d '{
        "campaignName": "Texas dental groups - new clinics",
        "campaignType": "ai-generated",
        "listId": "68a205cc9c41d20014b40999",
        "channels": ["email", "email"],
        "ctaLink": "https://example.com/demo",
        "userPrompt": "These groups just opened a new clinic. Offer help standardizing scheduling and billing across locations. Reference their expansion, briefly.",
        "excludeContacted": "last_3_months"
      }'
    ```

    Then wait for initialization, preview and approve as in steps 7 to 9 of [Build an AI outbound campaign from a prompt](/workflows/ai-campaign-from-a-prompt).
  </Step>
</Steps>

## What it costs

| Step | Credits |
| - | - |
| The research run | 20 per successful provider step on Fuse's keys, never more than the `effort` cap, and 5 per step on your own key, on top of the cap. `spend.settledCredits` shows what the Fuse-key steps cost. |
| Finding emails for a contact list | 20 to 50 per email found |
| Finding people at a company list's domains | 2 per row saved with `enrich: "none"`, or 20 to 50 per email found with `enrich: "email"` |
| Adding the report to a knowledge hub, reading runs and events | Free |

## Troubleshooting

| Symptom | Cause | Fix |
| - | - | - |
| `429 RATE_LIMITED` when starting a run | The research engine allows 5 run starts a minute and 100 a day. | Wait for `Retry-After`. Reuse your `X-Idempotency-Key` so the retry cannot start a duplicate. |
| `status: "failed"` | The run could not finish. `errorCode` says why, for example `STALE_TIMEOUT`. | Start it again, and quote the `runId` to [support](mailto:support@fuseai.com) if it keeps failing. |
| `resultListId` is `null` | The run wrote a report but surfaced no named people or companies. | Ask for named companies or people, and the fields you need, in the prompt. |
| The results are thin | Few providers covering this market are available to your workspace. | Add provider keys in the app under **Settings → Provider Keys**. |
| `409 RUN_ALREADY_TERMINAL` on cancel | The run had already finished. | Nothing to cancel. |
| `409 KNOWLEDGE_HUB_NOT_READY` adding the page | The hub is still building, or never finished. | Wait for the hub's `status` to be `complete`. |

## Run the whole workflow

<Accordion title="Python script: research run to grounded campaign">
  ```python theme={null}
  """Run Deep Research, keep the report as hub knowledge, and campaign the people it found."""
  import os
  import time

  import requests

  BASE = "https://api.tryfuse.ai/api/v1/business"
  AUTH = (os.environ["FUSE_API_KEY"], "")
  HUB_ID = os.environ["FUSE_KNOWLEDGE_HUB_ID"]  # your team's knowledge hub, already built
  PROMPT = (
      "Find independent dental groups in Texas with five or more clinics that opened a new location in "
      "2026. For each, give the website, the number of clinics, the city of the newest clinic, and the "
      "name and LinkedIn profile of the CEO or COO."
  )
  FINISHED = ("completed", "failed", "cancelled")


  class FuseError(Exception):
      def __init__(self, status, code, message):
          super().__init__(f"{status} {code}: {message}")
          self.code = code


  def call(method, path, **kwargs):
      """Send one request, wait out 429s, and raise FuseError on any other failure."""
      while True:
          response = requests.request(method, BASE + path, auth=AUTH, timeout=60, **kwargs)
          if response.status_code == 429:
              time.sleep(int(response.headers.get("Retry-After", 5)))
              continue
          if response.status_code >= 400:
              try:
                  error = response.json().get("error", {})
              except ValueError:
                  error = {}
              raise FuseError(response.status_code, error.get("code"), error.get("message"))
          return response.json() if response.content else None


  def wait_for_enrichment(job_id, delay=10, max_delay=60):
      """Poll an enrichment job until it ends. A stopped job waits for a top-up and a resume in the app."""
      while True:
          time.sleep(delay)
          job = call("GET", f"/jobs/{job_id}")["job"]
          if job["status"] in ("completed", "cancelled", "failed", "stopped"):
              return job
          delay = min(delay * 1.5, max_delay)


  # 2. Start the run; the idempotency key makes a retried start return the same run
  run_id = call("POST", "/research/runs",
                json={"prompt": PROMPT, "effort": "medium"},
                headers={"X-Idempotency-Key": "texas-dental-groups-2026-10"})["run"]["runId"]

  # 3. Follow the events until the run finishes
  since = 0
  while True:
      time.sleep(20)
      page = call("GET", f"/research/runs/{run_id}/events", params={"sinceSeq": since})
      for event in page["events"]:
          since = event["seq"]
          if event["type"] in ("run.status_changed", "progress"):
              print(event["type"], event["payload"])
      if page["status"] in FINISHED and page["nextSinceSeq"] is None:
          break

  # 4. Read the report and the results list
  run = call("GET", f"/research/runs/{run_id}")["run"]
  if run["status"] != "completed":
      raise SystemExit(f"Run {run['status']}: {run['errorCode']}")
  print(f"Spent {run['spend']['settledCredits']} of {run['spend']['capCredits']} credits")

  # 5. Keep the report as knowledge the campaign can draw on
  call("POST", f"/knowledge-hubs/{HUB_ID}/pages", json={
      "title": "Research: Texas dental groups opening new clinics",
      "category": "icp-use-cases",
      "content": run["reportMarkdown"][:60000],
  })

  # 6. Make the results reachable (a company list needs a people search instead)
  if not run["resultListId"]:
      raise SystemExit("The run found no named people or companies; the report is on the hub.")
  results = call("GET", f"/lists/{run['resultListId']}")["list"]
  if results["entityType"] != "contactList":
      raise SystemExit("Company results: search for people at their domains, as in Funding round outreach.")
  try:
      started = call("POST", f"/lists/{results['id']}/enrich",
                     json={"mode": "email", "limit": results["totalRecords"]})
      if started.get("jobId"):
          print("Enrichment", wait_for_enrichment(started["jobId"])["status"])
  except FuseError as error:
      if error.code != "ALL_CONTACTS_ALREADY_ENRICHED":
          raise

  # 7. Create the AI campaign, grounded on the team's hub (preview and approve it as in the first workflow)
  campaign_body = {
      "campaignName": "Texas dental groups - new clinics",
      "campaignType": "ai-generated",
      "listId": results["id"],
      "channels": ["email", "email"],
      "ctaLink": "https://example.com/demo",
      "userPrompt": "These groups just opened a new clinic. Offer help standardizing scheduling and "
                    "billing across locations. Reference their expansion, briefly.",
      "excludeContacted": "last_3_months",
  }
  for attempt in range(5):
      try:
          campaign_id = call("POST", "/campaigns", json=campaign_body)["campaignId"]
          break
      except FuseError as error:
          if error.code != "LIST_INCOMPATIBLE_WITH_CHANNELS" or attempt == 4:
              raise
          time.sleep(90)  # reachability stats catch up a little after enrichment
  print("Draft campaign:", campaign_id)
  ```
</Accordion>

## Do it from Claude

With the [Fuse MCP server](/mcp/connect) connected:

> Run a Deep Research on independent dental groups in Texas with five or more clinics that opened a new location in 2026, including each group's CEO or COO. When it finishes, summarize the report, find work emails for the people it found, and draft an AI campaign on that list. Don't launch it.

The assistant starts the run with `deep_research_start`, checks on it with `deep_research_status`, then uses the enrichment and campaign tools. The report and the results list also appear in the Fuse app.

## Related

<CardGroup cols={2}>
  <Card title="Data providers" icon="database" href="/research/providers">
    The providers Deep Research can call, and how to add your own keys.
  </Card>

  <Card title="Long-running work" icon="hourglass-half" href="/concepts/long-running-work">
    Following research events and every other background job.
  </Card>

  <Card title="Knowledge hubs" icon="book" href="/guides/knowledge-hubs">
    Pages, notes and how hubs ground AI campaigns.
  </Card>

  <Card title="Funding round outreach" icon="sack-dollar" href="/workflows/funding-round-outreach">
    Going from a company list to the people who work there.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.