
Clinical Trials Database
- 1.4k installs
- 2.6k repo stars
- Updated July 7, 2026
- google-deepmind/science-skills
clinical-trials-database is a Claude Code skill that teaches agents how to query ClinicalTrials.gov REST API v2 for real medical study data when developers build research agents, evidence-based tools, or health-tech prot
About
clinical-trials-database is a reference skill from google-deepmind/science-skills that documents ClinicalTrials.gov REST API v2 querying mechanics for developers building research agents and health-tech prototypes. The skill covers five endpoints—/studies, /studies/{nctId}, /studies/metadata, /studies/enums, and /studies/search-areas—plus ranked text search parameters such as query.cond for condition filters and advanced expression syntax. Developers reach for clinical-trials-database when an agent must search paginated trial listings, fetch a study by NCT ID, or discover available metadata fields and enum values instead of guessing API shapes. Version details are not pinned in the manifest, but the source explicitly targets API v2.
- Access 5 core REST endpoints including /studies and /studies/{nctId}
- Combines ranked text search with 7 query parameters such as condition, intervention, and sponsor
- Applies exact-match filters on overallStatus, phase, age-group, study-type and more
- Returns structured study records with NCT IDs, eligibility criteria, and outcome measures
- Supports advanced Essie expression syntax for complex boolean filtering
Clinical Trials Database by the numbers
- 1,394 all-time installs (skills.sh)
- +174 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #215 of 2,064 Data Science & ML skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/google-deepmind/science-skills --skill clinical-trials-databaseAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.4k |
|---|---|
| repo stars | ★ 2.6k |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 7, 2026 |
| Repository | google-deepmind/science-skills ↗ |
How do you query ClinicalTrials.gov API v2 from code?
Query ClinicalTrials.gov for real medical study data when building research agents, evidence-based tools, or health-tech prototypes.
Who is it for?
Backend and AI engineers wiring evidence-based health research features that need live trial registry data rather than static datasets.
Skip if: Developers who only need general web search or who are building non-clinical applications with no trial-registry requirement.
When should I use this skill?
The user asks to search ClinicalTrials.gov, filter trials by condition or NCT ID, or integrate ClinicalTrials.gov API v2 into an agent or health-tech prototype.
What you get
Paginated study search results, single-study JSON by NCT ID, metadata field lists, and enum value references for agent prompts.
- Paginated study search results
- Single-study JSON by NCT ID
- Metadata and enum reference notes
By the numbers
- Documents 5 ClinicalTrials.gov REST API v2 endpoints
- Targets ClinicalTrials.gov REST API v2 specifically
Files
Clinical Trials Database
Prerequisites
1. `uv`: Read the uv skill and follow its Setup instructions to ensure uv is installed and on PATH. 2. User Notification: If LICENSE_NOTIFICATION.txt does not already exist in this skill directory then (1) prominently notify the user to check the terms at https://clinicaltrials.gov/, then (2) create the file recording the notification text and timestamp.
Overview
Access worldwide clinical trial data from ClinicalTrials.gov via the REST API v2. The CLI script at scripts/clinical_trials_api.py wraps the API with dedicated flags for common filters (phase, age group, status, intervention, sponsor, etc.) so you rarely need to construct raw queries.
Core Rules
- Use the Wrapper: ALWAYS execute the provided helper scripts to query the
database rather than accessing the database directly. The scripts automatically enforce the required rate limit gracefully.
- Always use `--fields` — trial JSON records can be very large; restrict
to the data points you need.
- Use `--count-total` first — check result volume before fetching all
records.
- Paginate large result sets — use
--limitwith--page-tokento
iterate.
- Trust Search Filters: Do not manually re-filter results unless
explicitly asked to verify detailed eligibility.
- Notification: If this skill is used, ensure this is mentioned in the
output.
Context Efficiency Warning
Trial JSON records can be very large. Always use the --fields parameter to restrict the response to only the data points you need. After writing to file, read only the fields you need rather than the entire file.
[!TIP] Use references/studies_schema.md to identify exact field paths for--fields.Response Layout Summary
API responses contain a list of studies (usually in a studies[] array). Each study is split into protocolSection and optional resultsSection.
[!Tip] Use the shorthand aliases below with the --fields parameter torequest specific data and keep responses small.
Top-Level Fields
-
totalCount— Total studies matching query (integer) -
studies[]— Array of study objects -
nextPageToken— cursor string for pagination
Common Study Fields (and shorthand alias)
- Identification
-
protocolSection.identificationModule.nctId(NCTId) — Unique trial ID -
protocolSection.identificationModule.briefTitle(BriefTitle) — Short
title
- Status
-
protocolSection.statusModule.overallStatus(OverallStatus) —
Recruitment status
- Description
-
protocolSection.descriptionModule.briefSummary(BriefSummary) —
Short description
- Arms & Interventions
-
protocolSection.armsInterventionsModule.interventions
(ArmsInterventionsModule)
- Eligibility
-
protocolSection.eligibilityModule.eligibilityCriteria
(EligibilityCriteria) — Inclusion/Exclusion
-
protocolSection.eligibilityModule.stdAges(StdAge) — CHILD, ADULT,
etc.
Consult references/studies_schema.md for full paths (Locations, Outcomes, Results) and common --fields recipes.
Commands
Search for studies
Use for: finding trials by disease, drug, phase, status, age group, or any combination of these filters.
uv run scripts/clinical_trials_api.py search \
--condition "<disease>" \
--intervention "<drug_or_treatment>" \
--status "<status>" \
--phase "<phase>" \
--age-group "<age_group>" \
--study-type "<study_type>" \
--sponsor "<sponsor_name>" \
--has-results \
--sort "<field>:<asc|desc>" \
--fields "<fields>" \
--limit <N> \
--count-total \
--page-token "<token>" \
--output /tmp/search_results.jsonAll flags are optional and combine via AND logic.
Flag reference:
-
--condition— Disease or condition to search for (e.g. `"cystic
fibrosis"`).
-
--intervention— Drug, device, or treatment name (e.g."pembrolizumab"). -
--status— Recruitment status filter. Values: RECRUITING, COMPLETED,
NOT_YET_RECRUITING, ACTIVE_NOT_RECRUITING, ENROLLING_BY_INVITATION, TERMINATED, SUSPENDED, WITHDRAWN.
-
--phase— Trial phase filter. Values: PHASE1, PHASE2, PHASE3, PHASE4,
EARLY_PHASE1, NA.
-
--age-group— Patient age group filter. Values: CHILD (0–17), ADULT
(18–64), OLDER_ADULT (65+).
-
--study-type— Type of study. Values: INTERVENTIONAL, OBSERVATIONAL,
EXPANDED_ACCESS.
-
--sponsor— Lead sponsor or institution name (e.g. `"National Cancer
Institute"`).
-
--has-results— Boolean flag (no value needed). When present, filters for
studies that have results available on ClinicalTrials.gov.
-
--sort— Sort order asFieldName:ascorFieldName:desc. Common fields:
LastUpdatePostDate, EnrollmentCount, StudyFirstPostDate, StartDate.
-
--fields— Comma-separated list of JSON field names to include in the
response. Use this to keep responses small (e.g. "NCTId,BriefTitle,OverallStatus,Phase"). See references/studies_schema.md for available field paths.
-
--limit— Maximum number of studies to return per request (1–1000, default
10).
-
--count-total— Boolean flag (no value needed). When present, the response
includes a totalCount field showing the total number of matching studies across all pages.
-
--page-token— An opaque cursor string used to fetch the next page of
results. Obtain this value from the nextPageToken field in a previous search response. Do not construct this string yourself; always copy it verbatim from the API response. See the Pagination section below.
-
--advanced— Raw Essie filter expression for structured queries beyond the
dedicated flags (e.g. "AREA[LocationCountry]United States"). Combined with other flags via AND. See references/clinical_trials_api.md for syntax.
-
--output— (Required) File path where the JSON response is written.
Example — actively recruiting Phase 3 pediatric cystic fibrosis trials:
uv run scripts/clinical_trials_api.py search \
--condition "cystic fibrosis" \
--status RECRUITING \
--phase PHASE3 \
--age-group CHILD \
--fields "NCTId,BriefTitle,OverallStatus,Phase" \
--limit 10 \
--output /tmp/cf_trials.jsonExample — recruiting atezolizumab trials for esophageal cancer:
uv run scripts/clinical_trials_api.py search \
--condition "esophageal cancer" \
--intervention "Atezolizumab" \
--status RECRUITING \
--fields "NCTId,BriefTitle,Phase" \
--limit 10 \
--output /tmp/atezolizumab_trials.jsonRetrieve a study by NCT ID
Use for: fetching full details of a specific trial when you already have the NCT identifier.
uv run scripts/clinical_trials_api.py get-study \
<nct_id> [--fields "<fields>"] \
--output /tmp/study.jsonReturns a useful default set of fields if --fields is omitted: NCTId,BriefTitle,OverallStatus,Phase,BriefSummary, ConditionsModule,ArmsInterventionsModule,EligibilityModule
Structure of the default response:
{
"protocolSection": {
"identificationModule": {
"nctId": "NCT00000000",
"briefTitle": "Study Title"
},
"statusModule": {
"overallStatus": "RECRUITING"
},
"descriptionModule": {
"briefSummary": "This study is about..."
},
"conditionsModule": {
"conditions": [ "Condition Name" ]
},
"armsInterventionsModule": {
"interventions": [ { "type": "DRUG", "name": "Drug Name" } ]
},
"eligibilityModule": {
"eligibilityCriteria": "Inclusion:\n- ...",
"stdAges": [ "ADULT" ]
}
}
}Get eligibility / inclusion criteria
Use for: pulling inclusion/exclusion rules, age ranges, and sex requirements for patient-matching tasks.
uv run scripts/clinical_trials_api.py \
get-eligibility <nct_id> \
--output /tmp/eligibility.jsonShortcut that returns title and the full eligibility module (inclusion/exclusion criteria, age range, sex).
Example — inclusion criteria for NCT04886804:
uv run scripts/clinical_trials_api.py \
get-eligibility NCT04886804 \
--output /tmp/eligibility_NCT04886804.jsonCount matching studies
Use for: exploring the trial landscape — checking how many trials exist for a condition, phase, or status before fetching full records.
uv run scripts/clinical_trials_api.py count \
--condition "<disease>" \
[--status "<status>"] [--phase "<phase>"] ... \
--output /tmp/count.jsonReturns only the total count of clinical trials matching the search criteria without fetching study records. Accepts the same filter flags as search.
Search by location / geography
Use for: narrowing trials to a specific country, state, or city.
Use --advanced with AREA[LocationCountry] or AREA[LocationCity] to restrict results by geography:
uv run scripts/clinical_trials_api.py search \
--condition "cystic fibrosis" \
--status RECRUITING \
--advanced "AREA[LocationCity]New York" \
--fields "NCTId,BriefTitle" \
--limit 20 \
--output /tmp/nyc_cf_trials.jsonSearch by sponsor / organization
Use for: identifying a sponsor's or institution's trial portfolio.
Use --sponsor to find trials run by a specific institution or company:
uv run scripts/clinical_trials_api.py search \
--sponsor "National Cancer Institute" \
--fields "NCTId,BriefTitle,LeadSponsorName" \
--limit 20 \
--output /tmp/nci_trials.jsonCombined multi-criteria search
Use for: complex queries that layer multiple filters (condition and drug and phase and geography and sponsor, etc.).
All flags combine via AND, so you can layer conditions, interventions, status, phase, geography, and sponsor in a single query:
uv run scripts/clinical_trials_api.py search \
--condition "pancreatic cancer" \
--intervention "immunotherapy" \
--status RECRUITING \
--phase PHASE3 \
--advanced "AREA[LocationCountry]United States" \
--fields "NCTId,BriefTitle,Phase,LeadSponsorName" \
--limit 20 \
--output /tmp/panc_trials.jsonRaw API query (escape hatch)
Use for: uncommon endpoints or parameter combinations not covered by the dedicated flags.
uv run scripts/clinical_trials_api.py raw-query \
--endpoint <path> \
--params '<json_dict>' \
--output /tmp/raw_result.jsonPagination
When results exceed --limit, the response includes a nextPageToken. Pass it with --page-token to fetch the next page:
uv run scripts/clinical_trials_api.py search \
--condition "breast cancer" \
--status RECRUITING \
--limit 50 --count-total \
--output /tmp/breast_cancer_p1.json
uv run scripts/clinical_trials_api.py search \
--condition "breast cancer" \
--status RECRUITING \
--limit 50 --page-token "CAo=" \
--output /tmp/breast_cancer_p2.jsonAdvanced Querying
For complex filtering beyond the dedicated flags, use --advanced with an Essie expression.
What is an Essie Expression? Essie is the search engine powering ClinicalTrials.gov. An Essie expression is a structured query that targets specific fields (e.g., country, phase) rather than doing general keyword searches.
- `AREA[Field]Value`: Targets a specific field.
-
AREA[LocationCountry]United States -
AREA[Phase]PHASE3 - Boolean operators: Combine with
AND,OR,NOT. - `RANGE[min, max]`: For numeric/date fields (e.g.
RANGE[500, MAX]).
See references/clinical_trials_api.md for syntax and available fields.
It is combined with other flags via AND:
uv run scripts/clinical_trials_api.py search \
--condition "diabetes" \
--advanced "AREA[LocationCountry]United States \
AND AREA[EnrollmentCount]RANGE[500, MAX]" \
--fields "NCTId,BriefTitle,EnrollmentCount" \
--output /tmp/diabetes_us_large.jsonReferences
- API parameters, enum values, and Essie syntax:
references/clinical_trials_api.md
- JSON field paths and `--fields` recipes:
references/studies_schema.md
ClinicalTrials.gov API v2 Reference
This document covers the querying mechanics, parameter reference, valid enum values, and advanced expression syntax for the ClinicalTrials.gov REST API v2.
Endpoints
/studies— GET — Search and filter studies, returns paginated list/studies/{nctId}— GET — Retrieve a single study by NCT ID/studies/metadata— GET — List all available data fields/studies/enums— GET — List all enum types and valid values/studies/search-areas— GET — List searchable field areas
Query Parameters
Query parameters perform ranked text searches. They influence the relevance ordering of results.
query.cond(--condition) — Condition or diseasequery.intr(--intervention) — Intervention or treatment (drug, device,
etc.)
query.term(--term) — General search across all text fields (57 fields)query.titles(--title) — Study titles and acronymsquery.locn(--location) — Location-related fields (city, state, country,
facility)
query.spons(--sponsor) — Sponsor or collaborator namequery.id(--id) — Study identifiers (NCT ID, org study ID)
Filter Parameters
Filter parameters perform exact matching and do not affect relevance ranking.
filter.overallStatus(--status) — Recruitment status (comma-separated)filter.advanced(--advanced/--phase/--age-group/--study-type/
--sponsor) — Essie expression for structured filtering
filter.ids— Restrict to specific NCT IDsfilter.geo— Distance-based geographic filter
Control Parameters
fields(--fields) — Comma-separated list of fields to returnpageSize(--limit) — Results per page (1–1000, default 10)pageToken(--page-token) — Token for the next page of resultscountTotal(--count-total) — Iftrue, response includestotalCountsort(--sort) — Sort field and direction, e.g.LastUpdatePostDate:descformat— Response format:json(default) orcsv
Sortable Fields
Common sortable fields: LastUpdatePostDate, NumericChange, EnrollmentCount, StudyFirstPostDate, StartDate.
Format: FieldName:asc or FieldName:desc.
Valid Enum Values
Recruitment Status (filter.overallStatus)
RECRUITING— Currently enrolling participantsNOT_YET_RECRUITING— Approved but not yet enrollingACTIVE_NOT_RECRUITING— Ongoing but no longer enrollingENROLLING_BY_INVITATION— Enrolling by invitation onlyCOMPLETED— Study finishedSUSPENDED— Temporarily haltedTERMINATED— Stopped earlyWITHDRAWN— Pulled before enrollment
Phase
EARLY_PHASE1— Early Phase 1 (formerly Phase 0)PHASE1— Phase 1PHASE2— Phase 2PHASE3— Phase 3PHASE4— Phase 4 (post-marketing)NA— Not Applicable
Standard Age Group (StdAge)
CHILD— Birth to 17 yearsADULT— 18 to 64 yearsOLDER_ADULT— 65+ years
Study Type
INTERVENTIONAL— Tests a treatment or interventionOBSERVATIONAL— Observes outcomes without interventionEXPANDED_ACCESS— Treatment use outside of clinical trials
Sex
ALL— All sexes eligibleMALE— Males onlyFEMALE— Females only
Essie Expression Syntax (for filter.advanced)
The filter.advanced parameter accepts Essie expressions for structured, non-ranked filtering.
AREA Operator
Target a specific field: AREA[FieldName]Value
Examples:
AREA[Phase]PHASE3— Phase 3 trials onlyAREA[StdAge]CHILD— Trials accepting pediatric patientsAREA[StudyType]INTERVENTIONAL— Interventional studies onlyAREA[LeadSponsorName]Pfizer— Sponsored by PfizerAREA[LocationCountry]United States— Located in the USAREA[Sex]FEMALE— Female-only trials
Boolean Operators
Combine clauses with AND, OR, NOT:
AREA[Phase]PHASE3 AND AREA[StdAge]CHILDAREA[Phase]PHASE2 OR AREA[Phase]PHASE3NOT AREA[StudyType]OBSERVATIONAL
RANGE Operator
Filter date or numeric fields within a range:
AREA[StartDate]RANGE[01/01/2023, MAX]— Started on or after Jan 1, 2023AREA[EnrollmentCount]RANGE[100, 500]— Enrollment between 100 and 500AREA[CompletionDate]RANGE[MIN, 12/31/2025]— Completing before end of 2025
Use MIN and MAX for open-ended boundaries.
Response Data Structure
Study records are organised into hierarchical modules:
protocolSection
- identificationModule — NCT ID, titles, organisation
- statusModule — overall status, start / completion dates, last update
- sponsorCollaboratorsModule — lead sponsor, collaborators, responsible
party
- descriptionModule — brief summary, detailed description
- conditionsModule — conditions under study
- designModule — study type, phases, enrolment info
- armsInterventionsModule — study arms and interventions
- outcomesModule — primary and secondary outcomes
- eligibilityModule — inclusion / exclusion criteria, age / sex requirements
- contactsLocationsModule — contacts and site locations
- referencesModule — citations and links
derivedSection
- conditionBrowseModule — MeSH terms for conditions
- interventionBrowseModule — MeSH terms for interventions
resultsSection (when available)
- participantFlowModule — participant flow
- baselineCharacteristicsModule — baseline data
- outcomeMeasuresModule — outcome results
- adverseEventsModule — adverse events
hasResults
Boolean flag indicating whether results have been posted for the study.
Pagination
When results exceed the page size, the response includes a nextPageTokenfield. Pass this token via the pageToken parameter (or --page-token flag) to fetch the next page. The final page omits this token.
Response shape for multi-study queries:
{
"totalCount": 1234,
"studies": [...],
"nextPageToken": "CAo="
}Data Standards
- Dates — ISO 8601 structured objects,
e.g. {"date": "2024-03-15", "type": "ACTUAL"}
- Rich text — descriptive fields use CommonMark Markdown
- Enums — status, phase, study type, and similar fields use standardised
enumerated values (not free text)
ClinicalTrials.gov Study Data Schema
This reference documents the JSON structure and field paths returned by the ClinicalTrials.gov API v2. Use these paths with the --fields parameter to select specific data.
Top-Level Response
Multi-study query (/studies):
totalCount— integer (present when countTotal=true)studies[]— array of study objectsnextPageToken— string (omitted on final page)
Single-study query (/studies/{nctId}): returns a study object directly.
Study Object Structure
Each study has two major sections: protocolSection and resultsSection, plus a hasResults boolean.
Protocol Section
Identification Module
protocolSection.identificationModule.nctId(NCTId)
— Unique trial identifier
protocolSection.identificationModule.briefTitle(BriefTitle)
— Short public title
protocolSection.identificationModule.officialTitle(OfficialTitle)
— Full scientific title
protocolSection.identificationModule.organization.fullName(Organization)
— Sponsoring organization
Status Module
protocolSection.statusModule.overallStatus(OverallStatus)
— Recruitment status. Values: RECRUITING, NOT_YET_RECRUITING, ACTIVE_NOT_RECRUITING, ENROLLING_BY_INVITATION, COMPLETED, SUSPENDED, TERMINATED, WITHDRAWN
protocolSection.statusModule.startDateStruct.date(StartDate)
— Study start date
protocolSection.statusModule.primaryCompletionDateStruct.date
(PrimaryCompletionDate) — Primary outcome completion
protocolSection.statusModule.completionDateStruct.date(CompletionDate)
— Full study completion
protocolSection.statusModule.lastUpdatePostDateStruct.date
(LastUpdatePostDate) — Last record update
Sponsor/Collaborators Module
protocolSection.sponsorCollaboratorsModule.leadSponsor.name
(LeadSponsorName) — Lead sponsor
Description Module
protocolSection.descriptionModule.briefSummary(BriefSummary)
— Short study description
protocolSection.descriptionModule.detailedDescription
(DetailedDescription) — Extended scientific description
Conditions Module
protocolSection.conditionsModule.conditions(ConditionsModule)
— Conditions module (includes array of diseases)
protocolSection.conditionsModule.keywords(Keywords)
— Categorization terms
Design Module
protocolSection.designModule.studyType(StudyType)
— INTERVENTIONAL, OBSERVATIONAL, or EXPANDED_ACCESS
protocolSection.designModule.phases(Phase)
— Array: EARLY_PHASE1, PHASE1, PHASE2, PHASE3, PHASE4, NA
protocolSection.designModule.enrollmentInfo.count(EnrollmentCount)
— Participant count (actual or estimated)
Arms & Interventions Module
protocolSection.armsInterventionsModule.armGroups(ArmGroup)
— Trial arms with labels and descriptions
protocolSection.armsInterventionsModule.interventions
(ArmsInterventionsModule) — Arms and treatments (DRUG, DEVICE, etc.)
Outcomes Module
protocolSection.outcomesModule.primaryOutcomes(PrimaryOutcome)
— Primary endpoints
protocolSection.outcomesModule.secondaryOutcomes(SecondaryOutcome)
— Secondary endpoints
Eligibility Module
protocolSection.eligibilityModule.eligibilityCriteria
(EligibilityCriteria) — Full inclusion/exclusion text
protocolSection.eligibilityModule.sex(Sex) — ALL, MALE, or FEMALEprotocolSection.eligibilityModule.minimumAge(MinimumAge)
— e.g. "18 Years"
protocolSection.eligibilityModule.maximumAge(MaximumAge)
— e.g. "65 Years"
protocolSection.eligibilityModule.healthyVolunteers(HealthyVolunteers)
— Boolean
protocolSection.eligibilityModule.stdAges(StdAge)
— Array: CHILD, ADULT, OLDER_ADULT
To retrieve just the eligibility module, use: --fields "NCTId,BriefTitle,EligibilityModule" or the get-eligibility command.
Contacts & Locations Module
protocolSection.contactsLocationsModule.centralContacts(CentralContact) —
Primary contact persons
protocolSection.contactsLocationsModule.locations(LocationFacility) —
Facilities with city, state, country, and status
Results Section
Available when hasResults is true.
- Participant Flow Module — Participant counts per study period
- Baseline Characteristics Module — Demographics and baseline data
- Outcome Measures Module — Statistical results for primary/secondary
outcomes
- Adverse Events Module — Serious and other adverse event data
Common --fields Recipes
- Overview:
NCTId,BriefTitle,OverallStatus,Phase,ConditionsModule
- Eligibility details:
NCTId,BriefTitle,EligibilityModule
- Interventions:
NCTId,BriefTitle,ArmsInterventionsModule
- Locations:
NCTId,ContactsLocationsModule
- Outcomes:
NCTId,PrimaryOutcome,SecondaryOutcome
- Sponsor info:
NCTId,BriefTitle,LeadSponsorName,Organization
- Full protocol summary:
NCTId,BriefTitle,OverallStatus,Phase,BriefSummary,ConditionsModule, ArmsInterventionsModule,EligibilityModule
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""CLI tool for interacting with the ClinicalTrials.gov API v2.
This script provides command-line access to various endpoints of the
ClinicalTrials.gov API, including fetching study details, searching for studies,
and counting matching studies.
"""
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "scienceskillscommon",
# ]
# [tool.uv.sources]
# scienceskillscommon = { path = "../../scienceskillscommon" }
# ///
import argparse
import json
import sys
import urllib.parse
from science_skills.skills.scienceskillscommon import http_client
BASE_URL = "https://clinicaltrials.gov/api/v2"
_CLIENT = http_client.HttpClient(BASE_URL + "/", qps=1.0)
DEFAULT_STUDY_FIELDS = (
"NCTId,BriefTitle,OverallStatus,Phase,BriefSummary,"
"ConditionsModule,ArmsInterventionsModule,EligibilityModule"
)
def write_output(data, output_file):
"""Writes data to a JSON file.
Args:
data: The data to serialize as JSON.
output_file: Path to the output file.
"""
try:
with open(output_file, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
print(f"Success! Data written to: {output_file}")
except (OSError, TypeError) as e:
print(f"Error writing to file {output_file}: {e}")
sys.exit(1)
def _build_advanced_filter(args):
"""Builds an Essie advanced filter string from parsed CLI arguments.
Args:
args: Parsed argparse namespace with optional phase, age_group, study_type,
sponsor, and advanced attributes.
Returns:
A combined Essie filter string joined by AND, or None if no
filter clauses apply.
"""
clauses = []
if getattr(args, "phase", None):
clauses.append(f"AREA[Phase]{args.phase}")
if getattr(args, "age_group", None):
clauses.append(f"AREA[StdAge]{args.age_group}")
if getattr(args, "study_type", None):
clauses.append(f"AREA[StudyType]{args.study_type}")
if getattr(args, "sponsor", None):
clauses.append(f"AREA[LeadSponsorName]{args.sponsor}")
if getattr(args, "has_results", False):
clauses.append("AREA[HasResults]true")
if getattr(args, "advanced", None):
clauses.append(args.advanced)
if not clauses:
return None
return " AND ".join(clauses)
def _build_search_params(args):
"""Builds a list of (key, value) query-string pairs from CLI arguments.
Args:
args: Parsed argparse namespace containing search filter attributes.
Returns:
A list of (key, value) tuples suitable for urllib.parse.urlencode.
"""
params = []
if getattr(args, "condition", None):
params.append(("query.cond", args.condition))
if getattr(args, "intervention", None):
params.append(("query.intr", args.intervention))
if getattr(args, "term", None):
params.append(("query.term", args.term))
if getattr(args, "title", None):
params.append(("query.titles", args.title))
if getattr(args, "location", None):
params.append(("query.locn", args.location))
if getattr(args, "id_filter", None):
params.append(("query.id", args.id_filter))
if getattr(args, "status", None):
params.append(("filter.overallStatus", args.status))
advanced = _build_advanced_filter(args)
if advanced:
params.append(("filter.advanced", advanced))
if getattr(args, "fields", None):
params.append(("fields", args.fields))
limit = getattr(args, "limit", None)
if limit:
params.append(("pageSize", str(limit)))
if getattr(args, "sort", None):
params.append(("sort", args.sort))
if getattr(args, "count_total", False):
params.append(("countTotal", "true"))
if getattr(args, "page_token", None):
params.append(("pageToken", args.page_token))
return params
def _add_search_arguments(parser):
"""Registers common search/filter flags on the given argument parser.
Args:
parser: An argparse.ArgumentParser or subparser to add arguments to.
"""
parser.add_argument(
"--condition", help="Condition or disease (maps to query.cond)"
)
parser.add_argument(
"--intervention", help="Intervention or treatment (maps to query.intr)"
)
parser.add_argument(
"--term",
help="General search across all text fields (maps to query.term)",
)
parser.add_argument(
"--title", help="Search within study titles (maps to query.titles)"
)
parser.add_argument(
"--location", help="Search location fields (maps to query.locn)"
)
parser.add_argument(
"--id", dest="id_filter", help="Search by study ID (maps to query.id)"
)
parser.add_argument(
"--status",
help=(
"Filter by recruitment status. Comma-separated. "
"Values: RECRUITING, COMPLETED, ACTIVE_NOT_RECRUITING, "
"NOT_YET_RECRUITING, ENROLLING_BY_INVITATION, SUSPENDED, "
"TERMINATED, WITHDRAWN"
),
)
parser.add_argument(
"--phase",
help=(
"Filter by trial phase. "
"Values: EARLY_PHASE1, PHASE1, PHASE2, PHASE3, PHASE4, NA"
),
)
parser.add_argument(
"--age-group",
dest="age_group",
help="Filter by age group. Values: CHILD, ADULT, OLDER_ADULT",
)
parser.add_argument(
"--study-type",
dest="study_type",
help=(
"Filter by study type. Values: INTERVENTIONAL, OBSERVATIONAL,"
" EXPANDED_ACCESS"
),
)
parser.add_argument(
"--sponsor", help="Filter by lead sponsor name (Essie AREA expression)"
)
parser.add_argument(
"--has-results",
dest="has_results",
action="store_true",
help="Filter for studies that have results available",
)
parser.add_argument(
"--advanced",
help="Raw Essie filter expression (combined with other flags via AND)",
)
parser.add_argument(
"--fields", help="Comma-separated list of fields to return"
)
parser.add_argument(
"--sort",
help=(
"Sort results, e.g. 'LastUpdatePostDate:desc' or"
" 'EnrollmentCount:asc'"
),
)
parser.add_argument(
"--count-total",
dest="count_total",
action="store_true",
help="Include total count of matching studies in the response",
)
parser.add_argument(
"--page-token",
dest="page_token",
help="Token for fetching the next page of results",
)
parser.add_argument(
"--limit",
type=int,
default=10,
help="Number of results per page (max 1000)",
)
def get_study(args):
"""Retrieves a single study by NCT ID and writes it to a JSON file.
Args:
args: Parsed argparse namespace with nct_id, optional fields, and output.
"""
url = f"{BASE_URL}/studies/{urllib.parse.quote(args.nct_id)}"
fields = args.fields if args.fields else DEFAULT_STUDY_FIELDS
url += f"?fields={urllib.parse.quote(fields)}"
data = _CLIENT.fetch_json(url)
write_output(data, args.output)
def get_eligibility(args):
"""Retrieves the eligibility module for a study and writes it to a JSON file.
Args:
args: Parsed argparse namespace with nct_id and output.
"""
url = f"{BASE_URL}/studies/{urllib.parse.quote(args.nct_id)}"
url += f"?fields={urllib.parse.quote('NCTId,BriefTitle,EligibilityModule')}"
data = _CLIENT.fetch_json(url)
write_output(data, args.output)
def search(args):
"""Searches for studies and writes results to a JSON file.
Matches studies based on the given filters.
Args:
args: Parsed argparse namespace with search filter attributes and output.
"""
params = _build_search_params(args)
query_string = urllib.parse.urlencode(params)
url = f"{BASE_URL}/studies" + (f"?{query_string}" if query_string else "")
data = _CLIENT.fetch_json(url)
write_output(data, args.output)
def count(args):
"""Counts studies matching the given filters and prints the total as JSON.
Args:
args: Parsed argparse namespace with search filter attributes.
"""
params = _build_search_params(args)
params.append(("countTotal", "true"))
params.append(("pageSize", "0"))
params = [(k, v) for k, v in params if k not in ("pageSize",) or v == "0"]
final_params = []
seen_keys = {}
for k, v in params:
if k == "pageSize":
if k not in seen_keys:
seen_keys[k] = True
final_params.append((k, "0"))
elif k == "countTotal":
if k not in seen_keys:
seen_keys[k] = True
final_params.append((k, "true"))
else:
final_params.append((k, v))
query_string = urllib.parse.urlencode(final_params)
url = f"{BASE_URL}/studies" + (f"?{query_string}" if query_string else "")
data = _CLIENT.fetch_json(url)
write_output({"totalCount": data.get("totalCount", 0)}, args.output)
def raw_query(args):
"""Executes a raw API request against an arbitrary endpoint.
Args:
args: Parsed argparse namespace with endpoint and optional params.
"""
endpoint = args.endpoint.lstrip("/")
params_dict = {}
if args.params:
try:
params_dict = json.loads(args.params)
except json.JSONDecodeError:
print(json.dumps({"error": "Invalid JSON string provided for params."}))
sys.exit(1)
query_string = urllib.parse.urlencode(params_dict, doseq=True)
url = f"{BASE_URL}/{endpoint}" + (f"?{query_string}" if query_string else "")
data = _CLIENT.fetch_json(url)
write_output(data, args.output)
def main():
"""Parses CLI arguments and dispatches to the appropriate command handler."""
parser = argparse.ArgumentParser(description="ClinicalTrials.gov API v2 CLI")
subparsers = parser.add_subparsers(dest="command", required=True)
p_get = subparsers.add_parser("get-study", help="Retrieve a study by NCT ID")
p_get.add_argument("nct_id", help="NCT ID of the study (e.g. NCT04886804)")
p_get.add_argument(
"--fields",
help=(
"Comma-separated fields to return. "
"Defaults to a useful subset if omitted."
),
)
p_get.add_argument("--output", required=True, help="Output JSON file path")
p_elig = subparsers.add_parser(
"get-eligibility",
help="Retrieve just the eligibility/inclusion criteria for a study",
)
p_elig.add_argument("nct_id", help="NCT ID of the study")
p_elig.add_argument("--output", required=True, help="Output JSON file path")
p_search = subparsers.add_parser(
"search", help="Search for studies with filters"
)
_add_search_arguments(p_search)
p_search.add_argument("--output", required=True, help="Output JSON file path")
p_count = subparsers.add_parser(
"count",
help="Count matching studies without returning records",
)
_add_search_arguments(p_count)
p_count.add_argument("--output", required=True, help="Output JSON file path")
p_raw = subparsers.add_parser(
"raw-query", help="Execute a raw API request (escape hatch)"
)
p_raw.add_argument("--endpoint", required=True, help="API endpoint path")
p_raw.add_argument("--params", help="JSON-encoded dict of query parameters")
p_raw.add_argument("--output", required=True, help="Output JSON file path")
args = parser.parse_args()
commands = {
"get-study": get_study,
"get-eligibility": get_eligibility,
"search": search,
"count": count,
"raw-query": raw_query,
}
commands[args.command](args)
if __name__ == "__main__":
main()
Related skills
How it compares
Pick clinical-trials-database when the task requires structured trial registry data from ClinicalTrials.gov rather than generic web scraping or biomedical paper search.
FAQ
Which ClinicalTrials.gov API version does clinical-trials-database cover?
clinical-trials-database documents ClinicalTrials.gov REST API v2, including /studies search, single-study lookup by NCT ID, metadata, enums, and search-areas endpoints with query parameter reference.
What endpoints can developers query with clinical-trials-database?
clinical-trials-database covers five endpoints: /studies for paginated search, /studies/{nctId} for one study, /studies/metadata for fields, /studies/enums for valid values, and /studies/search-areas.
Is Clinical Trials Database safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.