# Clinical Trials Monitor - New Trials & Status Change Alerts (`datadeltas/clinical-trials-monitor`) Actor

Change feed for ClinicalTrials.gov. Watch conditions, sponsors and keywords and get one record per NEW trial registration and per STATUS CHANGE (Not yet recruiting → Recruiting → Completed) on the trials you already watch, with the previous status on the record.

- **URL**: https://apify.com/datadeltas/clinical-trials-monitor.md
- **Developed by:** [Daniel Matthee](https://apify.com/datadeltas) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.50 / 1,000 trial event detecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Clinical Trials Monitor — ClinicalTrials.gov New Trials & Status Changes

The **change feed** for ClinicalTrials.gov. Watch the conditions, sponsors and
keywords you care about; every scheduled run emits one record per **new trial
registration** and per **status change** on a trial you are already watching —
carrying the previous status on the record.

The registry has plenty of scrapers that dump studies on demand. This actor
answers the question a dump cannot: **what changed since I last looked?** No
API key, no login — it reads the official ClinicalTrials.gov API v2.

### Two kinds of record

| `eventType` | When it fires |
|---|---|
| `new-trial` | A trial matching your watch was **first posted** to the registry inside the window, and this monitor has never reported it before. |
| `status-change` | A trial this monitor already reported has **changed recruitment status** — e.g. `NOT_YET_RECRUITING` → `RECRUITING`, or `RECRUITING` → `COMPLETED`. `oldStatus` carries the previous value. |

A trial is announced **exactly once**, and it is never both a new trial and a
status change in the same run: a newly detected trial's status is stored
silently and transitions are reported from the next run onwards.

### The first run seeds status tracking

Run 1 delivers your backfill of new trials (everything first posted inside
`sinceDays`) and quietly records each one's current status. Status changes are
reported from run 2 on, so you are never billed for a wall of "changes" that
are just today's registry state. An empty dataset on a scheduled run means
nothing in your watch moved.

Recommended schedule: **daily** or weekly. Registrations post every business
day; the registry adds roughly 2,000 new studies a fortnight across all
conditions.

### Example output record

```json
{
  "nctId": "NCT07751042",
  "eventType": "new-trial",
  "title": "Phase 1/2 Study of Intravenous Injection of STX-003 in Advanced Solid Tumors as Monotherapy or in Combination With Pembrolizumab",
  "sponsor": "Strand Therapeutics Inc.",
  "conditions": ["Advanced Solid Tumor"],
  "phase": "PHASE1/PHASE2",
  "overallStatus": "RECRUITING",
  "oldStatus": null,
  "enrollmentCount": 220,
  "startDate": "2026-06-30",
  "studyFirstPostDate": "2026-08-06",
  "lastUpdatePostDate": "2026-08-06",
  "url": "https://clinicaltrials.gov/study/NCT07751042",
  "detectedAt": "2026-08-31T07:14:02Z"
}
```

A status change on the same trial would arrive as:

```json
{
  "nctId": "NCT07751042",
  "eventType": "status-change",
  "overallStatus": "ACTIVE_NOT_RECRUITING",
  "oldStatus": "RECRUITING",
  "...": "same columns as above"
}
```

`oldStatus` is `null` on `new-trial` rows so the column shape never varies.
`phase` is the study's phase list joined with `/` (`PHASE1/PHASE2`), `NA` for
studies recorded as not applicable, and empty for observational and
expanded-access studies that state no phase.

### Input options

| Field | Default | What it does |
|---|---|---|
| `conditions` | — | Conditions/diseases to watch, e.g. `melanoma`, `type 2 diabetes`. Each becomes its own registry condition search. |
| `sponsors` | — | Sponsor names to watch, e.g. `Pfizer`. Matches lead and collaborating sponsors. |
| `keywords` | — | Free-text terms searched across the whole study record (drug codes, mechanisms), e.g. `CAR-T`. Broader than a condition search. |
| `phases` | all | Only emit new trials in these phases. Studies with no stated phase are excluded whenever this is set. |
| `overallStatuses` | all | Only emit new trials currently in these statuses. Status *changes* are always reported for every watched trial, whatever this says. |
| `trackStatusChanges` | `true` | Turn off to receive new registrations only. |
| `sinceDays` | `14` | Days of newly registered trials to scan, by first-posted date. Your backfill size on run 1. |
| `maxItems` | `500` | Cap on records per run, split evenly between the two event types (unused half goes to the other). Records over the cap are held back and emitted on the next run, never dropped. |
| `stateStoreName` | `clinical-trials-monitor-state` | Named key-value store holding reported trial ids and their last known statuses. Change it to reset the monitor. |

`conditions`, `sponsors` and `keywords` are **OR'd** — each becomes its own
registry query and the results are merged and de-duplicated by NCT id. Give
none of them and the monitor watches *every* new registration in the window,
which is where `phases` and `overallStatuses` earn their keep. `phases` and
`overallStatuses` narrow every watch.

### Reliability

- **Announced once, never twice.** Reported NCT ids are persisted, so a
  re-run on the same day emits nothing — even though the registry's dates
  have day granularity and cannot be used as a watermark.
- **Never a fake transition.** Status changes are reported only from a stored
  previous value against a freshly read one. A trial the registry does not
  return this run keeps its last known status; a failed status refresh leaves
  every snapshot untouched and reports nothing.
- **Partial-failure safe.** One failing watch is logged and skipped while the
  others still emit; if *every* watch fails the run fails loudly, so a dead
  run never looks like a quiet day.
- **Cost control that loses nothing.** `maxItems` is split evenly between new
  trials and status changes, so a busy week of registrations cannot silence
  status tracking; whichever half is quiet lends its share to the other.
  Anything over the cap stays pending and emits on the next run — new trials
  are left unreported, and a held-back status change keeps its previously
  stored status so the same transition is re-detected. Schema drift is logged
  with the field named and never crashes a run.
- **Paid for only what you received.** Records are pushed to the dataset
  *before* any state is written, so a failed push means the next run reports
  the same events again rather than losing them.
- Only the official ClinicalTrials.gov API v2 is read — no scraping, no key.

### Use cases

- **Competitive intelligence** — "tell me when anyone registers a new phase 2
  or phase 3 trial in my indication", and when a rival's trial flips to
  `COMPLETED` (read-out coming) or `TERMINATED` (programme trouble).
- **CRO and site business development** — watch `sponsors` for newly
  registered studies and reach the sponsor while the protocol is still being
  operationalised, not after enrolment closed.
- **Patient recruitment and advocacy** — watch a condition for trials moving
  into `RECRUITING` so you point patients only at studies actually enrolling.
- **Healthcare investing** — a pipeline asset slipping from
  `ACTIVE_NOT_RECRUITING` to `SUSPENDED`, or an enrolment date moving, is a
  dated, citable signal with the NCT id attached.

### Pricing

Pay per event: a small per-run fee plus a fee per record emitted. Quiet weeks
cost almost nothing; busy ones deliver exactly the changes worth acting on.

### Maintenance

Field paths are checked against the live API v2 payload; drift is logged
clearly and never crashes a run. The state format is stable, so updates keep
your history.

### Changelog

- **0.1** — initial release: new-trial detection by condition, sponsor and
  keyword watches, plus status-change tracking with the previous status on the
  record.

# Actor input Schema

## `conditions` (type: `array`):

Conditions to watch, e.g. 'melanoma', 'type 2 diabetes'. Each becomes its own registry search of the condition field. Combined with sponsors and keywords as OR - a trial matching any watch is in scope.

## `sponsors` (type: `array`):

Sponsor names to watch, e.g. 'Pfizer', 'Memorial Sloan Kettering'. Matches lead and collaborating sponsors. Each name becomes its own registry search.

## `keywords` (type: `array`):

Free-text terms searched across the whole study record (intervention names, drug codes, mechanisms), e.g. 'CAR-T', 'GLP-1'. Broader than the condition search. Each term becomes its own registry search.

## `phases` (type: `array`):

Only emit NEW trials in these phases. Leave empty for all. Note that observational and expanded-access studies state no phase and are excluded whenever this filter is set (add 'NA' to include studies explicitly recorded as not applicable).

## `overallStatuses` (type: `array`):

Only emit NEW trials currently in these statuses. Leave empty for all. Status CHANGES are always reported for every watched trial regardless of this filter - a trial moving out of your chosen status is exactly the news you want.

## `trackStatusChanges` (type: `boolean`):

Also emit a record whenever a trial this monitor has already reported changes recruitment status (e.g. Recruiting to Completed), carrying the previous value in 'oldStatus'. Turn off to receive new registrations only.

## `sinceDays` (type: `integer`):

How many days of newly registered trials to scan, by first-posted date. On the first run this is your backfill; afterwards only trials never reported before are emitted.

## `maxItems` (type: `integer`):

Cap on records emitted in one run (cost control), split evenly between the two event types so neither can starve the other; whichever half is quiet lends its unused share to the other. Nothing over the cap is lost: unreported new trials and held-back status changes both stay pending in state and emit on the next run.

## `stateStoreName` (type: `string`):

Named key-value store holding the reported trial ids and their last known statuses. Change it to reset the monitor and re-emit the backfill.

## Actor input object example

```json
{
  "conditions": [
    "melanoma"
  ],
  "phases": [],
  "overallStatuses": [],
  "trackStatusChanges": true,
  "sinceDays": 14,
  "maxItems": 500,
  "stateStoreName": "clinical-trials-monitor-state"
}
```

# Actor output Schema

## `records` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "conditions": [
        "melanoma"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datadeltas/clinical-trials-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "conditions": ["melanoma"] }

# Run the Actor and wait for it to finish
run = client.actor("datadeltas/clinical-trials-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "conditions": [
    "melanoma"
  ]
}' |
apify call datadeltas/clinical-trials-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datadeltas/clinical-trials-monitor"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/LAv7nzhb02cxdJNp2/builds/bVdpDLT4fQYxqs0lU/openapi.json
