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, diagnostic_accuracy, unclassified
endpoint_canonical recognised concept: overall_survival, progression_free_survival, objective_response_rate, adverse_event_incidence, hba1c_change, quality_of_life, seroconversion, …
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 the full registry, ~67% of endpoints get a domain, ~87% a measure type, ~61% a parsed time horizon. 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.0

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.0
File Size Uploaded Provenance Actions
ctgov-0.1.0.tar.gz 13.9 kB Without provenance Details for ctgov-0.1.0.tar.gz

Built distribution (wheel)

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

Total release size: 28.3 kB

Release files / ctgov-0.1.0.tar.gz

Download URL ctgov-0.1.0.tar.gz
Size 13.9 kB
Tags Source
SHA-256 checksum
How to use checksums
d0db529c5035e04e0d6740f874135c3eeb9a7df5ed810cd39018300311e8da29
BLAKE2b-256 checksum
How to use checksums
a9450abcfb754282a5166aed3831b19ad5757fdc7e743f9be3e614fcc6be0b3d
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.0-py3-none-any.whl

Download URL ctgov-0.1.0-py3-none-any.whl
Size 14.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0975cf6077ce686ba6ab8a283205da5ecdcf094254aaf6610e555f59d75b5404
BLAKE2b-256 checksum
How to use checksums
9df97f1239a3d4671cf353b941e0941b2843ada98246de274dd278e83f830b0d
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.0

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