# EU Clinical Trials (CTIS) Scraper (`crawlerbros/eu-ctis-scraper`) Actor

Scrape the EU Clinical Trials Information System (CTIS) - the EMA's official register of clinical trials across the EU/EEA. Search by title, condition, sponsor, status, phase, therapeutic area, country, and more, or look up full trial detail by EU CT number. Public REST API - no auth, no proxy.

- **URL**: https://apify.com/crawlerbros/eu-ctis-scraper.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Developer tools, Integrations
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## EU Clinical Trials (CTIS) Scraper

Scrape the **EU Clinical Trials Information System (CTIS)** — the European Medicines Agency's official public register of clinical trials authorised across the EU and EEA. Search by title, medical condition, sponsor, trial status, phase, therapeutic area, country, age group, and more, or look up complete trial detail by EU CT number. HTTP-only via the public euclinicaltrials.eu JSON API. No auth, no proxy required.

### What this actor does

- **Two modes:** `search` (filtered trial listing) and `byTrialId` (full detail for one or more EU CT numbers)
- **Rich server-side filters:** title, condition, sponsor, EU CT number, trial status, phase, therapeutic area, EU/EEA member state, age group, gender, sponsor type, trial region, rare disease, low intervention, orphan designation, has-results
- **Full trial detail on demand:** objectives, endpoints, recruitment dates, per-country site counts, product names
- **Trial URL included** — direct link to the public CTIS trial page
- **Empty fields are omitted** — no `null`, `""`, `[]`, or `{}` in the output

### Output per trial (mode = `search`)

- `ctNumber` — EU CT number, e.g. `2023-509723-41-00`
- `title`, `shortTitle`
- `condition` — medical condition(s) under study
- `sponsor`, `sponsorType`
- `therapeuticAreas[]` — MeSH-derived therapeutic area(s)
- `countries[]` — EU/EEA member states concerned
- `status`, `statusCode` — human-readable status + its numeric code
- `phase` — trial phase
- `ageGroup`, `gender`
- `trialRegion` — EEA only / non-EEA only / both
- `products[]` — investigational medicinal product name(s)
- `totalNumberEnrolled`
- `primaryEndPoint`, `endPoint`
- `decisionDate`, `decisionDateDetail`, `startDate`, `endDate`, `endDateDetail`, `lastUpdated`, `lastPublicationUpdate`
- `hasResults` — whether results have been submitted
- `trialUrl` — public CTIS trial page
- `recordType: "trial"`, `scrapedAt`

### Output per trial (mode = `byTrialId`)

- `ctNumber`, `title`, `shortTitle`
- `status`, `statusCode`
- `decisionDate`, `publishDate`, `startDate`, `endDate`
- `trialRegion`, `sponsor`
- `conditions[]`, `therapeuticAreas[]`, `products[]`
- `isLowIntervention`
- `mainObjective`, `primaryEndPoint`
- `estimatedRecruitmentStartDate`, `estimatedEndDate`
- `countries[]`, `totalNumberEnrolled` (summed across reporting member states)
- `hasResults`
- `trialUrl`
- `recordType: "trialDetail"`, `scrapedAt`

Trials that fail to resolve (invalid EU CT number format, or a number not found in the public register) are still emitted as a typed record with `recordType: "error"` and an `error` message — they are never silently dropped.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `search` | `search` / `byTrialId` |
| `title` | string | – | Free-text search against the trial title (mode=search) |
| `medicalCondition` | string | – | Free-text search against condition(s) (mode=search) |
| `sponsorName` | string | – | Free-text search against sponsor name (mode=search) |
| `ctNumberQuery` | string | – | Free-text / partial EU CT number search (mode=search) |
| `status` | array | `[]` | One or more trial statuses (12 options) |
| `trialPhase` | array | `[]` | One or more trial phases (11 options) |
| `therapeuticArea` | array | `[]` | One or more therapeutic areas (58 options) |
| `ageGroup` | array | `[]` | One or more participant age groups (4 options) |
| `gender` | array | `[]` | One or more participant genders |
| `countries` | array | `[]` | One or more EU/EEA member states (30 options) |
| `trialRegion` | string | – | EEA only / non-EEA only / both (note: since CTIS is the EU/EEA trial register, "non-EEA only" trials are rare-to-nonexistent in practice) |
| `sponsorType` | array | `[]` | One or more sponsor organisation types |
| `hasResultsOnly` | boolean | `false` | Only trials with published results |
| `isRareDiseaseOnly` | boolean | `false` | Only rare-disease trials |
| `isLowInterventionOnly` | boolean | `false` | Only low-intervention trials |
| `hasOrphanDesignationOnly` | boolean | `false` | Only trials with an orphan-designated product |
| `ctNumbers` | array | `[]` | EU CT numbers to fetch (mode=byTrialId) |
| `sortBy` | string | `decisionDate` | Sort search results by `decisionDate` or `ctNumber` (mode=search) |
| `sortDirection` | string | `DESC` | `ASC` or `DESC`, used with `sortBy` (mode=search) |
| `maxItems` | integer | `5` | Hard cap on emitted records (1–2000) |

#### Example: search by condition and status

```json
{
  "mode": "search",
  "medicalCondition": "leukemia",
  "status": ["2", "3", "4"],
  "maxItems": 25
}
```

#### Example: search by therapeutic area and country

```json
{
  "mode": "search",
  "therapeuticArea": ["4"],
  "countries": ["724", "250"],
  "trialPhase": ["4", "5"],
  "maxItems": 50
}
```

#### Example: rare-disease trials with published results

```json
{
  "mode": "search",
  "isRareDiseaseOnly": true,
  "hasResultsOnly": true,
  "maxItems": 20
}
```

#### Example: lookup by EU CT number

```json
{
  "mode": "byTrialId",
  "ctNumbers": ["2023-509723-41-00", "2024-517420-20-00"]
}
```

### Use cases

- **Pharma competitive intelligence** — track competitor trials by sponsor, therapeutic area, or phase across the EU
- **Regulatory monitoring** — watch trial status transitions (authorised, halted, suspended, ended) for a therapy area
- **Market access research** — identify which EU/EEA member states host trials for a given condition
- **Academic research** — bulk-export CTIS records for meta-analysis or systematic review
- **Patient advocacy** — find ongoing rare-disease or orphan-designation trials enrolling participants
- **Investment research** — monitor clinical development pipelines by sponsor or product

### FAQ

**What is the data source?**
The European Medicines Agency's Clinical Trials Information System (CTIS) public portal at euclinicaltrials.eu — the EU's official register of clinical trials since January 2023, covering all EU/EEA member states.

**Is this affiliated with the EMA or CTIS?**
No. This is an independent third-party actor built on CTIS's public read API. It is not endorsed by or affiliated with the European Medicines Agency.

**Do I need an account or API key?**
No. CTIS's trial search and detail data is fully public — no login or credentials required.

**What's the difference between `search` and `byTrialId` mode?**
`search` returns a filtered list of trials matching your criteria (title, condition, sponsor, status, etc.). `byTrialId` returns the complete detail record for one or more specific trials when you already know their EU CT number.

**Why do some trials only return a `status` of "Not authorised" or "Cancelled" with limited fields?**
CTIS discloses fewer public fields for trials that were withdrawn, refused, or cancelled before authorisation — this actor surfaces every field CTIS makes public for each trial state.

**How fresh is the data?**
Every run queries the live CTIS API directly, so results reflect the current public register at run time.

**Why is my EU CT number rejected in `byTrialId` mode?**
EU CT numbers follow the fixed format `YYYY-NNNNNN-NN-NN` (e.g. `2023-509723-41-00`). Numbers that don't match this shape, or that aren't found in the public register, are returned as a typed error record explaining why.

**Can I combine multiple filters in `search` mode?**
Yes — all filters are combined with AND logic. For example, `therapeuticArea` + `countries` + `trialPhase` together narrow to trials matching all three.

# Actor input Schema

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

What to fetch.

## `title` (type: `string`):

Free-text search against the trial's public title (mode=search).

## `medicalCondition` (type: `string`):

Free-text search against the trial's medical condition(s) (mode=search).

## `sponsorName` (type: `string`):

Free-text search against the sponsor organisation name (mode=search).

## `ctNumberQuery` (type: `string`):

Free-text search against the EU CT number, e.g. `2023-509723` (mode=search). For an exact single-trial lookup use mode=byTrialId instead.

## `status` (type: `array`):

Restrict to one or more overall trial statuses. Empty = no filter (mode=search).

## `trialPhase` (type: `array`):

Restrict to one or more clinical trial phases. Empty = no filter (mode=search).

## `therapeuticArea` (type: `array`):

Restrict to one or more MeSH-derived CTIS therapeutic areas. Empty = no filter (mode=search).

## `ageGroup` (type: `array`):

Restrict to one or more participant age groups. Empty = no filter (mode=search).

## `gender` (type: `array`):

Restrict to trials enrolling one or more genders. Empty = no filter (mode=search).

## `countries` (type: `array`):

Restrict to trials with a site in one or more EU/EEA member states concerned. Empty = no filter (mode=search).

## `trialRegion` (type: `string`):

Restrict to trials run only in the EEA, only outside it, or in both. Empty = no filter (mode=search).

## `sponsorType` (type: `array`):

Restrict to one or more sponsor organisation types. Empty = no filter (mode=search).

## `hasResultsOnly` (type: `boolean`):

Only emit trials that have published results (mode=search).

## `isRareDiseaseOnly` (type: `boolean`):

Only emit trials targeting a rare disease (mode=search).

## `isLowInterventionOnly` (type: `boolean`):

Only emit trials classified as low-intervention (mode=search).

## `hasOrphanDesignationOnly` (type: `boolean`):

Only emit trials involving a product with orphan drug designation (mode=search).

## `ctNumbers` (type: `array`):

One or more EU CT numbers to fetch full trial detail for, e.g. `2023-509723-41-00`.

## `sortBy` (type: `string`):

Field to sort search results by.

## `sortDirection` (type: `string`):

Ascending or descending, used together with `Sort by`.

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

Hard cap on emitted records.

## Actor input object example

```json
{
  "mode": "search",
  "status": [],
  "trialPhase": [],
  "therapeuticArea": [],
  "ageGroup": [],
  "gender": [],
  "countries": [],
  "sponsorType": [],
  "hasResultsOnly": false,
  "isRareDiseaseOnly": false,
  "isLowInterventionOnly": false,
  "hasOrphanDesignationOnly": false,
  "ctNumbers": [],
  "sortBy": "decisionDate",
  "sortDirection": "DESC",
  "maxItems": 5
}
```

# Actor output Schema

## `trials` (type: `string`):

Dataset containing all scraped CTIS trial records.

# 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 = {
    "mode": "search",
    "status": [],
    "trialPhase": [],
    "therapeuticArea": [],
    "ageGroup": [],
    "gender": [],
    "countries": [],
    "sponsorType": [],
    "hasResultsOnly": false,
    "isRareDiseaseOnly": false,
    "isLowInterventionOnly": false,
    "hasOrphanDesignationOnly": false,
    "ctNumbers": [],
    "sortBy": "decisionDate",
    "sortDirection": "DESC",
    "maxItems": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/eu-ctis-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 = {
    "mode": "search",
    "status": [],
    "trialPhase": [],
    "therapeuticArea": [],
    "ageGroup": [],
    "gender": [],
    "countries": [],
    "sponsorType": [],
    "hasResultsOnly": False,
    "isRareDiseaseOnly": False,
    "isLowInterventionOnly": False,
    "hasOrphanDesignationOnly": False,
    "ctNumbers": [],
    "sortBy": "decisionDate",
    "sortDirection": "DESC",
    "maxItems": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/eu-ctis-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 '{
  "mode": "search",
  "status": [],
  "trialPhase": [],
  "therapeuticArea": [],
  "ageGroup": [],
  "gender": [],
  "countries": [],
  "sponsorType": [],
  "hasResultsOnly": false,
  "isRareDiseaseOnly": false,
  "isLowInterventionOnly": false,
  "hasOrphanDesignationOnly": false,
  "ctNumbers": [],
  "sortBy": "decisionDate",
  "sortDirection": "DESC",
  "maxItems": 5
}' |
apify call crawlerbros/eu-ctis-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/eu-ctis-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/eiKBH78fbCDK5PiWf/builds/Qp1aDdYqwbFrRZhf9/openapi.json
