JavaScript/TypeScript SDK
The official JavaScript/TypeScript client for CTWiseAPI.
Installation
npm install @ctwise/sdk
# or
yarn add @ctwise/sdk
# or
pnpm add @ctwise/sdk
Requirements:
- Node.js 16+ or modern browser
- TypeScript 4.5+ (optional, for type safety)
Quick Start
TypeScript
import { CTWiseClient } from '@ctwise/sdk';
const client = new CTWiseClient({ apiKey: 'ctw_YOUR_API_KEY' });
const results = await client.requirements.search({
therapeuticArea: 'Oncology',
authorities: ['FDA', 'EMA'],
phase: 'III',
});
results.requirements.forEach(req => {
console.log(`[${req.authority}] ${req.title}`);
});
JavaScript (CommonJS)
const { CTWiseClient } = require('@ctwise/sdk');
const client = new CTWiseClient({ apiKey: 'ctw_YOUR_API_KEY' });
async function main() {
const results = await client.requirements.search({
therapeuticArea: 'Oncology',
});
console.log(`Found ${results.totalCount} requirements`);
}
main();
Configuration
Basic Configuration
import { CTWiseClient } from '@ctwise/sdk';
const client = new CTWiseClient({
apiKey: 'ctw_YOUR_API_KEY',
baseUrl: 'https://api.ctwise.ai', // Default
timeout: 30000, // Timeout in milliseconds
maxRetries: 3, // Number of retries on failure
});
Environment Variables
export CTWISE_API_KEY="ctw_YOUR_API_KEY"
import { CTWiseClient } from '@ctwise/sdk';
// API key is read from CTWISE_API_KEY
const client = new CTWiseClient();
Custom Fetch Implementation
For environments with custom fetch requirements:
import { CTWiseClient } from '@ctwise/sdk';
import fetch from 'node-fetch';
const client = new CTWiseClient({
apiKey: 'xxx',
fetch: fetch as any, // Custom fetch implementation
});
API Reference
Requirements Search
Search for regulatory requirements across multiple authorities.
const results = await client.requirements.search({
// Search criteria
therapeuticArea: 'Oncology',
requirementTypes: ['endpoint_definition', 'safety_monitoring'],
authorities: ['FDA', 'ICH', 'EMA'],
phase: 'III',
keywords: ['primary endpoint', 'overall survival'],
// Options
includeGuidance: true,
includeExamples: false,
maxResults: 100,
});
// TypeScript provides full type inference
console.log(`Found ${results.totalCount} requirements`);
for (const req of results.requirements) {
console.log(req.requirementId);
console.log(req.title);
console.log(req.description);
console.log(req.implementationGuidance); // Starter tier only
}
Amendment Patterns
Get historical amendment patterns for a therapeutic area.
const patterns = await client.patterns.get({
therapeuticArea: 'Oncology',
timePeriod: '5_years',
});
for (const pattern of patterns.patterns) {
console.log(`Pattern: ${pattern.patternType}`);
console.log(`Frequency: ${pattern.frequency}%`);
console.log(`Risk Level: ${pattern.riskLevel}`);
}
Prevention Strategies
Get prevention strategies to avoid common protocol issues.
const strategies = await client.prevention.getStrategies({
therapeuticArea: 'Oncology',
phase: 'III',
});
for (const strategy of strategies.strategies) {
console.log(`Strategy: ${strategy.title}`);
console.log(`Effectiveness: ${strategy.effectiveness}%`);
strategy.actions.forEach(action => console.log(` - ${action}`));
}
Protocol Validation
Validate a protocol against regulatory requirements.
const validation = await client.rules.validate({
protocol: {
title: 'Phase III Oncology Study',
therapeuticArea: 'Oncology',
phase: 'III',
primaryEndpoint: 'Overall survival',
sampleSize: 500,
},
});
console.log(`Compliance Score: ${validation.complianceScore}%`);
console.log(`Status: ${validation.status}`);
for (const issue of validation.issues) {
console.log(`[${issue.severity}] ${issue.description}`);
console.log(` Recommendation: ${issue.recommendation}`);
}
Error Handling
import { CTWiseClient } from '@ctwise/sdk';
import {
CTWiseError,
AuthenticationError,
RateLimitError,
ValidationError,
NotFoundError,
InsufficientTierError,
} from '@ctwise/sdk/errors';
const client = new CTWiseClient({ apiKey: 'xxx' });
try {
const results = await client.requirements.search({
therapeuticArea: 'Oncology',
});
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
} else if (error instanceof InsufficientTierError) {
console.error('This feature requires a higher tier');
} else if (error instanceof ValidationError) {
console.error(`Invalid request: ${error.message}`);
} else if (error instanceof CTWiseError) {
console.error(`API error: ${error.message}`);
} else {
throw error;
}
}
TypeScript Types
The SDK exports all types for full type safety:
import { CTWiseClient } from '@ctwise/sdk';
import type {
RequirementsSearchRequest,
RequirementsSearchResponse,
Requirement,
RegulatoryReference,
TherapeuticArea,
Authority,
Phase,
} from '@ctwise/sdk/types';
const searchRequest: RequirementsSearchRequest = {
therapeuticArea: 'Oncology' as TherapeuticArea,
authorities: ['FDA', 'EMA'] as Authority[],
phase: 'III' as Phase,
};
const results: RequirementsSearchResponse = await client.requirements.search(searchRequest);
const requirement: Requirement = results.requirements[0];
const reference: RegulatoryReference = requirement.regulatoryReference;
Pagination
For large result sets, use pagination:
// Get first page
const firstPage = await client.requirements.search({
therapeuticArea: 'Oncology',
maxResults: 50,
});
// Async iterator for all pages
const allRequirements = [];
for await (const page of client.requirements.searchPaginated({
therapeuticArea: 'Oncology',
})) {
allRequirements.push(...page.requirements);
}
console.log(`Total requirements: ${allRequirements.length}`);
React Integration
Hook Pattern
import { useState, useEffect } from 'react';
import { CTWiseClient } from '@ctwise/sdk';
import type { RequirementsSearchResponse } from '@ctwise/sdk/types';
const client = new CTWiseClient({ apiKey: process.env.REACT_APP_CTWISE_API_KEY });
function useRequirements(therapeuticArea: string) {
const [data, setData] = useState<RequirementsSearchResponse | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetchData() {
try {
setLoading(true);
const results = await client.requirements.search({ therapeuticArea });
setData(results);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}
fetchData();
}, [therapeuticArea]);
return { data, loading, error };
}
// Usage
function RequirementsList() {
const { data, loading, error } = useRequirements('Oncology');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<ul>
{data?.requirements.map(req => (
<li key={req.requirementId}>{req.title}</li>
))}
</ul>
);
}
React Query Integration
import { useQuery } from '@tanstack/react-query';
import { CTWiseClient } from '@ctwise/sdk';
const client = new CTWiseClient({ apiKey: process.env.REACT_APP_CTWISE_API_KEY });
function useRequirements(therapeuticArea: string) {
return useQuery({
queryKey: ['requirements', therapeuticArea],
queryFn: () => client.requirements.search({ therapeuticArea }),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
Node.js Server Integration
Express.js
import express from 'express';
import { CTWiseClient } from '@ctwise/sdk';
const app = express();
const client = new CTWiseClient({ apiKey: process.env.CTWISE_API_KEY });
app.get('/api/requirements', async (req, res) => {
try {
const results = await client.requirements.search({
therapeuticArea: req.query.area as string,
});
res.json(results);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch requirements' });
}
});
app.listen(3000);
Next.js API Route
// pages/api/requirements.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { CTWiseClient } from '@ctwise/sdk';
const client = new CTWiseClient({ apiKey: process.env.CTWISE_API_KEY });
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { therapeuticArea } = req.query;
try {
const results = await client.requirements.search({
therapeuticArea: therapeuticArea as string,
});
res.status(200).json(results);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch requirements' });
}
}
Browser Usage
For browser environments, use the UMD build:
<script src="https://unpkg.com/@ctwise/sdk@latest/dist/ctwise.umd.js"></script>
<script>
const client = new CTWise.CTWiseClient({ apiKey: 'xxx' });
client.requirements.search({ therapeuticArea: 'Oncology' })
.then(results => console.log(results));
</script>
Best Practices
1. Reuse Client Instances
// Good - singleton pattern
const client = new CTWiseClient({ apiKey: process.env.CTWISE_API_KEY });
export { client };
// Bad - create new client each time
export function search() {
const client = new CTWiseClient({ apiKey: 'xxx' });
return client.requirements.search(...);
}
2. Handle Rate Limits with Retry
import { RateLimitError } from '@ctwise/sdk/errors';
async function searchWithRetry(params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await client.requirements.search(params);
} catch (error) {
if (error instanceof RateLimitError && i < maxRetries - 1) {
await new Promise(r => setTimeout(r, error.retryAfter * 1000));
} else {
throw error;
}
}
}
}
3. Use Environment Variables
// Good
const client = new CTWiseClient({
apiKey: process.env.CTWISE_API_KEY,
});
// Bad - never hardcode keys
const client = new CTWiseClient({
apiKey: 'ctw_YOUR_API_KEY',
});
FDA 483 Inspection Intelligence
CTWiseAPI includes comprehensive FDA 483 inspection data with semantic search, facility risk scoring, and regulatory cross-references.
Search 483 Observations
Search FDA 483 inspection observations using natural language:
const API_KEY = "YOUR_API_KEY";
const BASE_URL = "https://api.ctwise.ai/v1";
// Semantic search for 483 observations
const response = await fetch(`${BASE_URL}/483/observations/search`, {
method: "POST",
headers: {
"X-Api-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: "data integrity failures in sterile manufacturing",
top_k: 10,
filters: {
category: "Data Integrity",
program_area: "Human Drugs",
},
}),
});
const data = await response.json();
console.log(`Found ${data.total} results\n`);
data.results.forEach((obs) => {
console.log(`Facility: ${obs.legal_name} (FEI: ${obs.fei_number})`);
console.log(`CFR: ${obs.act_cfr_number}`);
console.log(`Description: ${obs.short_description.substring(0, 100)}...`);
console.log(`Similarity: ${obs.similarity_score.toFixed(2)}`);
console.log(`Date: ${obs.inspection_end_date}`);
console.log();
});
Get Facility Profile
Retrieve a facility's inspection history and classification breakdown:
// Get facility profile by FEI number
const feiNumber = "3016004437";
const response = await fetch(`${BASE_URL}/483/facilities/${feiNumber}`, {
headers: { "X-Api-Key": API_KEY },
});
const facility = await response.json();
console.log(`Facility: ${facility.legal_name}`);
console.log(`Location: ${facility.city}, ${facility.state} ${facility.zip_code}`);
console.log(`Total Inspections: ${facility.total_inspections}`);
console.log("\nClassifications:");
console.log(` OAI (Official Action Indicated): ${facility.classification_breakdown.oai_count}`);
console.log(` VAI (Voluntary Action Indicated): ${facility.classification_breakdown.vai_count}`);
console.log(` NAI (No Action Indicated): ${facility.classification_breakdown.nai_count}`);
console.log(`\nRisk Score: ${facility.risk_assessment.risk_score.toFixed(1)}/100`);
console.log(`Risk Level: ${facility.risk_assessment.risk_level}`);
Get Facility Citations
Retrieve all citations for a specific facility:
// Get citations for a facility
const citationsResponse = await fetch(
`${BASE_URL}/483/facilities/${feiNumber}/citations?limit=20`,
{ headers: { "X-Api-Key": API_KEY } }
);
const citations = await citationsResponse.json();
console.log(`Total Citations: ${citations.total}\n`);
citations.results.forEach((citation) => {
console.log(`Date: ${citation.inspection_end_date}`);
console.log(`CFR: ${citation.act_cfr_number}`);
console.log(`Description: ${citation.short_description.substring(0, 80)}...`);
console.log(`Category: ${citation.category}`);
console.log();
});
Calculate Risk Scores
Get a comprehensive risk assessment with 5-factor breakdown:
// Get risk score for a facility
const riskResponse = await fetch(`${BASE_URL}/483/risk-scores/${feiNumber}`, {
headers: { "X-Api-Key": API_KEY },
});
const risk = await riskResponse.json();
console.log(`Composite Risk Score: ${risk.risk_score.toFixed(1)}/100`);
console.log(`Risk Level: ${risk.risk_level}`);
console.log(`Methodology: ${risk.methodology_version}`);
console.log("\nFactor Breakdown:");
console.log(` OAI Ratio: ${risk.factors.oai_ratio.toFixed(1)}`);
console.log(` Citation Frequency: ${risk.factors.citation_frequency.toFixed(1)}`);
console.log(` Recency: ${risk.factors.recency.toFixed(1)}`);
console.log(` Severity: ${risk.factors.severity.toFixed(1)}`);
console.log(` Peer Benchmark: ${risk.factors.peer_benchmark.toFixed(1)}`);
Browse CFR References
See which CFR sections are most frequently cited:
// Get most cited CFR sections
const cfrResponse = await fetch(
`${BASE_URL}/483/cfr-references?sort_order=desc&limit=10`,
{ headers: { "X-Api-Key": API_KEY } }
);
const cfrData = await cfrResponse.json();
console.log(`Top ${cfrData.results.length} most cited CFR sections:\n`);
cfrData.results.forEach((cfr) => {
console.log(`${cfr.act_cfr_number}: ${cfr.occurrence_count} citations`);
console.log(` ${cfr.short_description}`);
console.log();
});
// Get details for a specific CFR section
const cfrSection = "21 CFR 211.68";
const cfrDetailResponse = await fetch(
`${BASE_URL}/483/cfr-references/${encodeURIComponent(cfrSection)}?cfr=${encodeURIComponent(cfrSection)}`,
{ headers: { "X-Api-Key": API_KEY } }
);
const cfrDetail = await cfrDetailResponse.json();
console.log(`\nCFR: ${cfrDetail.act_cfr_number}`);
console.log(`Title: ${cfrDetail.short_description}`);
console.log(`Total Citations: ${cfrDetail.occurrence_count}`);
Get Analytics Summary
Retrieve aggregate statistics across all 483 data:
// Get analytics summary
const summaryResponse = await fetch(`${BASE_URL}/483/analytics/summary`, {
headers: { "X-Api-Key": API_KEY },
});
const summary = await summaryResponse.json();
console.log(`Total Citations: ${summary.total_citations.toLocaleString()}`);
console.log(`Total Facilities: ${summary.total_facilities.toLocaleString()}`);
console.log(`Total CFR References: ${summary.total_cfr_references.toLocaleString()}`);
console.log("\nRisk Distribution:");
Object.entries(summary.risk_distribution).forEach(([level, count]) => {
console.log(` ${level.charAt(0).toUpperCase() + level.slice(1)}: ${count}`);
});
console.log("\nTop 5 Most Cited CFRs:");
summary.top_cited_cfr.slice(0, 5).forEach((cfr) => {
console.log(` ${cfr.act_cfr_number}: ${cfr.occurrence_count} citations`);
});
Get Trending Citations
Find CFR sections with increasing citation frequency:
// Get trending citations
const trendingResponse = await fetch(
`${BASE_URL}/483/citations/trending?days=365&limit=10`,
{ headers: { "X-Api-Key": API_KEY } }
);
const trending = await trendingResponse.json();
console.log(`Trending citations in the last ${trending.days} days:\n`);
trending.results.forEach((item) => {
console.log(item.act_cfr_number);
console.log(` Recent: ${item.recent_count} citations`);
console.log(` Previous: ${item.previous_count} citations`);
console.log(` Trend: ${item.trend_direction} (${item.trend_percentage > 0 ? '+' : ''}${item.trend_percentage.toFixed(1)}%)`);
console.log();
});
Regulatory Mapping (ICH E6 R3)
Cross-reference CFR citations with ICH E6(R3) requirements:
// Get regulatory mapping for a CFR section
const mappingResponse = await fetch(
`${BASE_URL}/483/regulatory-mapping/${encodeURIComponent("21 CFR 211.68")}`,
{ headers: { "X-Api-Key": API_KEY } }
);
const mapping = await mappingResponse.json();
console.log(`CFR: ${mapping.cfr_section}`);
console.log(`Title: ${mapping.cfr_title}`);
console.log(`483 Citations: ${mapping.occurrence_count}`);
console.log("\nICH E6(R3) Mappings:");
mapping.ich_mappings.forEach((ich) => {
console.log(`\n ${ich.ich_section}`);
console.log(` Requirement: ${ich.matched_requirement}`);
console.log(` Relevance: ${ich.relevance_score.toFixed(2)}`);
console.log(` Rationale: ${ich.mapping_rationale}`);
});
Get Analytics Benchmarks
Compare facility risk scores against peer groups:
// Get benchmarks by product type
const benchmarksResponse = await fetch(
`${BASE_URL}/483/analytics/benchmarks?product_type=drug`,
{ headers: { "X-Api-Key": API_KEY } }
);
const benchmarks = await benchmarksResponse.json();
console.log(`Product Type: ${benchmarks.product_type}`);
console.log(`Facility Count: ${benchmarks.facility_count}`);
console.log("\nRisk Score Statistics:");
console.log(` Mean: ${benchmarks.risk_score_stats.mean.toFixed(1)}`);
console.log(` Median: ${benchmarks.risk_score_stats.median.toFixed(1)}`);
console.log(` P75: ${benchmarks.risk_score_stats.p75.toFixed(1)}`);
console.log(` P90: ${benchmarks.risk_score_stats.p90.toFixed(1)}`);
Using Axios
If you prefer using Axios instead of fetch:
import axios from 'axios';
const client = axios.create({
baseURL: 'https://api.ctwise.ai/v1',
headers: { 'X-Api-Key': API_KEY },
});
// Search observations
const { data } = await client.post('/483/observations/search', {
query: 'contamination in manufacturing',
top_k: 10,
});
console.log(`Found ${data.total} observations`);
// Get facility profile
const facility = await client.get(`/483/facilities/${feiNumber}`);
console.log(facility.data);
// Get risk score
const risk = await client.get(`/483/risk-scores/${feiNumber}`);
console.log(`Risk: ${risk.data.risk_score}/100`);
REST API Reference
If you prefer to call the REST API directly without the SDK, here are the available endpoints:
Public Endpoints (No Authentication Required)
| Endpoint | Description |
|---|---|
GET /v1/catalog/sources | List all available regulatory sources |
Protected Endpoints (API Key Required)
Regulatory Rules
| Endpoint | Description |
|---|---|
GET /v1/rules | Search rules by keyword, source, category |
GET /v1/rules/{id} | Get specific rule details |
POST /v1/semantic-search | AI-powered natural language search |
GET /v1/rules/search | Alternative GET-based semantic search |
FDA 483 Intelligence
| Endpoint | Description |
|---|---|
POST /v1/483/observations/search | Semantic search for 483 observations |
GET /v1/483/facilities/{fei} | Get facility profile |
GET /v1/483/facilities/{fei}/citations | Get facility citations |
GET /v1/483/risk-scores/{fei} | Get facility risk score |
GET /v1/483/cfr-references | List CFR references |
GET /v1/483/cfr-references/{cfr} | Get CFR details |
GET /v1/483/analytics/summary | Get analytics summary |
GET /v1/483/citations/trending | Get trending citations |
GET /v1/483/regulatory-mapping | List regulatory mappings |
GET /v1/483/regulatory-mapping/{cfr} | Get CFR to ICH mapping |
GET /v1/483/analytics/benchmarks | Get peer benchmarks |
Semantic Search Endpoints
The SDK's search() method uses these endpoints under the hood:
// SDK method
const results = await client.rules.search({ query: "informed consent requirements" });
// Equivalent REST API call (POST method - recommended)
const response = await fetch("https://api.ctwise.ai/v1/semantic-search", {
method: "POST",
headers: {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({ query: "informed consent requirements", limit: 10 }),
});
// Equivalent REST API call (GET method - alternative)
const getResponse = await fetch(
"https://api.ctwise.ai/v1/rules/search?q=informed+consent+requirements&limit=10",
{ headers: { "X-Api-Key": "YOUR_API_KEY" } }
);
Troubleshooting
Common Errors
| Error Code | HTTP Status | Cause | Solution |
|---|---|---|---|
INVALID_API_KEY | 401 | Invalid or expired API key | Verify your API key is correct and active |
MISSING_API_KEY | 401 | No API key provided | Include X-Api-Key header in requests |
RATE_LIMIT_EXCEEDED | 429 | Too many requests | Wait and retry with exponential backoff |
INSUFFICIENT_TIER | 403 | Feature requires higher tier | Upgrade your subscription |
INVALID_REQUEST | 400 | Malformed request body | Check request parameters |
NOT_FOUND | 404 | Resource doesn't exist | Verify the rule ID or endpoint path |
Debugging Tips
-
Test connectivity with the catalog endpoint (no auth required):
const response = await fetch("https://api.ctwise.ai/v1/catalog/sources");
console.log(response.status); // Should be 200 -
Verify API key format: Keys should start with
ctw_prefix -
Check tier access: Some sources (ICH, EMA, WHO) require Starter tier or higher
-
Inspect error responses:
try {
await client.requirements.search({ therapeuticArea: "Oncology" });
} catch (error) {
console.log("Error code:", error.code);
console.log("HTTP status:", error.status);
console.log("Message:", error.message);
}
Support
- npm: npmjs.com/package/@ctwise/sdk
- GitHub: github.com/ctwise/js-sdk
- Issues: github.com/ctwise/js-sdk/issues