# ClinicalTrials.gov Scraper (Clinical Trial & Study Data) (`steadfetch/clinicaltrials-gov-extractor`) Actor

ClinicalTrials.gov trial records as JSON: NCT ID, title, status, conditions, interventions and phases when provided, enrollment, dates, sponsor, locations. Filter: condition, intervention, status, phase or date window. Registry metadata only - no participant data. For trial-landscape research.

- **URL**: https://apify.com/steadfetch/clinicaltrials-gov-extractor.md
- **Developed by:** [Steadfetch](https://apify.com/steadfetch) (community)
- **Categories:** Business, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 study delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## ClinicalTrials.gov Scraper (Clinical Trial & Study Data)

Extract clinical trial records from the official ClinicalTrials.gov API v2: registry status, phase, conditions, interventions, enrollment, dates, sponsors and locations as structured JSON. Study and sponsor metadata only, with no participant or contact data.

### What it does

This Actor queries the official, keyless ClinicalTrials.gov API v2 (`https://clinicaltrials.gov/api/v2/studies`) and returns one dataset item per clinical study. It is an official-API extractor, not a web scraper: no browser, login, proxy or CAPTCHA is involved.

Choose a last-update window (`since`, default the last 30 days, and optional `until`), then narrow the results by condition, intervention, overall status and phase. Pages are requested in `LastUpdatePostDate` descending order with an opaque `pageToken`; up to 1,000 studies are requested per page and up to 5,000 per run. Studies are deduplicated by NCT id within the run, so a record repeated across pages is delivered and charged once.

`mode: "extractor"` (default) delivers every matching study. `mode: "monitor"` remembers the NCT ids delivered by previous runs that share the same `stateKey` in the named key-value store `clinicaltrials-gov-monitor-state` and delivers only new studies. Failed requests, retries, empty pages and studies already emitted are never charged as results.

The data comes from the public registry maintained by the U.S. National Library of Medicine. Per the NLM Terms and Conditions, results carry the attribution "Courtesy of the U.S. National Library of Medicine"; NLM does not endorse this Actor. The Actor returns the studies that match the requested last-update window, so a dataset can lag the live registry and may not reflect the most current data. Use a recent `since`, or no `until`, when you need the freshest records.

Only study-level and sponsor-organisation metadata is returned. Participant records, contact persons, investigator names, emails and phone numbers are never requested or mapped.

**Input:** `since` (default: last 30 days, on LastUpdatePostDate), `until`, `condition`, `intervention`, `status`, `phase`, `maxResults` (default 1000, maximum 5000), `mode` (`extractor` or `monitor`), `stateKey` and `strictSchema` (default true).

**Output:** one dataset item per study with `nct_id`, `brief_title`, `official_title`, `overall_status`, `study_type`, `phases`, `conditions`, `interventions`, `enrollment_count`, `start_date`, `primary_completion_date`, `completion_date`, `lead_sponsor`, `collaborators`, `locations`, `last_update_post_date`, `source_url` and `source_attribution`, plus the common `url`, `title` and `scrapedAt` envelope. Format: JSON dataset.

### Example output

Real record produced from the committed API response for `NCT02684435` (fixture captured on 2026-10-20):

```json
{
    "url": "https://clinicaltrials.gov/study/NCT02684435",
    "title": "Contrast-enhanced Ultrasound of the Kidney",
    "scrapedAt": "2026-10-20T08:30:00.000Z",
    "nct_id": "NCT02684435",
    "brief_title": "Contrast-enhanced Ultrasound of the Kidney",
    "official_title": "Contrast-enhanced Ultrasound for Complex Kidney Lesion Diagnosis in Patients With CKD (CEUS CKD)",
    "overall_status": "COMPLETED",
    "study_type": "INTERVENTIONAL",
    "phases": ["PHASE2"],
    "conditions": ["Chronic Kidney Disease", "Cystic Kidney Disease"],
    "interventions": ["DRUG: Perflutren lipid microsphere"],
    "enrollment_count": 63,
    "start_date": "2016-02",
    "primary_completion_date": "2018-07-09",
    "completion_date": "2018-07-09",
    "lead_sponsor": "University of North Carolina, Chapel Hill",
    "collaborators": [
        "Vanderbilt University",
        "North Carolina Translational and Clinical Sciences Institute"
    ],
    "locations": [
        { "country": "United States", "state": "North Carolina", "city": "Chapel Hill" },
        { "country": "United States", "state": "Tennessee", "city": "Nashville" }
    ],
    "last_update_post_date": "2019-09-26",
    "source_url": "https://clinicaltrials.gov/study/NCT02684435",
    "source_attribution": "Courtesy of the U.S. National Library of Medicine"
}
```

Partial registry dates are preserved exactly as published, so `start_date` can be `2016-02` as well as `2016-02-18`.

### Pricing

Pay-per-event pricing (`.actor/pay_per_event.json`, referenced from `actor.json` as `payPerEvent`):

| Event | Charged when | Price |
| --- | --- | --- |
| `apify-actor-start` | a run starts (synthetic, automatic) | 0.00005 USD per run (Apify-recommended default; includes 5 seconds of free compute) |
| `apify-default-dataset-item` | a study is saved to the dataset (synthetic, automatic) | 0.003 USD per result = **3.00 USD per 1,000 results** |

There is no custom per-item event and no `Actor.charge()` call: the platform already charges `apify-default-dataset-item` per pushed item, so adding another event would bill users twice. Failed requests, retries, empty pages and studies already emitted by a monitor run are not charged as results. No proxy is required and no proxy cost is added.

The price sits inside the direct-competition band for ClinicalTrials.gov data on the Apify Store (about 0.01-12 USD per 1,000 studies) and inside Steadfetch's 1-10 USD band.

Measured cost basis per 1,000 results (no browser, no proxy): one API page request (page size 1,000) plus JSON parsing and schema validation. The local fixture pipeline processed and validated 1,000 studies in 74 ms of compute, about USD 0.0000011 at 256 MB and Apify's USD 0.20 per GB-hour compute rate. Even a conservative 60 seconds of total runtime would cost about USD 0.00085 per 1,000 results, so the cost basis is below USD 0.01 per 1,000 results and the gross margin is above 99%. The first paid platform run will replace this local measurement with the billed figure.

### FAQ

**Do I need an API key, account or login?**
No. The ClinicalTrials.gov API v2 studies endpoint is public and keyless. The Actor needs no credentials.

**Does it need a browser?**
No. The source returns structured JSON, so plain HTTP is roughly 10x cheaper than a browser-based Actor.

**Does it scrape the ClinicalTrials.gov website?**
No. It uses the official API v2 endpoint and never parses the public search pages.

**Does it return personal data?**
No. Only study-level metadata and sponsor-organisation names are mapped. Participant records, contact persons, investigators, emails and phone numbers are never requested.

**How does monitor mode avoid charging twice?**
It stores the NCT ids it has delivered under `stateKey` in the named key-value store `clinicaltrials-gov-monitor-state` (the default store is run-scoped, so a named store is required to survive scheduled runs) and skips those ids on later runs. Only new studies reach the dataset and trigger the per-result event.

**What happens when no study matches?**
The run finishes successfully with an empty dataset. Only the start event applies; there is no per-result charge.

**Are partial dates supported?**
Yes. ClinicalTrials.gov publishes some dates as `YYYY-MM` or `YYYY`. They are preserved as published instead of being invented or rounded.

**Is the output schema stable?**
Yes. New fields are additive and the required fields in `.actor/dataset_schema.json` do not change.

### Maintenance

**Maintenance status:** the Actor is registered in the Steadfetch daily catalog QA workflow. The label `Tested daily. Last verified: {YYYY-MM-DD}.` will be published only after the first successful scheduled run is logged. Until then no verified date is claimed.

The source is versioned by the public ClinicalTrials.gov API v2. Committed API fixtures and schema tests catch response-shape changes in CI, and any break in the daily run is reported as an issue in the Steadfetch Actors repository.

### Keywords

clinical trials, ClinicalTrials.gov, clinical study data, trial status, clinical trial API, JSON dataset

### Input reference

Minimal input (the specification test case):

```json
{
    "since": "2026-10-01",
    "maxResults": 10
}
```

Filtered monitor input:

```json
{
    "since": "2026-09-20",
    "condition": "breast cancer",
    "status": "RECRUITING",
    "phase": "PHASE2",
    "maxResults": 1000,
    "mode": "monitor",
    "stateKey": "breast-cancer-phase2"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `since` | string | Optional `YYYY-MM-DD` lower bound on LastUpdatePostDate. Default: last 30 days. |
| `until` | string | Optional `YYYY-MM-DD` upper bound on LastUpdatePostDate. Default: no upper bound. |
| `condition` | string | Filter by condition or disease. |
| `intervention` | string | Filter by intervention or treatment. |
| `status` | string | One overall status or a comma-separated list, for example `RECRUITING,ACTIVE_NOT_RECRUITING`. |
| `phase` | string | `EARLY_PHASE1`, `PHASE1`-`PHASE4` or `NA`. Short forms such as `2` are accepted. |
| `maxResults` | integer | Default 1000, maximum 5000. |
| `mode` | string | `extractor` (default) or `monitor`. |
| `stateKey` | string | Key-value store key used to remember emitted NCT ids in monitor mode. |
| `strictSchema` | boolean | Default `true`; fail the run on schema violations. |

### Development

```bash
npm install
npm test                # lint + typecheck + offline Vitest suites (no credit)
npm run publish:check   # Steadfetch publish gate
npm run build && npm run verify:build   # production entrypoint guard
npm run schemas:validate
CLINICALTRIALS_LIVE_TESTS=1 npm run test:live   # optional live API check (no Apify credit)
```

The default test suite runs against committed API fixtures and never touches the external network or Apify. The live suite is opt-in. For a credit-free end-to-end run, point `CLINICALTRIALS_API_BASE_URL` at a loopback server that serves `test/fixtures/studies-page-*.json` and run `apify run --input ...`; production always defaults to the official API host.

Project structure:

```
.actor/actor.json          Actor definition (payPerEvent, dataset schema, output schema)
.actor/input_schema.json   INPUT options (dates, condition, intervention, status, phase, maxResults, mode)
.actor/dataset_schema.json Item JSON Schema (fields) + table view for the Store
.actor/pay_per_event.json  PPE pricing (two synthetic platform events)
src/fetch.ts               ClinicalTrials.gov API v2 client: filters, pagination, retry/backoff
src/extract.ts             Pure extraction and normalization (fixture-tested, no network)
src/dedupe.ts              Pure NCT-id dedupe and monitor-state merge helpers
src/monitor-state.ts       Named key-value store persistence for monitor mode
src/main.ts                Entry point: fetch, monitor filter, validate, push
src/validate-output.ts     Ajv validator shared by the run and the tests
test/                      Offline API fixtures, Vitest suites and an optional live test
```

# Actor input Schema

## `since` (type: `string`):

ISO date (YYYY-MM-DD) applied to LastUpdatePostDate. Defaults to the last 30 days.

## `until` (type: `string`):

Optional ISO date (YYYY-MM-DD) upper bound for LastUpdatePostDate. Defaults to no upper bound.

## `condition` (type: `string`):

Filter by condition or disease, for example diabetes or breast cancer.

## `intervention` (type: `string`):

Filter by intervention or treatment, for example metformin.

## `status` (type: `string`):

Filter by overall status. Accepts one value or a comma-separated list, for example RECRUITING,ACTIVE_NOT_RECRUITING.

## `phase` (type: `string`):

Filter by phase: EARLY_PHASE1, PHASE1, PHASE2, PHASE3, PHASE4 or NA. Short forms such as 2 or phase 2 are accepted.

## `maxResults` (type: `integer`):

Maximum number of studies to save to the dataset.

## `mode` (type: `string`):

extractor: deliver every matching study (deduplicated by NCT id within the run). monitor: deliver only studies not emitted by a previous run with the same stateKey.

## `stateKey` (type: `string`):

Key-value store key that remembers emitted NCT ids in monitor mode. Use a different key per saved filter set.

## `strictSchema` (type: `boolean`):

Fail the run when an item does not match the OUTPUT schema. Keep enabled: Steadfetch never publishes Actors without output-schema validation.

## Actor input object example

```json
{
  "since": "2026-10-01",
  "until": "2026-10-20",
  "condition": "breast cancer",
  "intervention": "metformin",
  "status": "RECRUITING",
  "phase": "PHASE2",
  "maxResults": 10,
  "mode": "extractor",
  "stateKey": "clinicaltrials-monitor-state",
  "strictSchema": true
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset items validated against the item schema in dataset_schema.json (fields).

# 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 = {
    "since": "2026-10-01",
    "until": "2026-10-05",
    "maxResults": 10,
    "mode": "extractor",
    "strictSchema": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadfetch/clinicaltrials-gov-extractor").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 = {
    "since": "2026-10-01",
    "until": "2026-10-05",
    "maxResults": 10,
    "mode": "extractor",
    "strictSchema": True,
}

# Run the Actor and wait for it to finish
run = client.actor("steadfetch/clinicaltrials-gov-extractor").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 '{
  "since": "2026-10-01",
  "until": "2026-10-05",
  "maxResults": 10,
  "mode": "extractor",
  "strictSchema": true
}' |
apify call steadfetch/clinicaltrials-gov-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadfetch/clinicaltrials-gov-extractor"
        }
    }
}
```

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/ZgozegDpkVzm4tY2K/builds/BUFqZ0gQD3A7sW4tr/openapi.json
