Skip to main content

ctgov

Paginating ClinicalTrials.gov API v2 client with free-text endpoint normalization.

Zero dependencies. Pure standard library. No API key.

pip install ctgov

Why

Two things the registry makes harder than they should be.

1. You cannot get more than 1,000 studies

The API caps a response at 1,000 and hands back a nextPageToken. Existing wrappers stop there, so "how do I pull more than 1000 studies?" is the question that keeps getting asked and never answered.

ctgov follows the token for you. Your result set is bounded by your query, not by the page size.

from ctgov import Client

client = Client()
studies = client.studies("AREA[Phase]PHASE3", max_studies=5000)   # pages automatically
print(len(studies))                                                # 5000

for study in client.iter_studies("cancer"):     # streams; one page in memory at a time
    ...

2. Endpoints are prose, so you cannot compare across trials

Straight from the API, an outcome measure looks like this:

raw endpoint raw timeframe
Serum Markers (HbA1c) to Follow the Evolution of Liver Damage 6 months
Primary Safety: Freedom From Index Procedure Related Major Complications 30 days
Cmax of midazolam 0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 12 hours post-dose
Anxiety Rating Baseline

You cannot group, filter, or compare on that. ctgov structures it:

domain measure type canonical horizon_days direction
biomarker_laboratory change_from_baseline hba1c_change 182.64 lower_is_better
safety proportion — 30.0 higher_is_better
pharmacokinetics pk_parameter pk_exposure 0.5 unspecified
patient_reported score_scale anxiety_score 0.0 lower_is_better

Query to structured rows in one call

from ctgov import endpoints

rows = [r for r in endpoints("AREA[Phase]PHASE3", max_studies=200)
        if r["normalization_confidence"] >= 0.7]

for r in rows[:3]:
    print(r["endpoint_canonical"], r["horizon_days"], r["condition_primary"])

Each row carries the study context (phase, condition, enrollment, sponsor, allocation, masking), the normalized endpoint fields, and the untouched raw text, so any row can be audited rather than taken on trust.

Straight into pandas:

import pandas as pd
df = pd.DataFrame(endpoints("AREA[Condition]diabetes", max_studies=500))
df.groupby("endpoint_canonical")["horizon_days"].median().sort_values()

Counting without downloading

client.count("AREA[Phase]PHASE3", filters={"advanced": "AREA[HasResults]true"})   # 14748

What you get per endpoint

field meaning
endpoint_raw untouched registry text
endpoint_domain safety, efficacy, patient_reported, pharmacokinetics, biomarker_laboratory, clinical_assessment, behavioral_functional, healthcare_utilization, immunogenicity, feasibility_implementation, diagnostic_accuracy, unclassified
endpoint_measure_type change_from_baseline, proportion, count, score_scale, time_to_event, pk_parameter, concentration, continuous_value, diagnostic_accuracy, unclassified
endpoint_canonical one of 38 recognised concepts: overall_survival, progression_free_survival, invasive_disease_free_survival, invasive_breast_cancer_free_survival, recurrence_free_survival, objective_response_rate, duration_of_response, pathologic_complete_response, ki67_response, dose_limiting_toxicity, adverse_event_incidence, serious_adverse_events, hba1c_change, cgm_time_in_range, clinical_status_ordinal_scale, quality_of_life, …
endpoint_domain_inferred true when a recognised concept set the domain. The concept outranks loose keywords, so this is the stronger signal — do not filter these rows out.
endpoint_measure_inferred true when the text named a concept but no measure (e.g. a bare "HbA1c"), so the measure type came from the concept.
endpoint_direction higher_is_better / lower_is_better / ambiguous / unspecified
horizon_days free-text timeframe parsed to a number
timepoint_count, is_longitudinal, baseline_anchored schedule shape
normalization_confidence 0–1. Filter on >= 0.7 for high-precision work.

Honesty about coverage

Normalization is deterministic and rule-based — no model inference, so it is reproducible, auditable, and free to run. It does not classify everything, and it says so rather than guessing: across 124,790 endpoints from 14,170 trials started since 2020, 72% get a domain, 90% a measure type, 61% a parsed time horizon, and 35% a recognised concept. The rest is a genuine long tail of disease-specific measures ("Swollen Joint Count", "Bowel Filling Properties: Distension/Distal Ileum") and is labelled unclassified with raw text retained.

normalization_confidence exists so you can pick your own precision/recall tradeoff.

Being a good citizen

NLM asks for no more than 20 requests/second per IP. ctgov defaults to roughly 3/s and retries with exponential backoff. Please set a contactable user agent:

Client(user_agent="my-project (me@example.com)")

Relationship to pytrials

pytrials is a good, focused API wrapper. ctgov is not a replacement for it so much as a different scope: pagination past the 1,000 cap, and a normalization layer over the free-text outcome fields. If you only need to pull a few hundred studies as a DataFrame, pytrials is a smaller dependency and will serve you fine.

Licence and attribution

MIT. Source data is ClinicalTrials.gov (U.S. National Library of Medicine), public domain.

This product uses publicly available data from the U.S. National Library of Medicine (NLM), National Institutes of Health, Department of Health and Human Services; NLM is not responsible for the product and does not endorse or recommend this or any other product.

Metadata

Release files for ctgov 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ctgov 0.1.1
File Size Uploaded Provenance Actions
ctgov-0.1.1.tar.gz 24.5 kB Without provenance Details for ctgov-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for ctgov 0.1.1
File Interpreter ABI Platform Actions
ctgov-0.1.1-py3-none-any.whl Python 3 none any Details for ctgov-0.1.1-py3-none-any.whl

Total release size: 43.1 kB

Release files / ctgov-0.1.1.tar.gz

Download URL ctgov-0.1.1.tar.gz
Size 24.5 kB
Tags Source
SHA-256 checksum
How to use checksums
15fe5369a351272c366e9e3e11c7266636d53d32ae5e5ea844e74c2b73c97350
BLAKE2b-256 checksum
How to use checksums
cd1d10decbaf931b2b9a80ab9d0a84a603be36392f0a364d6638ca727fe555f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / ctgov-0.1.1-py3-none-any.whl

Download URL ctgov-0.1.1-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ee4dde4e16a377ca0e6b038b712178bd53af8a2db08e1d5185c0462ab1b358c8
BLAKE2b-256 checksum
How to use checksums
680ced15535b714d82ae557deccc65e9dd0ab3a25044480908f615b6de6b2a93
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for ctgov 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page