Day 17 of 30 Days of Search.
TL;DR
To find recruiting clinical trials with Valyu Search API, describe the condition and research task, and restrict retrieval to this dataset - valyu/valyu-clinical-trials.
Parse each result's content, then check the exact recruitment status, study type, condition, and NCT identifier.
Return a source-linked shortlist, not a promise of eligibility or a complete registry export.
-
This tutorial: TypeScript,
valyu-js, and a recruiting melanoma-trial search. - Required: A Valyu API key with access to the clinical-trials dataset. It is listed as a subscription source in the healthcare documentation.
- Verified result: The October 17 test returned five candidates whose study-level statuses and update dates matched the original registry when checked.
-
Important:
RECRUITINGat study level does not mean every site is open or that a particular person qualifies.
A research coordinator needs a shortlist of interventional melanoma studies accepting participants. Searching for a disease name is the easy part. The useful decision is which records deserve closer review—and what still needs checking before anyone contacts a study site.
We'll build that shortlist using ClinicalTrials.gov records retrieved through Valyu, then walk through a real example where the overall study and one location have different recruitment statuses.
This extends our recruiting clinical trials API guide with a complete TypeScript script, a fresh retrieval check, and a site-level verification example. It is a developer tutorial for research discovery, not medical advice, a treatment recommendation, or a patient-eligibility service.
What will the clinical-trial search return?
The output is a small, reviewable list: an NCT ID, study title, phase, overall recruitment status, registry update date, source URL, retrieval timestamp, and available site statuses. A researcher can open each registry record, read its criteria, and decide which study team to contact next.
ClinicalTrials.gov is the original registry. Valyu Search is the retrieval interface in the main example; it is not a proxy for every ClinicalTrials.gov API feature.
Valyu retrieves candidates. Application code verifies the fields. The original registry and study team remain the next checks.
Step 1: Set up TypeScript and clinical-trials dataset access
Use Node.js 24.2 or later: the script uses import.meta.main, which was added in Node.js 24.2.0. The example was tested with Node.js 24.12.0 and valyu-js@2.10.5.
mkdir clinical-trial-search
cd clinical-trial-search
npm init -y
npm pkg set type=module
npm install valyu-js@2.10.5
Set VALYU_API_KEY in your environment. No separate ClinicalTrials.gov API key is needed for the Valyu example.
Clinical-trials access and successful authentication are separate checks. A working key does not necessarily have access to every dataset. Check the data catalog and your account's access before debugging an empty response as a query problem.
Step 2: Ask for the trials you actually want
Start with one bounded task:
Recruiting interventional clinical trials for melanoma
The TypeScript Search SDK accepts the question and source restriction like this:
import { Valyu } from "valyu-js";
const valyu = new Valyu(); // Reads VALYU_API_KEY from the environment.
const response = await valyu.search(
"Recruiting interventional clinical trials for melanoma",
{
includedSources: ["valyu/valyu-clinical-trials"],
maxNumResults: 5
},
);
Natural-language constraints guide retrieval; they are not guaranteed field-level filters. A result still needs to pass the recruiting, interventional, and condition checks below. Five requested results can become fewer than five usable rows.
Step 3: Parse the records and check the exact fields
The tested results contained JSON-encoded strings in content. The script also accepts an already-decoded object. It checks the dataset source, validates the NCT ID against the official registry URL, and removes duplicate NCT IDs.
For this recruiting-only task:
| Returned field | Check or retain |
|---|---|
overall_status |
Must equal RECRUITING
|
study_type |
Must equal INTERVENTIONAL
|
conditions |
Must contain the example condition, melanoma |
nct_id |
Must have a valid NCT format and match the source link |
phases |
Retain the reported value; not every study has an applicable phase |
last_update_posted |
Retain the registry record's update date |
locations |
Retain available site details without inventing a missing status |
Save this complete script as clinical-trials.ts:
import { Valyu } from "valyu-js";
export const SOURCE = "valyu/valyu-clinical-trials";
export const QUERY = "Recruiting interventional clinical trials for melanoma";
type RecordData = Record<string, unknown>;
type Result = { source?: unknown; url?: unknown; content?: unknown };
export type Site = {
facility: string | null;
city: string | null;
country: string | null;
status: string | null;
};
export type Trial = {
nct_id: string;
title: string;
status: "RECRUITING";
phases: string | null;
last_update_posted: string | null;
source_url: string;
retrieved_at: string;
locations: Site[];
};
function object(value: unknown): RecordData | null {
return value !== null && typeof value === "object" && !Array.isArray(value)
? (value as RecordData) : null;
}
function string(value: unknown): string | null {
return typeof value === "string" && value.trim() ? value : null;
}
function parse(content: unknown): RecordData | null {
try { return object(typeof content === "string" ? JSON.parse(content) : content); }
catch { return null; }
}
export function shortlist(
results: Result[], condition = "melanoma",
retrievedAt = new Date().toISOString(),
): Trial[] {
if (!condition.trim()) throw new Error("A condition is required.");
const trials = new Map<string, Trial>();
for (const result of results) {
if (result.source !== SOURCE) continue;
const record = parse(result.content);
if (!record) continue;
const id = string(record.nct_id);
const title = string(record.brief_title);
if (!id || !/^NCT\d{8}$/.test(id) || !title) continue;
if (typeof result.url !== "string") continue;
let url: URL;
try { url = new URL(result.url); } catch { continue; }
if (url.protocol !== "https:" || url.host !== "clinicaltrials.gov" ||
url.username || url.password || url.pathname !== `/study/${id}`) continue;
if (record.overall_status !== "RECRUITING" ||
record.study_type !== "INTERVENTIONAL") continue;
const conditions = Array.isArray(record.conditions)
? record.conditions.filter((value): value is string => typeof value === "string").join(" ")
: string(record.conditions) ?? "";
if (!conditions.toLowerCase().includes(condition.trim().toLowerCase())) continue;
const locations: Site[] = [];
for (const value of Array.isArray(record.locations) ? record.locations : []) {
const site = object(value);
if (site) locations.push({ facility: string(site.facility), city: string(site.city),
country: string(site.country), status: string(site.status) });
}
if (trials.has(id)) continue;
trials.set(id, {
nct_id: id, title, status: "RECRUITING",
phases: string(record.phases),
last_update_posted: string(record.last_update_posted),
source_url: `https://clinicaltrials.gov/study/${id}`,
retrieved_at: retrievedAt, locations,
});
}
return [...trials.values()];
}
export function recruitingSites(trial: Trial, country: string): Site[] {
return trial.locations.filter(site => site.status === "RECRUITING" &&
site.country?.toLowerCase() === country.trim().toLowerCase());
}
export async function searchTrials(apiKey: string) {
const client = new Valyu(apiKey);
let response: Awaited<ReturnType<Valyu["search"]>>;
try {
response = await client.search(QUERY, {
searchType: "proprietary", includedSources: [SOURCE],
maxNumResults: 5, responseLength: "max",
});
} catch {
throw new Error("Trial search failed. Check connectivity and dataset access.");
}
if (!response.success || !Array.isArray(response.results)) {
throw new Error("Trial search failed. Check the key and clinical-trials access.");
}
const retrievedAt = new Date().toISOString();
return {
retrieved_at: retrievedAt, returned_results: response.results.length,
candidates: shortlist(response.results, "melanoma", retrievedAt),
};
}
if (import.meta.main) {
const key = process.env.VALYU_API_KEY;
if (!key) {
console.error("Set VALYU_API_KEY in the server environment.");
process.exitCode = 1;
} else {
try { console.log(JSON.stringify(await searchTrials(key), null, 2)); }
catch {
console.error("Trial search failed; check connectivity, API key, and dataset access.");
process.exitCode = 1;
}
}
}
Run it with your environment variable set:
node clinical-trials.ts
The example makes one Search request. That request uses your Valyu account's credits. Importing the module does not run a search.
The condition check is deliberately modest: it looks for the text melanoma in the returned conditions. It is not a clinical synonym resolver, a subtype assessment, or an eligibility check. For another condition, change QUERY, the condition passed to shortlist, and your validation logic together.
retrieved_at is generated by this application when it receives the search response. last_update_posted belongs to the registry record. A fresh retrieval timestamp does not make an older registry entry newly updated.
Step 4: Inspect a real recruiting-trial search result
As at today, the full script returned five candidates. All five passed its status, study-type, identifier, source-link, and melanoma-text checks. We then queried the official registry for each NCT ID: its overall recruitment status and posted update date matched the indexed values at that check.
| ClinicalTrials.gov study | Phase in returned content | Last update posted |
|---|---|---|
| NCT05039801 | PHASE1 |
2026-10-06 |
| NCT07400302 | PHASE2 |
2026-09-03 |
| NCT07057596 | PHASE2 |
2026-09-15 |
| NCT06163820 | PHASE1, PHASE2 |
2026-10-13 |
| NCT04741997 | EARLY_PHASE1 |
2026-09-16 |
These are dated research examples, not recommendations or a list guaranteed to remain open. Later searches may retrieve different studies, and later registry records may show different statuses.
One candidate looked like this. This is the application's output shape, shortened by omitting locations, not the full Valyu response:
{
"nct_id": "NCT05039801",
"title": "IACS-6274 With or Without Bevacizumab and Paclitaxel for the Treatment of Advanced Solid Tumors",
"status": "RECRUITING",
"phases": "PHASE1",
"last_update_posted": "2026-10-06",
"source_url": "https://clinicaltrials.gov/study/NCT05039801",
"retrieved_at": "2026-10-17T18:15:46.016Z"
}
Notice that a study can investigate several conditions. A title such as “advanced solid tumors” does not itself exclude melanoma; the script checks the record's conditions rather than relying on title wording. Conversely, melanoma appearing in a record does not establish that its cohort is suitable for a particular person.
Step 5: Check recruitment at the study site, not just the study
Overall recruitment and facility recruitment are separate fields. A multi-site trial can be recruiting overall while a specific location has not started accepting participants.
There is a concrete example in NCT04903119, a melanoma study we checked separately in the official registry on October 17. The overall study status was RECRUITING, but its locations included:
| Facility | City | Listed site status at the check |
|---|---|---|
| University of Iowa | Iowa City | NOT_YET_RECRUITING |
| Markey Cancer Center | Lexington | RECRUITING |
| St. Luke's University Health Network | Easton | RECRUITING |
| Vanderbilt-Ingram Cancer Center | Nashville | RECRUITING |
This study illustrates the distinction; it was not one of the five candidates in the preceding search. Its registry update date was February 13, 2026. Checking it in October confirms what the registry reported then, not that a coordinator had independently confirmed an available enrollment slot.
Overall recruitment, site recruitment, and individual eligibility are separate questions. The sites in the diagram are illustrative; the table above records a dated registry check.
The recruitingSites function in the script selects sites whose country matches the requested country and whose own status is exactly RECRUITING. For a returned trial, call recruitingSites(trial, "United States"). A missing location list or missing site status returns no confirmed recruiting-site match; it must not silently become “open.”
That country check is not a distance search. If the requirement is “within 50 miles,” you also need validated coordinates, a distance calculation, and a check that the matching location is recruiting. The Search API's countryCode option biases retrieval; it is not a substitute for checking a trial site's country.
Optionally verify a candidate against the original registry API
For a selected NCT ID, a read-only request to the official ClinicalTrials.gov API v2 lets you compare the registry's current reported study status, update date, and locations with your shortlist. This is an additional source check, not part of the Valyu search call.
This standalone example checks the multi-site study above:
const nctId = "NCT04903119";
const response = await fetch(
`https://clinicaltrials.gov/api/v2/studies/${nctId}`,
{ signal: AbortSignal.timeout(30_000) },
);
if (!response.ok) {
throw new Error(`Registry check failed: HTTP ${response.status}`);
}
const { protocolSection } = await response.json();
console.log({
overall_status: protocolSection.statusModule.overallStatus,
last_update_posted:
protocolSection.statusModule.lastUpdatePostDateStruct?.date,
locations: protocolSection.contactsLocationsModule?.locations?.map(
(site: { facility?: string; city?: string; status?: string }) => ({
facility: site.facility,
city: site.city,
status: site.status ?? "UNKNOWN",
}),
) ?? [],
});
Before using a result for participant-facing work, read the record's inclusion and exclusion criteria and confirm the relevant site's current availability with the study team. Age range, disease stage, biomarkers, prior treatments, and other requirements can matter. Only the study team can determine eligibility through its screening process.
How do you adapt the search to another clinical-trial question?
Change both the retrieval question and the application checks. The following are adaptation patterns, not additional tested searches:
| Research need | Example query | Required follow-up check |
|---|---|---|
| Another condition | Recruiting interventional clinical trials for heart failure | Replace the example condition validator; account for relevant terminology |
| A particular phase | Recruiting phase 3 clinical trials for melanoma | Check phase values explicitly; do not infer phase from ranking |
| A country | Recruiting melanoma trials with study sites in the United Kingdom | Check the matching location's country and its own recruitment status |
| A known study | Clinical trial NCT04903119 recruitment status and study locations | Confirm the returned NCT ID and compare with the original record |
For the coordinator's original job, the outcome is now a bounded shortlist with evidence attached. The next action is to review a candidate's criteria and relevant location—not tell a person they qualify.
If that becomes a monitoring task, store snapshots by NCT ID and compare status, locations, and update dates on later runs. Scheduling, storage, change detection, and alerts belong to your application. Do not infer that a study disappeared from the registry merely because it no longer appears in a top-five search result.
When should you use Valyu Search or the official ClinicalTrials.gov API?
Use Valyu for natural-language discovery and a ranked shortlist, particularly when the same application also retrieves supporting literature. Use the official registry API's structured filters and pagination when you need to enumerate registry matches rather than find a few relevant candidates.
| Requirement | Better starting point |
|---|---|
| Describe a focused trial-research question in plain language | Valyu Search, followed by record checks |
| Find supporting biomedical literature through the same Search interface | Valyu with its PubMed source |
| Read a particular study's current reported registry fields | Official ClinicalTrials.gov study record or API v2 |
| Enumerate matches to an exact registry query | Official API filters and pagination |
| Decide whether an individual can enroll | Study-team screening, not a search response |
The native API supports simple searches and synonym expansion; you do not have to hand-write complex Boolean expressions for every request. Valyu is an alternative retrieval interface, not a reason to portray the public registry API as unusable. An exhaustive export of registry-query matches is also not proof that every clinically relevant study was captured by the chosen query.
Common clinical-trial search mistakes
-
Checking whether text contains “recruiting.”
NOT_YET_RECRUITINGandACTIVE_NOT_RECRUITINGcontain that text too. Compare the field to the exact valueRECRUITING. -
Treating a ranked result limit as complete coverage.
maxNumResults: 5applies before your checks. It does not mean five studies exist—or that five will pass. - Reporting “no trials exist” after an empty output. Distinguish API failure, zero returned candidates, and candidates rejected by your checks. Broaden or inspect the query and use the registry when appropriate.
- Calling a retrieved record live availability. Valyu's healthcare documentation notes possible update delays of 24–48 hours. Registry postings can also lag real-world changes. Keep dates visible and confirm with the site.
- Putting personal health information into search queries. This tutorial uses a public condition and research task. Patient-specific integrations need appropriate privacy, security, consent, and data-handling review before sending sensitive information to any service.
FAQ
How do I find recruiting clinical trials using an API?
With Valyu, search a condition and recruitment goal, restrict the request to valyu/valyu-clinical-trials, parse returned records, and keep the exact overall_status: "RECRUITING" value. Verify other required fields and the relevant site before using the shortlist.
Does this tutorial call the ClinicalTrials.gov API directly?
The main script calls Valyu Search. ClinicalTrials.gov is the original data source. The optional verification example makes a separate, direct request to the public ClinicalTrials.gov API v2.
Is clinical-trials search available on every Valyu plan?
The healthcare documentation lists valyu/valyu-clinical-trials as a subscription dataset. You need a Valyu API key with that access; check current account permissions and pricing rather than assuming the dataset is included.
Does RECRUITING mean I can join the trial?
No. It is a reported recruitment status, not an individual eligibility decision. A particular site can have a different status, and participants must meet study criteria and complete screening with the study team.
Can this search find every recruiting clinical trial?
Not through the ranked shortlist demonstrated here. For enumeration, use an appropriately constructed official registry query and follow its pagination. Neither a short search nor an empty result establishes complete clinical coverage.
Can I search by location and phase?
Yes, include them in the natural-language research question, then enforce required phase and site-location constraints against returned fields. Query wording alone is not an exact field filter, and location relevance does not establish eligibility.
Try a source-linked clinical-trial search
Get a Valyu API key, confirm clinical-trials dataset access, and run the TypeScript example. Start with a public research question. Keep the NCT ID, original link, and dates attached to every candidate you retain.


Top comments (0)