# ClinicalTrials.gov Scraper & API - Trials by Condition & Phase (`neverempty/clinical-trials-scraper`) Actor

For pharma intelligence, site feasibility and RAG pipelines: trials from the official ClinicalTrials.gov v2 API, flattened from 12 nested modules, each start date labelled day, month or year. The registry holds 602,104 studies; a phase 2, 3 and 4 search returns 51,862 here, 2,597 naively

- **URL**: https://apify.com/neverempty/clinical-trials-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Developer tools, MCP servers, AI
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.84 / 1,000 study returneds

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

## ClinicalTrials.gov Scraper & API

**For pharma and biotech competitive intelligence, CRO site selection, patient recruitment and healthcare investors**: clinical trials from the US National Library of Medicine's own registry as flat rows — condition, phase, status, sponsor, locations and dates.

**The registry is far bigger than a search page can show you.** Measured against the live API on 2026-09-10 it held **602,104** studies; `cancer` alone matched **123,154** of them. Narrowing that one term: `RECRUITING` **18,755**, a Japan location **3,105**, phase 3 **10,989**.

**The registry's own phase filter silently throws your filters away. This Actor does not.** Asking for `phase:2,phase:3` returns **10,989** — phase 2 gone. `phase:2,phase:3,phase:4` returns **2,597** — only phase 4. The form this Actor sends returns **51,862**, which is all three. Dates are just as treacherous: of 200 cancer trials, **117 had a full date, 81 had only `2011-12`, and 2 had none**, so every date here carries the precision it actually has (`day`, `month`) instead of a `2011-12-01` nobody ever published.

No API key, no scraping, no proxy. A typo is reported as a typo, never as an empty result that looks like an answer. Export as JSON, CSV or Excel.

```json
{
  "condition": "breast cancer",
  "statuses": ["RECRUITING"],
  "phases": ["3"],
  "maxStudies": 200
}
```

### What this fixes about the raw API

Everything below was measured against the live API on 2026-09-10, not read from documentation.

#### 1. Four in ten start dates hold only a year and a month

Of 200 cancer trials, **117 had a full date, 81 had only `2011-12`, and 2 had none**. A scraper that parses
these into a date either drops them or invents a day the registry never published.

Here the date is passed through exactly as the registry wrote it, and a second column says how precise it is:

| `startDate` | `startDatePrecision` |
|---|---|
| `2011-12-16` | `day` |
| `2011-12` | `month` |
| *(absent)* | `unknown` |

So you can sort by it, and you can also tell which rows you are allowed to sort to the day.

#### 2. An empty search returns the entire registry

Leave every filter blank and the API happily returns all **602,104** studies. A blank field or a variable that
did not get set becomes a bill. This Actor never sends an unnarrowed search: if you supply nothing at all it
falls back to the condition `diabetes`, says so in the log, and writes the search it actually ran into the
`matchedQuery` column of every row. If you supply anything - a drug, a sponsor, a location, a status, a phase -
that is used on its own and nothing is added to it.

#### 3. Picking two phases means OR, not "the last one you picked"

The API takes several values for one facet separated by a space; a comma between them silently keeps only the
last. Measured on `cancer`:

| what is sent | studies matched |
|---|---|
| `phase:2` | 39,750 |
| `phase:3` | 10,989 |
| `phase:2,phase:3` (the comma form) | **10,989** - phase 2 gone |
| `phase:2,phase:3,phase:4` | **2,597** - only phase 4 |
| `phase:2 3 4` (what this Actor sends) | **51,862** |

Ticking Phase 2, 3 and 4 with the comma form returns 5 per cent of the matching trials and says nothing. Here
the values for one facet are joined with a space and different facets with a comma, so both AND and OR behave
the way the input schema says they do.

#### 4. A term the registry does not know returns zero, not an error

`query.cond=zzznotadisease` answers HTTP 200 with `totalCount: 0`. A typo therefore looks exactly like a
disease nobody studies. That empty result comes back as its own row saying the search worked and matched
nothing - which is not the same row you get when a filter value was rejected (HTTP 400, with the registry's
reason) or when the request simply failed. None of those three rows is charged.

#### 5. `pageSize` is silently capped at 1,000

Ask for 2,000 and you get 1,000, with no error and no warning. Paging here is done against what actually came
back rather than what was requested, so a run never reports more studies than it received.

#### 6. Twelve nested modules become one row

The API returns each study as twelve nested objects. This flattens them into one row per study, with lists
kept as lists: `conditions`, `phases`, `interventionNames`, `countries`, `collaborators`, `keywords`. A study
with no phase - every observational study - gets an empty list rather than an invented `N/A`.

### What you get

`nctId`, `url`, `briefTitle`, `officialTitle`, `overallStatus`, `whyStopped`, `studyType`, `phases`,
`enrollmentCount`, `enrollmentType`, `conditions`, `keywords`, `interventionTypes`, `interventionNames`,
`leadSponsor`, `leadSponsorClass`, `collaborators`, `startDate`, `startDatePrecision`, `completionDate`,
`completionDatePrecision`, `firstPostedDate`, `lastUpdatePostedDate`, `sex`, `minimumAge`, `maximumAge`,
`healthyVolunteers`, `standardAges`, `locationCount`, `countries`, `firstLocationFacility`,
`firstLocationCity`, `briefSummary`.

### Input

| Field | What it does |
|---|---|
| `condition` | Condition or disease, e.g. `diabetes`, `breast cancer`. |
| `intervention` | Drug, device or procedure, e.g. `semaglutide`, `CAR-T`. |
| `term` | Free text across the whole record, e.g. `CRISPR`. |
| `location` | Country, state or city with a study site, e.g. `Japan`. |
| `sponsor` | Organisation running the study, e.g. `Pfizer`, `NIH`. |
| `statuses` | Recruitment status. An invalid value is rejected by the registry with its reason, not ignored. |
| `phases` | Trial phase. Observational studies have none and are excluded by this filter. |
| `studyType` | Interventional or observational. |
| `maxStudies` | How many to return. `cancer` alone matched 123,154 studies, so this is what keeps a run affordable. |
| `maxRetries` | Attempts per request. HTTP 400 is permanent and is never retried. |

### Scale

Measured on 2026-09-10: the registry held 602,104 studies. `cancer` matched 123,154 of them. Each filter
applied on its own to `cancer` gave: `RECRUITING` 18,755, a Japan location 3,105, phase 3 10,989 - so the
filters do narrow the search rather than being accepted and ignored. (These are separate figures, not a
sequence.)

### Source and licence

ClinicalTrials.gov API v2 (`clinicaltrials.gov/api/v2`). Works of the US federal government are in the public
domain. This Actor makes plain HTTPS requests to the documented JSON endpoints; it does not scrape any web page
and needs no API key or proxy.

# Actor input Schema

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

The condition to search for, such as diabetes, breast cancer or Parkinson's disease. Leave it empty and search by intervention, sponsor, location or term instead - nothing is added to your search that you did not ask for. A term the registry does not recognise returns zero studies rather than an error, so a typo looks exactly like a disease nobody studies; that empty result is returned as its own row saying so.

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

A drug, device or procedure, such as metformin, semaglutide or CAR-T.

## `term` (type: `string`):

Free text searched across the whole record - useful for a technique or gene name that is neither a condition nor an intervention, such as CRISPR.

## `location` (type: `string`):

A country, state or city where the study has a site, such as Japan, California or Berlin.

## `sponsor` (type: `string`):

The organisation running the study, such as Pfizer, NIH or Mayo Clinic. The lead sponsor and its class (industry, NIH, other) come back on every row.

## `statuses` (type: `array`):

Keep only studies in these statuses. An invalid value is rejected by the registry with HTTP 400 and the reason, rather than being ignored.

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

Keep only these phases. Observational studies have no phase at all and come back with an empty list rather than a made-up value, so filtering by phase excludes them.

## `studyType` (type: `string`):

int = interventional (a trial that gives a treatment), obs = observational (a study that watches without intervening). Leave empty for both.

## `maxStudies` (type: `integer`):

How many studies to return. You are charged for the rows you actually receive. The registry holds over 600,000 studies and a broad condition can match tens of thousands - cancer matched 123,154 when this was built - so this limit is what keeps a run affordable.

## `maxRetries` (type: `integer`):

How many times to try a request in total when the registry does not answer. HTTP 400 is a permanent answer and is never retried.

## Actor input object example

```json
{
  "condition": "diabetes",
  "intervention": "",
  "term": "",
  "location": "",
  "sponsor": "",
  "statuses": [],
  "phases": [],
  "studyType": "",
  "maxStudies": 200,
  "maxRetries": 4
}
```

# Actor output Schema

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

One row per clinical trial, plus one row when the registry rejected the search, matched nothing, or could not be read.

# 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 = {
    "condition": "diabetes",
    "statuses": [],
    "phases": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/clinical-trials-scraper").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 = {
    "condition": "diabetes",
    "statuses": [],
    "phases": [],
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/clinical-trials-scraper").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 '{
  "condition": "diabetes",
  "statuses": [],
  "phases": []
}' |
apify call neverempty/clinical-trials-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/wz9CUYJERbJlLSm0S/builds/CerfSFCqvkVCeZXue/openapi.json
