Skip to main content

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

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`);
});

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)

EndpointDescription
GET /v1/catalog/sourcesList all available regulatory sources

Protected Endpoints (API Key Required)

Regulatory Rules

EndpointDescription
GET /v1/rulesSearch rules by keyword, source, category
GET /v1/rules/{id}Get specific rule details
POST /v1/semantic-searchAI-powered natural language search
GET /v1/rules/searchAlternative GET-based semantic search

FDA 483 Intelligence

EndpointDescription
POST /v1/483/observations/searchSemantic search for 483 observations
GET /v1/483/facilities/{fei}Get facility profile
GET /v1/483/facilities/{fei}/citationsGet facility citations
GET /v1/483/risk-scores/{fei}Get facility risk score
GET /v1/483/cfr-referencesList CFR references
GET /v1/483/cfr-references/{cfr}Get CFR details
GET /v1/483/analytics/summaryGet analytics summary
GET /v1/483/citations/trendingGet trending citations
GET /v1/483/regulatory-mappingList regulatory mappings
GET /v1/483/regulatory-mapping/{cfr}Get CFR to ICH mapping
GET /v1/483/analytics/benchmarksGet 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 CodeHTTP StatusCauseSolution
INVALID_API_KEY401Invalid or expired API keyVerify your API key is correct and active
MISSING_API_KEY401No API key providedInclude X-Api-Key header in requests
RATE_LIMIT_EXCEEDED429Too many requestsWait and retry with exponential backoff
INSUFFICIENT_TIER403Feature requires higher tierUpgrade your subscription
INVALID_REQUEST400Malformed request bodyCheck request parameters
NOT_FOUND404Resource doesn't existVerify the rule ID or endpoint path

Debugging Tips

  1. 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
  2. Verify API key format: Keys should start with ctw_ prefix

  3. Check tier access: Some sources (ICH, EMA, WHO) require Starter tier or higher

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