# Research runs

> Statuses, retries, refunds, depth, and monitoring — the run lifecycle in full


Base URL + auth as everywhere: `Authorization: Bearer ds_live_...`.

## Lifecycle

`queued → running → completed | failed | cancelled` (with `cancelling` between a
cancel request and its terminal state). The enum is closed — build exhaustive
matches safely.

- **completed** — results persisted with citations; `run.completed` webhook fires.
- **failed** — `error.code`/`error.message` on the resource; the run charge is
  automatically refunded; `run.failed` webhook fires. Don't blindly re-create —
  read the message first.
- **cancelled** — partial results may exist; the charge stands (work was done).

## Cost & latency

A run bills prepaid credits when it starts (typically $10 standard / $20 max
depth — see [pricing](/pricing), or `/billing` for your own account) and takes
**15-60 minutes**.

**One run researches one topic area** and fills your Blueprint with what it
finds. How many rows that yields depends on the area, so don't budget from an
entity count — price the specific run:

```bash
curl -s -X POST $BASE_URL/v1/research/runs/estimate \
  -H "Authorization: Bearer $DEEPSIEVE_API_KEY" -H "Content-Type: application/json" \
  -d '{"query": "solid-state battery startups in Japan", "depth": "standard"}'
```

Your **first real run is free** (one per account, before you subscribe) and
isn't consumed if it fails.

Failed runs are refunded automatically; cancelled runs are charged (work was
done). Spending is capped by the prepaid balance and any per-key budget — an
agent can hit `402`, never an invoice.

## Top up a report instead of re-running it

If you already have a report and want to extend it, pass `additive_to` rather
than starting a fresh run. Entities that report already holds with in-TTL
evidence are dropped before the run is batched, so **you pay for the gap, not
the list** — re-sending 20 held seeds plus 6 new ones costs about the 6.
There is no cap on how many seeds you send: price scales with the list, so a
longer one simply costs more and you approve it first.

```bash
curl -s -X POST $BASE_URL/v1/research/runs \
  -H "Authorization: Bearer $DEEPSIEVE_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"seeds": ["newco.example", "otherco.example"],
       "additive_to": "<existing_run_id>"}'
```

This is the right shape for a recurring loop — a quarterly refresh, a watchlist
you keep topping up. Without it, re-sending the full list starts a new run that
re-researches, and re-charges for, everything you already had. `404` means that
id isn't a run in your workspace.

**Price it first.** `additive_to` works on `/runs/estimate` too, and it narrows
the list exactly the way the charge does — so the quote is the gap, not the list
you sent:

```bash
curl -s -X POST $BASE_URL/v1/research/runs/estimate \
  -H "Authorization: Bearer $DEEPSIEVE_API_KEY" -H "Content-Type: application/json" \
  -d '{"seeds": ["newco.example", "otherco.example"],
       "additive_to": "<existing_run_id>"}'
```

The response carries `skipped_already_held` when anything was dropped — that is
why the number is lower than the list you sent, and worth logging so a surprise
looks like an explanation rather than a bug.

## Reuse what we already have

If the workspace already holds current data on some of your seeds, you can keep
it instead of paying to research those entities again:

```bash
curl -s -X POST $BASE_URL/v1/research/runs \
  -H "Authorization: Bearer $DEEPSIEVE_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"seeds": ["acme.example", "newco.example"], "reuse_existing": true}'
```

Entities we already cover with **in-TTL** evidence are carried into the new
report as they stand, with their sources, and cost no run credit. Anything stale
is researched again regardless — reuse never passes off out-of-date data as
current. The skipped entities come back as `reused_existing` so a cheaper run
than your seed list implies is explained rather than mysterious.

This is the lineage-free sibling of `additive_to`: use `reuse_existing` when you
just want whatever we have, and `additive_to` when you specifically mean "extend
that report".

## Depth

`"depth": "standard"` (default) or `"max"` — max lifts research budgets and
uses the deeper agent preview; it costs more per run.

## Monitoring

`"monitored": true` (default) keeps the run's target re-verified after the
initial research, on a paid plan: every report is covered for its first 30 days,
then while monitoring stays on. That is the "living dataset". Your plan keeps a set number
of units fresh at no charge; each unit beyond that allowance bills per day
(see [pricing](/pricing) for your plan's rate, or `/billing` for your account).

**The unit is a DOSSIER, not a report** — one Deep Research call's returned data.
A 20-entity request is researched as 4 dossiers, so it is 4 run credits AND 4
monitoring units, even though it reads as one report. Budget for the second
number as well as the first: it is the recurring one. When a monitored value is
re-verified and **changes**, a `dataset.updated` webhook fires with the
record/column/evidence ids. Toggle per target later in the app.

### See everything you're paying to monitor

An agent that creates reports on a schedule is the caller most likely to
accumulate overlapping monitoring: re-researching a watchlist into a fresh
report, rather than topping up the old one, is the natural loop — and it
silently doubles the recurring cost. `GET /v1/research/monitoring` is how you
audit that in one call.

```bash
curl -s $BASE_URL/v1/research/monitoring \
  -H "Authorization: Bearer $DEEPSIEVE_API_KEY"
```

```json
{
  "object": "monitoring_overview",
  "monitored_units": 12,
  "billing_units": 8,
  "duplicated_units": 4,
  "reports": [
    {"run_id": "…", "label": "EU vendors", "units": 4, "entities": 20,
     "in_free_window": false, "free_until": "2026-07-30", "billing": true,
     "fully_covered_by": ["<other_run_id>"]},
    {"run_id": "…", "label": "Watchlist", "units": 4, "entities": 18,
     "in_free_window": true, "free_until": "2026-09-04", "billing": false,
     "fully_covered_by": []}
  ],
  "warnings": [{"level": "INFO", "code": "monitoring_fully_duplicated", "…": "…"}]
}
```

Three things worth wiring into a scheduled job:

- **`in_free_window` and `billing`** are separate flags, not one status. The
  first means the report is inside its free 30 days and costs nothing yet
  (`free_until` is the date that ends); the second means it is past that and
  spending `units` daily. The word "monitored" hides that difference, and it is
  the difference that shows up on an invoice next month.
- **`fully_covered_by`** lists reports whose in-TTL entity set is a *superset*
  of this one's — everything here is already kept fresh there, so these units
  buy nothing. Only reported for reports actually `billing`.
- **`billing_units` vs `monitored_units`** is what you are charged for versus
  what you are watching. `duplicated_units` is the gap you could reclaim.

Advisory only: `warnings` carries `INFO` entries and nothing is turned off on
your behalf. Deciding which report keeps the coverage is a judgement call about
which one your pipeline reads from, and we won't guess it.

## Attribution & budgets

Runs created with an API key record that key (`api_key_id`) for audit and
budget purposes. Keys can carry `budget_runs`/`budget_cents` ceilings —
exceeding one returns `402 budget_exceeded`.

## Questions about a finished run

Ask the research agent directly — see
[Chat with the research agent](/developers/chat). Your questions appear in the
customer's own chat history for that report, attributed to your API key.
