What We're Building
In the next 5 minutes, you'll build a simple web app that lets users search for clinical trials by condition and phase, display results in cards, and paginate through results. Clinical Trials API is a REST API that wraps ClinicalTrials.gov into a developer-friendly interface. It gives you structured eligibility criteria (parsed into categories like Age, Gender, Condition, Lab Values -- no regex needed), geographic radius search by lat/lon, HTTP webhook alerts for new trials, cursor-based pagination, and aggregate statistics. Free tier available with no credit card. 480,000+ trials across 220+ countries, updated daily from the FDA. All powered by one API. You'll need basic HTML/JavaScript knowledge and a free RapidAPI account.
Step 1: Get Your API Key (30 Seconds)
- Go to https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api
- Click "Subscribe" on the Free plan (no credit card required)
- Copy your X-RapidAPI-Key from the dashboard
That's it. You're ready to make API calls.
Step 2: Make Your First API Call
Open your terminal and run this curl command (replace YOUR_API_KEY):
curl --request GET \
--url 'https://clinical-trials-api.p.rapidapi.com/v1/trials/search?query=breast+cancer&phase=Phase+2&status=Recruiting&limit=3' \
--header 'X-RapidAPI-Key: YOUR_API_KEY'
You should see something like this:
{
"success": true,
"data": [
{
"nct_id": "NCT04589845",
"title": "Atezolizumab and Chemotherapy for Triple-Negative Breast Cancer",
"brief_summary": "This phase II trial studies how well atezolizumab works with chemotherapy...",
"condition": "Breast Cancer",
"phase": "Phase 2",
"status": "Recruiting",
"enrollment_count": 280,
"last_updated": "2026-05-15",
"locations": [
{
"facility": "Dana-Farber Cancer Institute",
"city": "Boston",
"state": "MA",
"country": "United States"
}
]
}
],
"pagination": {
"total": 847,
"limit": 3,
"next_cursor": "eyJvZmZzZXQiOjN9",
"has_more": true
}
}
That's 847 matching trials, returned in clean JSON with built-in pagination. No scraping, no parsing, no pipeline maintenance.
Step 3: Build the Trial Search App
Create a single HTML file (index.html) and paste this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clinical Trial Search</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; background: #f5f5f5; }
h1 { color: #1a1a2e; }
.search-box { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }
input, select, button { padding: 10px 15px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
input { flex: 1; min-width: 200px; }
button { background: #1a56db; color: white; border: none; cursor: pointer; font-weight: 600; }
button:hover { background: #1647b8; }
.trial-card { background: white; border-radius: 8px; padding: 20px; margin-bottom: 15px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.trial-card h3 { margin: 0 0 8px 0; color: #1a1a2e; font-size: 16px; }
.trial-meta { display: flex; gap: 15px; font-size: 13px; color: #666; margin-bottom: 8px; flex-wrap: wrap; }
.badge { padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; }
.badge-recruiting { background: #d4edda; color: #155724; }
.badge-phase { background: #cce5ff; color: #004085; }
.summary { font-size: 14px; color: #444; line-height: 1.5; }
.pagination { display: flex; gap: 10px; align-items: center; margin-top: 20px; justify-content: center; }
.loading { text-align: center; padding: 40px; color: #666; }
.error { text-align: center; padding: 20px; color: #dc3545; background: #f8d7da; border-radius: 8px; }
.nct { color: #1a56db; font-family: monospace; font-size: 14px; }
.location { font-size: 12px; color: #888; margin-top: 6px; }
</style>
</head>
<body>
<h1>Clinical Trial Search</h1>
<p style="color: #666; margin-bottom: 20px;">Powered by <a href="https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api">Clinical Trials API</a> -- 480K+ FDA trials, free tier, no credit card.</p>
<div class="search-box">
<input type="text" id="query" placeholder="Search condition or keyword (e.g., breast cancer)" value="breast cancer">
<select id="phase">
<option value="">All Phases</option>
<option value="Phase 1">Phase 1</option>
<option value="Phase 2" selected>Phase 2</option>
<option value="Phase 3">Phase 3</option>
<option value="Phase 4">Phase 4</option>
</select>
<select id="status">
<option value="">All Statuses</option>
<option value="Recruiting" selected>Recruiting</option>
<option value="Not yet recruiting">Not Yet Recruiting</option>
<option value="Active not recruiting">Active, Not Recruiting</option>
<option value="Completed">Completed</option>
</select>
<button onclick="searchTrials()">Search</button>
</div>
<div id="results"></div>
<script>
const API_KEY = 'YOUR_RAPIDAPI_KEY'; // Replace with your key
const BASE_URL = 'https://clinical-trials-api.p.rapidapi.com';
let currentCursor = null;
let currentQuery = null;
async function searchTrials(cursor = null) {
const query = document.getElementById('query').value;
const phase = document.getElementById('phase').value;
const status = document.getElementById('status').value;
const params = new URLSearchParams({ query, limit: '5' });
if (phase) params.append('phase', phase);
if (status) params.append('status', status);
if (cursor) params.append('cursor', cursor);
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<div class="loading">Searching 480K+ clinical trials...</div>';
try {
const r = await fetch(`${BASE_URL}/v1/trials/search?${params}`, {
headers: { 'X-RapidAPI-Key': API_KEY }
});
if (r.status === 401) {
resultsDiv.innerHTML = '<div class="error">Invalid API key. Get your free key at <a href="https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api">rapidapi.com/clinical-trials-api</a></div>';
return;
}
if (!r.ok) {
resultsDiv.innerHTML = `<div class="error">API error: HTTP ${r.status}</div>`;
return;
}
const body = await r.json();
if (!body.success) {
resultsDiv.innerHTML = `<div class="error">${body.error?.message || 'Unknown error'}</div>`;
return;
}
currentCursor = cursor;
currentQuery = { query, phase, status };
renderResults(body);
} catch (e) {
resultsDiv.innerHTML = `<div class="error">Network error: ${e.message}</div>`;
}
}
function renderResults(body) {
const resultsDiv = document.getElementById('results');
const pagination = body.pagination;
let html = `<p style="color: #666; margin-bottom: 15px;">Found <strong>${pagination.total.toLocaleString()}</strong> trials. Showing ${(currentCursor ? 'next page' : 'first')} ${body.data.length} results.</p>`;
body.data.forEach(trial => {
const loc = trial.locations && trial.locations[0] ? `${trial.locations[0].facility}, ${trial.locations[0].city}, ${trial.locations[0].state}` : '';
html += `
<div class="trial-card">
<h3>${trial.title}</h3>
<div class="trial-meta">
<span class="nct">${trial.nct_id}</span>
<span class="badge badge-phase">${trial.phase}</span>
<span class="badge badge-recruiting">${trial.status}</span>
${trial.enrollment_count ? `<span>${trial.enrollment_count} participants</span>` : ''}
</div>
<p class="summary">${(trial.brief_summary || '').substring(0, 200)}${(trial.brief_summary || '').length > 200 ? '...' : ''}</p>
${loc ? `<p class="location">${loc}</p>` : ''}
</div>
`;
});
html += '<div class="pagination">';
if (currentCursor) {
html += `<button onclick="searchTrials(null)">First Page</button>`;
}
if (pagination.has_more) {
html += `<button onclick="loadNextPage('${pagination.next_cursor}')">Next Page (${pagination.total - (currentCursor ? parseInt(currentCursor.match(/\d+/)?.[0] || 0) : body.data.length)} remaining)</button>`;
} else if (body.data.length > 0) {
html += '<span style="color: #666;">All results loaded.</span>';
}
html += '</div>';
resultsDiv.innerHTML = html;
}
function loadNextPage(cursor) {
document.getElementById('query').value = currentQuery.query;
document.getElementById('phase').value = currentQuery.phase;
document.getElementById('status').value = currentQuery.status;
searchTrials(cursor);
}
// Search on load
searchTrials();
</script>
</body>
</html>
Step 4: Run It
- Replace
YOUR_RAPIDAPI_KEYon line ~70 with your actual API key - Open
index.htmlin your browser (just double-click the file) - You have a working clinical trial search app
Try searching for "Alzheimer", "diabetes", or leave the default "breast cancer" -- you'll see real FDA trial data appear in under a second.
What's Next?
- Add the geographic radius search endpoint (
/v1/trials/nearby) to build a "trials near me" feature with HTML5 geolocation - Add structured eligibility display by calling
/v1/trials/{nct_id}/eligibilitywhen a user clicks a trial card (Pro tier: $19/mo) - Set up a webhook alert via
POST /v1/alertsto get notified when new trials match your users' saved search criteria - Deploy to Vercel, Netlify, or GitHub Pages for a live demo
- Style it with Tailwind CSS for a professional look
The API
- Free tier: 100 requests/day, basic search, nearby search, stats, conditions browser, 3 saved alerts
- Pro: $19/mo -- 10,000 requests, structured eligibility parsing, 25 webhook alerts, email support
Ultra: $49/mo -- 50,000 requests, unlimited alerts, priority support
Docs: https://rapidapi.com/capifactory-capifactory-default/api/clinical-trials-api/docs
Subscribe on RapidAPI - free tier, no credit card required.
If you build something with this API, tag me - I'd love to see what you create. Drop questions in the comments and I'll answer them.
Top comments (0)