ClinicalTrials.gov API Migration: Classic to v2
Migrate the retired ClinicalTrials.gov classic API to v2. Compare endpoints, map queries and pagination, handle schema changes, and use XML shims safely.

Moving from the Classic ClinicalTrials.gov API to v2
ClinicalTrials.gov API Migration: Classic to v2
If a script that queried ClinicalTrials.gov started returning 404 errors in 2024, the reason is pretty simple: the classic API was retired. The trick is replacing it without accidentally changing (1) which studies you get back, (2) how your parser reads the response, or (3) what your users end up seeing.
The replacement is API v2, but it isn't just "swap the URL." The old expr parameter becomes query.term; rank-based paging becomes cursor-based paging; and what used to be top-level fields now lives inside nested objects. The legacy endpoints can keep XML clients running for a little while, but they're really a bridge - not a long-term solution.
This guide maps the retired endpoints and parameters to their v2 equivalents, calls out the schema changes that most often break downstream code, and suggests a migration approach you can test before switching production traffic. The endpoint behavior and examples below are based on the NLM migration guidance and live checks reported on October 14, 2026.
Why did the classic ClinicalTrials.gov API stop working?
The National Library of Medicine (NLM) retired the classic ClinicalTrials.gov website and API on June 25, 2024. The old API routes - including /api/query/full_studies, /api/query/study_fields, and several /api/info/ endpoints - don't provide the service older clients expect anymore. Requests to those routes on the modern site return HTTP 404.
This matters because the retirement announcements mention redirects for the classic website, but API clients shouldn't assume that an old API request will redirect to an equivalent API response. A browser search page can redirect cleanly, while a programmatic request to an API path ends up as a 404.
What happens to the old URLs?
The classic hostname redirects requests to the matching path on the modern domain, but that doesn't "revive" the retired route. For example, a request to a classic study_fields endpoint may redirect first and then land on a 404. Old website search URLs can redirect to the modern search experience, but that's not the same thing as getting an API response.
If something started failing around mid-2024, don't start with credentials or network debugging. Check the final HTTP status code and the response body. Also be aware that some clients hide the first redirect - so the final 404 can look like a mysterious endpoint failure when it's really "redirect + dead route."
What replaces the classic service?
The current API lives under /api/v2/. The main study-search endpoint is /api/v2/studies. By default it returns complete study records; if you pass fields, it returns only the fields you request. You can retrieve a single study by NCT number, and there are separate endpoints for metadata, field statistics, search areas, version information, and bulk downloads.
NLM also provides /api/legacy/ endpoints meant to reproduce parts of the classic API behavior. They can help if you have an existing system that can't switch from the classic XML shape immediately. They aren't a way to keep calling the old /api/query/ routes, and you still need a v2 migration plan.
How do classic endpoints and parameters map to v2?
Start with what your client actually uses - endpoint, requested fields, output format, sorting expectations, paging behavior - not just what URL it calls today. It's easy to swap endpoints and end up with results that look "plausible" while quietly differing in ways that matter to your product.
Endpoint mapping
| Classic endpoint or use | API v2 replacement | Legacy option |
|---|---|---|
/api/query/full_studies |
/api/v2/studies |
/api/legacy/full-studies |
/api/query/study_fields |
/api/v2/studies with fields |
/api/legacy/study-fields |
/api/query/field_values |
/api/v2/stats/field/values, or studies with selected fields |
None listed |
/api/info/data_vrs and /api/info/api_vrs |
/api/v2/version |
Version tags in legacy XML |
/api/info/study_fields_list |
/api/v2/studies/metadata |
/api/legacy/study-fields-list |
/api/info/search_areas |
/api/v2/studies/search-areas |
None listed |
| Study by NCT number | /api/v2/studies/{nctId} |
/api/legacy/public-xml/{nctId} |
| Study with results by NCT number | /api/v2/studies/{nctId} |
Add full=true to the legacy route |
| Full registry download | /api/v2/studies/download?format=json.zip |
/api/legacy/public-xml?format=zip |
For field values, the stats endpoint gives you values across the registry, capped at 250 values. Pick the endpoint based on what you're trying to answer: metadata tells you what fields exist, field-value statistics help you inspect candidate values, and study search returns matching records.
Parameter mapping
| Classic parameter or behavior | API v2 equivalent | What changes |
|---|---|---|
expr |
query.term |
Search syntax and default search areas may differ slightly. |
min_rnk and max_rnk |
pageSize and pageToken |
v2 uses cursor pagination rather than rank ranges. |
fmt=json or fmt=csv |
format=json or format=csv |
v2 does not return XML. |
fields |
fields |
Field names can be reused, but the response structure is different. |
| Relevance ranking | sort=@relevance |
v2 results are not sorted by relevance unless you ask for it. |
Website search parameters such as cond or intr |
query.cond or query.intr |
Website search parameters are not interchangeable with every classic API parameter. |
In classic, rank ranges were part of the request. In v2, you don't convert that into "page 3." Instead, v2 returns a page and may include nextPageToken. To fetch the next page, pass that token back as pageToken on the next request. Default page size is 10; maximum is 1,000.
A minimal search request might look like this:
/api/v2/studies?query.term=heart%20attack&fields=NCTId,BriefTitle&pageSize=100&countTotal=true
The studies array holds the records. If the response includes nextPageToken, keep requesting pages until there's no next token left. Don't build later pages by guessing a rank offset - v2's cursor works differently.
Check result ordering before you switch
Even when you get the same studies back, the order can change. In a live comparison using heart attack, the classic-style legacy response and v2 returned the same total count but different first results. Adding sort=@relevance to the v2 request brought the first three results into line with the legacy response in that test.
That's not a blanket guarantee that every query will match rankings. For queries that power user-facing lists, saved searches, or scheduled exports, compare both membership and order. If order matters, set it explicitly and test representative queries.
What data model changes can break an API client?
The response schema is where migrations tend to hurt. You can get HTTP 200, see the study you expected, and still break your client because you were reading fields from old paths or relying on legacy conventions for how values are shaped. Test the transformation from "API response" to "your internal data model" - not just the request.
Fields are nested inside modules
In v2, records are structured around protocolSection and other sections. For example, the NCT identifier is at protocolSection.identificationModule.nctId instead of a top-level NCTId field. The field name you request might look familiar, but its location in the response changes.
Use the NLM Study Data Structure documentation to map the fields your application actually consumes. Build a field-by-field mapping for your parser and downstream storage, especially if you only request a subset with fields. And don't assume every record has every selected field - handle missing or null values explicitly.
Lists, enums, and dates use different conventions
List fields are returned as plain arrays, without the wrapper objects some classic responses used. You may also see pluralized names like conditions and phases. Code that expects a single object or reads values from a legacy wrapper will need changes.
Enum values use uppercase codes (for example, RECRUITING, PHASE2, and NA) rather than display labels like "Recruiting," "Phase 2," and "Not Applicable." The /api/v2/studies/enums endpoint provides values alongside legacy values. If you need the old labels in a UI or you're reconciling historical data, use that mapping.
Dates come back in ISO 8601 formats, with precision varying by field (year only, year+month, full date, or date+time). Legacy display strings like "June 2020" shouldn't be treated as the canonical stored representation. v2 also includes boolean fields for some "unknown" cases that were previously expressed differently, including SubmissionUnreleaseDateUnknown and UnpostedEventDateUnknown.
Rich text and XML are separate concerns
API v2 doesn't have an XML output mode. The standard response is JSON, and rich text is represented in Markdown. If an application needs the old formatting behavior, markupFormat=legacy can help with markup formatting - but it doesn't convert the v2 response into the old XML schema.
Plan for these as separate compatibility issues: output format, markup representation, and data structure. A formatting option won't fix code that expects legacy XPath-like locations or XML element names.
How should you migrate safely, and when should you use legacy XML?
The safest migration is staged. Keep the old client behavior as a reference, implement a v2 adapter alongside it, and compare normalized results before switching any production consumers. That way, differences stay visible while you can still attribute them to query syntax, sorting, pagination, or parsing.
Use a migration checklist
- Inventory every classic call. Record the endpoint, parameters, requested fields, output format, request frequency, and what consumes the response.
- Map each call to v2. Replace
exprwithquery.term, choose the equivalent query-area parameter where relevant, and define the requiredfieldsexplicitly. - Implement cursor paging. Pick a starting
pageSize, save and reuse eachnextPageTokenaspageToken, and stop only when there's no next token. Don't assume "bigger page" equals "fetch everything." - Update the parser. Read nested fields, accept arrays and missing values, normalize enums, and parse ISO 8601 dates without leaning on display strings.
- Compare representative queries. Check total counts, record IDs, field values, and order. Include multi-page queries and studies with results.
- Switch consumers in stages. Log errors and differences, keep a rollback path, and remove legacy assumptions only after v2 output is verified in production.
For high-impact data pipelines, compare a normalized internal representation rather than raw JSON/XML. For example, convert each response into a stable internal record with the NCT ID, title, status, phase, and relevant dates. That makes it easier to tell the difference between a true data difference and a harmless structural change.
When is the legacy endpoint reasonable?
Use a legacy endpoint when XML is a hard short-term requirement and rewriting the parser immediately would create more operational risk than a temporary compatibility layer. These endpoints are built to mimic classic behavior as closely as possible, which can make the transition smoother for older clients.
There are real trade-offs, though:
- Legacy endpoints return XML only. JSON isn't supported on those routes.
- When
expris used, retrieval is limited to the first 10,000 studies. - The legacy
study-fieldsendpoint omitsFlowReasonNumUnitsandOrgStudyIdDomain; NLM notes these fields contain no data in any study. - Legacy responses report their own API version, separate from the v2 version endpoint.
If you adopt the shim, set an explicit removal condition. For example: keep it until the XML parser is replaced and v2 results pass your comparison tests. Without an exit criterion, a "temporary" bridge tends to become permanent, and the compatibility surface gets narrower over time.
Use bulk download for a full registry mirror
If you're trying to mirror the complete registry, don't page through search results. Use the bulk download endpoint. The v2 download route can provide a ZIP of JSON files, with each study file matching the single-study v2 schema.
Plan for file size, processing time, and schema evolution. The registry contained 606,964 studies in the live check reported on October 14, 2026, so this isn't a small download. Store it durably, validate the archive before processing, and make ingestion resumable. For targeted queries, the search endpoint is usually the better fit; for a complete local copy, bulk download is the simpler route.
Key takeaways
- The classic API was retired on June 25, 2024; old API paths aren't a fallback.
/api/v2/studiesreplaces the classic study-search endpoints, but the response model is different.- Translate rank-based paging into cursor-based paging, and set sorting explicitly when order matters.
- Treat legacy XML as a time-limited compatibility bridge, not the default path.
- Test the whole pipeline - from query construction to parsing and downstream display.
Conclusion
Migrating from the classic ClinicalTrials.gov API isn't just a URL change. You'll translate query parameters, swap rank ranges for cursor pagination, handle explicit sorting, and update code that reads the nested v2 schema. The work is manageable if you separate "request behavior" from "parsing," and compare normalized results before switching consumers.
Use the legacy XML routes only when they reduce immediate risk, and give that compatibility layer a real end date. For new work, build against API v2 and test the areas that can silently change outcomes: query coverage, result order, page traversal, and field interpretation.