// --------------------------------------------------------------------------- // The Last Economy Wire — TypeScript SDK (API version 1) // // Generated from the live capability catalog at https://negativeresistance.com/api/v1 // Zero dependencies. Works in Node 18+ and in the browser. // // Usage: // import { LastEconomyWire } from './lew-sdk'; // const wire = new LastEconomyWire(); // anonymous, 120 req/min // const wire = new LastEconomyWire({ apiKey: 'lew_live_...' }); // higher quota // const mind = await wire.getMind({ days: 30 }); // // Worldwide trial search, honestly: // const scope = await wire.getSources(); // is any registry degraded? // const hits = await wire.searchTrials({ q: 'type 1 diabetes', status: 'active' }); // hits._meta.pagination.hasMore // keep paging until this is false // hits._meta.provenance.worldwide // false means you may not call the answer worldwide // // MCP tools covering the same surface: get_mind_signal, get_hope_signal, get_trends_signal, get_batch_signals, search_world_trials, get_world_trial, get_trial_related_research, get_trial_coverage, get_trial_landscape, find_trial_registrations, get_trial_stats, get_registry_sources, list_capabilities // --------------------------------------------------------------------------- /** Standard success envelope. Failures throw an ApiError instead. */ export interface ApiResponse { data: T; _meta: { api?: string; version?: string; schemaVersion?: string; correlationId?: string; generatedAt?: string; degraded?: boolean; filters?: Record; pagination?: { page?: number; pageSize?: number; totalItems?: number; returned?: number; hasMore?: boolean; nextUrl?: string | null; [k: string]: any; }; provenance?: { registries?: string[]; dedupMethod?: string; worldwide?: boolean; [k: string]: any; }; freshness?: Record; links?: Record; [k: string]: any; }; } /** Thrown for any non-2xx response. Carries the structured error envelope. */ export class ApiError extends Error { status: number; code?: string; hint?: string; correlationId?: string; constructor(message: string, status: number, code?: string, hint?: string, correlationId?: string) { super(message); this.name = 'ApiError'; this.status = status; this.code = code; this.hint = hint; this.correlationId = correlationId; } } export interface LEWClientOptions { /** Base URL of the API. Defaults to the public endpoint. */ baseUrl?: string; /** Optional API key for higher rate limits. */ apiKey?: string; /** Custom fetch implementation (for Node 16 or test doubles). */ fetch?: typeof globalThis.fetch; } export class LastEconomyWire { private baseUrl: string; private apiKey: string | null; private fetchFn: typeof globalThis.fetch; constructor(opts?: LEWClientOptions) { this.baseUrl = (opts?.baseUrl ?? 'https://negativeresistance.com').replace(/\/+$/, ''); this.apiKey = opts?.apiKey ?? null; this.fetchFn = opts?.fetch ?? globalThis.fetch.bind(globalThis); } private async request(path: string, qs?: URLSearchParams): Promise { const qsStr = qs?.toString(); const url = `${this.baseUrl}${path}${qsStr ? `?${qsStr}` : ''}`; const headers: Record = { Accept: 'application/json' }; if (this.apiKey) headers['x-api-key'] = this.apiKey; const res = await this.fetchFn(url, { headers }); const body: any = await res.json(); if (!res.ok) { throw new ApiError( body?.error?.message ?? `HTTP ${res.status}`, res.status, body?.error?.code, body?.error?.hint, body?._meta?.correlationId, ); } return body as ApiResponse; } /** * Capability inventory — every endpoint, MCP tool, workflow and discovery * link. Call this first if you do not know what exists. */ async getCapabilities(): Promise { const res = await this.fetchFn(`${this.baseUrl}/api/v1`, { headers: { Accept: 'application/json' }, }); return res.json(); } /** * Walk every page of a paginated endpoint and return the concatenated * items. Guards against runaway loops with maxPages. */ async paginate( call: (page: number) => Promise, maxPages = 50, ): Promise<{ items: any[]; pages: number; complete: boolean }> { const items: any[] = []; let page = 1; let hasMore = true; while (hasMore && page <= maxPages) { const res = await call(page); const data: any = res.data; const chunk = Array.isArray(data) ? data : (data?.results ?? data?.trials ?? data?.items ?? []); items.push(...chunk); hasMore = Boolean(res._meta?.pagination?.hasMore); page += 1; } return { items, pages: page - 1, complete: !hasMore }; } /** * MIND — the AI-economy signal * * A single trailing-window read on where capital, compute, and conscience are flowing: the four capitals (M·I·N·D), a sparkline, the conscience index, and the cure pulse. * * When to use: The question is about money, compute, networks or data flowing through the AI economy — funding momentum, infrastructure build-out, concentration of attention. * * Note: Derived from the platform's own curated signal feed, not a market data vendor. It measures published attention and announced capital, not settled transactions. * * Example: /api/v1/mind?days=30 */ async getMind(opts?: { /** Length of the trailing analysis window (default 30). */ days?: number; /** Optional keyword — narrows the capital breakdown to signals matching this topic. */ disease?: string; }): Promise { const path = '/api/v1/mind'; const qs = new URLSearchParams(); if (opts?.days !== undefined) qs.set('days', String(opts.days)); if (opts?.disease !== undefined) qs.set('disease', String(opts.disease)); return this.request(path, qs); } /** * Hope — the cure signal * * Global active-trial counts, a per-disease breakdown, the latest curated cure research (paginated), and platform stats. Also carries a `world` block summarising the multi-registry trial index. * * When to use: You want the headline state of cure progress — how much active trial activity exists, per disease, plus the latest curated research reading. * * Note: The `trials` block is a US ClinicalTrials.gov census kept separately from the multi-registry world index. Do NOT add the two together. For worldwide, deduplicated trial evidence use /api/v1/trials/*. * * Example: /api/v1/hope */ async getHope(opts?: { /** trials, research, stats (default all). */ include?: string; /** Filter research articles by disease name. */ disease?: string; /** Max research articles (default 5). */ limit?: number; /** Forward pagination cursor from _meta. */ cursor?: string; /** Zero-based offset. Ignored when `cursor` is supplied. */ offset?: number; }): Promise { const path = '/api/v1/hope'; const qs = new URLSearchParams(); if (opts?.include !== undefined) qs.set('include', String(opts.include)); if (opts?.disease !== undefined) qs.set('disease', String(opts.disease)); if (opts?.limit !== undefined) qs.set('limit', String(opts.limit)); if (opts?.cursor !== undefined) qs.set('cursor', String(opts.cursor)); if (opts?.offset !== undefined) qs.set('offset', String(opts.offset)); return this.request(path, qs); } /** * Trends — the feed + capital lens * * The live shape of the Wire (volume, 7-day momentum, where attention concentrates) plus the 2026 crypto TGE list read through the Cure Protocol distribution lens. * * When to use: You need attention dynamics — what is accelerating or decelerating in coverage — or the crypto capital-distribution reading. * * Example: /api/v1/trends */ async getTrends(opts?: { /** signal, crypto (default all). */ include?: string; /** Set false to drop the daily time series. */ series?: boolean; /** Optional keyword — narrows the signal section to matching stories. */ disease?: string; }): Promise { const path = '/api/v1/trends'; const qs = new URLSearchParams(); if (opts?.include !== undefined) qs.set('include', String(opts.include)); if (opts?.series !== undefined) qs.set('series', String(opts.series)); if (opts?.disease !== undefined) qs.set('disease', String(opts.disease)); return this.request(path, qs); } /** * Batch — every signal in one round-trip * * Fetch mind, hope, and trends together in a single request. Each signal is returned as its own sub-envelope so a partial upstream failure never fails the whole call. * * When to use: You want a broad orientation on the platform in one call, or you are rate-limit sensitive — batch costs ONE rate-limit slot instead of three. * * Note: A signal that fails returns a 503 sub-envelope in place of its data; the surrounding call still succeeds. Check each sub-envelope before using it. * * Example: /api/v1/batch */ async getBatch(opts?: { /** mind, hope, trends (default all). */ include?: string; /** Forwarded to the mind signal. */ days?: number; /** Forwarded to the trends signal. */ series?: boolean; /** Forwarded to every composed signal to narrow results. */ disease?: string; }): Promise { const path = '/api/v1/batch'; const qs = new URLSearchParams(); if (opts?.include !== undefined) qs.set('include', String(opts.include)); if (opts?.days !== undefined) qs.set('days', String(opts.days)); if (opts?.series !== undefined) qs.set('series', String(opts.series)); if (opts?.disease !== undefined) qs.set('disease', String(opts.disease)); return this.request(path, qs); } /** * World trials — search * * Search the deduplicated multi-registry trial store. Sources are primary registries only — ClinicalTrials.gov (US NIH) and ISRCTN (UK) — never secondary aggregators. A trial registered twice is returned once. Every result carries normalized fields, its source registries, source URLs and a quotable citation block. * * When to use: You need actual trial RECORDS — a list of studies matching a clinical question, with provenance you can cite. * * Note: Scope is worldwide by default. Passing `registry` narrows the answer to SINGLE-REGISTRY evidence — say so if you report it. * Note: Isolate a treatment MODALITY with `intervention`, not with `q` alone: q also matches title and sponsor text. * Note: Filters compose with AND. Every filter you add can silently exclude records where that field was never reported by the registry — check data.fieldCompleteness before calling a filtered count exhaustive. * Note: Paginate with page/pageSize and follow _meta.pagination.nextUrl until hasMore is false. * * Example: /api/v1/trials/search?q=type+1+diabetes&status=active&pageSize=25 */ async searchTrials(opts?: { /** Free-text query matched (AND across tokens) over title, conditions, interventions and sponsor. British/American spellings and simple plurals are treated as the same token. */ q?: string; /** EXACT condition term as recorded by a registry. Use `q` unless you already know the registry's exact string — an exact match on a guessed term returns zero rows. */ condition?: string; /** Cure Protocol topic id — one of the 15 curated disease domains. List them via /api/v1/trials/stats. */ topic?: string; /** Restrict to trials carrying a record from one primary registry. Omit for full-index (all-registry) scope. */ registry?: string; /** Normalized recruitment status, or the shorthand `active` for the recruiting + active + not_yet_recruiting group. */ status?: string; /** One or more trial phases, comma-separated. Matches if the trial carries ANY listed phase. Phase is recorded for only part of the corpus — see fieldCompleteness before treating a phase filter as exhaustive. */ phase?: string; /** Normalized study type. */ studyType?: string; /** Free-text match against intervention names and descriptions — the practical way to isolate a MODALITY (e.g. "stem cell", "encapsulation", "gene edit", "xenotransplant"). */ intervention?: string; /** Free-text match against the sponsor name. */ sponsor?: string; /** Match a recruiting country or site country (case-insensitive). Derived from registry location records; not every record declares a location. */ country?: string; /** Minimum normalized enrollment (actual where reported, otherwise target). */ minEnrollment?: number; /** Maximum normalized enrollment. */ maxEnrollment?: number; /** Restrict to trials with (true) or without (false) results posted. See /api/v1/trials/landscape for how each registry's results flag is derived. */ resultsAvailable?: boolean; /** true = only trials registered in more than one registry; false = only single-registry trials. */ crossRegistry?: boolean; /** Only trials whose registry record was last updated on or after this date. */ updatedSince?: string; /** Only trials with a study start date on or after this date. */ startedAfter?: string; /** Only trials with a study start date on or before this date. */ startedBefore?: string; /** Only trials with a completion date on or after this date. */ completionAfter?: string; /** Only trials with a completion date on or before this date. */ completionBefore?: string; /** Result ordering (descending, except canonicalId). Default lastUpdated — newest registry activity first. */ sort?: string; /** Extra evidence blocks: `eligibilityText` (full inclusion/exclusion prose, large), `raw` is NOT available here — use the detail endpoint for raw payloads. */ include?: string; /** Page number, 1-based. Default 1. _meta.pagination.hasMore and _meta.pagination.nextUrl tell you whether and how to continue. */ page?: number; /** Results per page (default 50). `limit` is accepted as an alias. */ pageSize?: number; }): Promise { const path = '/api/v1/trials/search'; const qs = new URLSearchParams(); if (opts?.q !== undefined) qs.set('q', String(opts.q)); if (opts?.condition !== undefined) qs.set('condition', String(opts.condition)); if (opts?.topic !== undefined) qs.set('topic', String(opts.topic)); if (opts?.registry !== undefined) qs.set('registry', String(opts.registry)); if (opts?.status !== undefined) qs.set('status', String(opts.status)); if (opts?.phase !== undefined) qs.set('phase', String(opts.phase)); if (opts?.studyType !== undefined) qs.set('studyType', String(opts.studyType)); if (opts?.intervention !== undefined) qs.set('intervention', String(opts.intervention)); if (opts?.sponsor !== undefined) qs.set('sponsor', String(opts.sponsor)); if (opts?.country !== undefined) qs.set('country', String(opts.country)); if (opts?.minEnrollment !== undefined) qs.set('minEnrollment', String(opts.minEnrollment)); if (opts?.maxEnrollment !== undefined) qs.set('maxEnrollment', String(opts.maxEnrollment)); if (opts?.resultsAvailable !== undefined) qs.set('resultsAvailable', String(opts.resultsAvailable)); if (opts?.crossRegistry !== undefined) qs.set('crossRegistry', String(opts.crossRegistry)); if (opts?.updatedSince !== undefined) qs.set('updatedSince', String(opts.updatedSince)); if (opts?.startedAfter !== undefined) qs.set('startedAfter', String(opts.startedAfter)); if (opts?.startedBefore !== undefined) qs.set('startedBefore', String(opts.startedBefore)); if (opts?.completionAfter !== undefined) qs.set('completionAfter', String(opts.completionAfter)); if (opts?.completionBefore !== undefined) qs.set('completionBefore', String(opts.completionBefore)); if (opts?.sort !== undefined) qs.set('sort', String(opts.sort)); if (opts?.include !== undefined) qs.set('include', String(opts.include)); if (opts?.page !== undefined) qs.set('page', String(opts.page)); if (opts?.pageSize !== undefined) qs.set('pageSize', String(opts.pageSize)); return this.request(path, qs); } /** * World trials — canonical record * * One canonical trial plus every source record behind it: registry, source id, source URL, retrieval timestamp, payload checksum and the raw registry payload. * * When to use: You have a canonicalId and need the complete record, or you need to inspect the untouched registry payload to verify a claim. * * Note: `includeRaw=true` returns the exact registry payload we stored, after contact-field redaction, with its SHA-256. That is the strongest evidence this platform can offer. * * Example: /api/v1/trials/NCT07093359 */ async getTrial(canonicalId: string, opts?: { /** Include the full raw registry payloads (alias: raw). Default false. */ includeRaw?: boolean; }): Promise { let path = '/api/v1/trials/{canonicalId}'; path = path.replace('{canonicalId}', encodeURIComponent(String(canonicalId))); const qs = new URLSearchParams(); if (opts?.includeRaw !== undefined) qs.set('includeRaw', String(opts.includeRaw)); return this.request(path, qs); } /** * World trials — related research (literature sidecar) * * The published research attached to one trial: the publications the registry itself declares, the Europe PMC records whose indexed text mentions the trial’s registration identifiers, and the OTHER registry identifiers those publications mention — each flagged with whether we hold it. A read-only sidecar: nothing it returns enters the index, any count, or deduplication. * * When to use: You have a trial in the index and need the evidence published around it — results papers, secondary analyses, protocol papers — or you want to discover sibling and follow-on registrations recorded in other registries through the literature. * * Note: This is a SIDECAR. Nothing here is part of the trial index or of any count. An identifier surfaced under crossRegistryMentions with inIndex=false is a literature mention only — we hold no record for it, so do not describe it as a trial in this index. * Note: Literature linkage is an identifier MENTION in Europe PMC indexed text or accession lists. It can be a citation, a secondary analysis or a passing reference — it is not by itself proof that the publication reports this trial’s results. Use registryDeclared items with kind=results for that. * Note: registryDeclared needs no network and is always present. If Europe PMC is unreachable the response stays 200 with _meta.degraded=true and an empty literature section — never silently zero. * * Example: /api/v1/trials/NCT04333823/related */ async getTrialRelatedResearch(canonicalId: string, opts?: { /** Comma-separated sections: declared, literature, crossref. Default all three. crossref is derived from literature. */ include?: string; /** Maximum literature hits per registration identifier searched. Default 25. */ limit?: number; }): Promise { let path = '/api/v1/trials/{canonicalId}/related'; path = path.replace('{canonicalId}', encodeURIComponent(String(canonicalId))); const qs = new URLSearchParams(); if (opts?.include !== undefined) qs.set('include', String(opts.include)); if (opts?.limit !== undefined) qs.set('limit', String(opts.limit)); return this.request(path, qs); } /** * World trials — cross-registry coverage and duplicate detection * * How many DISTINCT trials exist for a condition across every registry in the index, how many are the same trial registered more than once, which trials are exclusive to each registry, and the count inflation you would suffer by querying each registry separately and adding the results. * * When to use: Before you state a worldwide count. This is the endpoint that tells you whether a number is a true distinct count or an inflated sum. * * Note: `inflation` is the error you would have made by querying each registry separately and adding. Quote the distinct count, not the sum. * * Example: /api/v1/trials/coverage?q=type+1+diabetes */ async getTrialCoverage(opts?: { /** Free-text query matched (AND across tokens) over title, conditions, interventions and sponsor. British/American spellings and simple plurals are treated as the same token. */ q?: string; /** EXACT condition term as recorded by a registry. Use `q` unless you already know the registry's exact string — an exact match on a guessed term returns zero rows. */ condition?: string; /** Cure Protocol topic id — one of the 15 curated disease domains. List them via /api/v1/trials/stats. */ topic?: string; /** Restrict to trials carrying a record from one primary registry. Omit for full-index (all-registry) scope. */ registry?: string; /** Normalized recruitment status, or the shorthand `active` for the recruiting + active + not_yet_recruiting group. */ status?: string; /** One or more trial phases, comma-separated. Matches if the trial carries ANY listed phase. Phase is recorded for only part of the corpus — see fieldCompleteness before treating a phase filter as exhaustive. */ phase?: string; /** Normalized study type. */ studyType?: string; /** Free-text match against intervention names and descriptions — the practical way to isolate a MODALITY (e.g. "stem cell", "encapsulation", "gene edit", "xenotransplant"). */ intervention?: string; /** Free-text match against the sponsor name. */ sponsor?: string; /** Match a recruiting country or site country (case-insensitive). Derived from registry location records; not every record declares a location. */ country?: string; /** Minimum normalized enrollment (actual where reported, otherwise target). */ minEnrollment?: number; /** Maximum normalized enrollment. */ maxEnrollment?: number; /** Restrict to trials with (true) or without (false) results posted. See /api/v1/trials/landscape for how each registry's results flag is derived. */ resultsAvailable?: boolean; /** true = only trials registered in more than one registry; false = only single-registry trials. */ crossRegistry?: boolean; /** Only trials whose registry record was last updated on or after this date. */ updatedSince?: string; /** Only trials with a study start date on or after this date. */ startedAfter?: string; /** Only trials with a study start date on or before this date. */ startedBefore?: string; /** Only trials with a completion date on or after this date. */ completionAfter?: string; /** Only trials with a completion date on or before this date. */ completionBefore?: string; }): Promise { const path = '/api/v1/trials/coverage'; const qs = new URLSearchParams(); if (opts?.q !== undefined) qs.set('q', String(opts.q)); if (opts?.condition !== undefined) qs.set('condition', String(opts.condition)); if (opts?.topic !== undefined) qs.set('topic', String(opts.topic)); if (opts?.registry !== undefined) qs.set('registry', String(opts.registry)); if (opts?.status !== undefined) qs.set('status', String(opts.status)); if (opts?.phase !== undefined) qs.set('phase', String(opts.phase)); if (opts?.studyType !== undefined) qs.set('studyType', String(opts.studyType)); if (opts?.intervention !== undefined) qs.set('intervention', String(opts.intervention)); if (opts?.sponsor !== undefined) qs.set('sponsor', String(opts.sponsor)); if (opts?.country !== undefined) qs.set('country', String(opts.country)); if (opts?.minEnrollment !== undefined) qs.set('minEnrollment', String(opts.minEnrollment)); if (opts?.maxEnrollment !== undefined) qs.set('maxEnrollment', String(opts.maxEnrollment)); if (opts?.resultsAvailable !== undefined) qs.set('resultsAvailable', String(opts.resultsAvailable)); if (opts?.crossRegistry !== undefined) qs.set('crossRegistry', String(opts.crossRegistry)); if (opts?.updatedSince !== undefined) qs.set('updatedSince', String(opts.updatedSince)); if (opts?.startedAfter !== undefined) qs.set('startedAfter', String(opts.startedAfter)); if (opts?.startedBefore !== undefined) qs.set('startedBefore', String(opts.startedBefore)); if (opts?.completionAfter !== undefined) qs.set('completionAfter', String(opts.completionAfter)); if (opts?.completionBefore !== undefined) qs.set('completionBefore', String(opts.completionBefore)); return this.request(path, qs); } /** * World trials — landscape structure and change over time * * The derived shape of research on a condition: phase, status and study-type mix, sponsor concentration, enrollment scale, the completed-but-unreported results gap, momentum over 30/90/365 days, and a real diff against a stored earlier snapshot. Reports per-registry field completeness so a distribution built from one registry is never mistaken for a world-scale fact. * * When to use: You need the SHAPE of a field rather than its members — where the phase mass sits, who sponsors it, whether it is accelerating, how much of it went unreported. * * Note: `fieldCompleteness` is not decoration. If phase is recorded for 25% of a registry's records, a phase distribution describes that 25% — say so. * Note: `evidenceGap` reports completed-but-unreported trials per registry and refuses to publish a combined figure when the registries' results flags are not comparable. * Note: `changeSince` is a real diff against a stored earlier snapshot, not a recomputation. * * Example: /api/v1/trials/landscape?q=type+1+diabetes */ async getTrialLandscape(opts?: { /** Free-text query matched (AND across tokens) over title, conditions, interventions and sponsor. British/American spellings and simple plurals are treated as the same token. */ q?: string; /** EXACT condition term as recorded by a registry. Use `q` unless you already know the registry's exact string — an exact match on a guessed term returns zero rows. */ condition?: string; /** Cure Protocol topic id — one of the 15 curated disease domains. List them via /api/v1/trials/stats. */ topic?: string; /** Restrict to trials carrying a record from one primary registry. Omit for full-index (all-registry) scope. */ registry?: string; /** Normalized recruitment status, or the shorthand `active` for the recruiting + active + not_yet_recruiting group. */ status?: string; /** One or more trial phases, comma-separated. Matches if the trial carries ANY listed phase. Phase is recorded for only part of the corpus — see fieldCompleteness before treating a phase filter as exhaustive. */ phase?: string; /** Normalized study type. */ studyType?: string; /** Free-text match against intervention names and descriptions — the practical way to isolate a MODALITY (e.g. "stem cell", "encapsulation", "gene edit", "xenotransplant"). */ intervention?: string; /** Free-text match against the sponsor name. */ sponsor?: string; /** Match a recruiting country or site country (case-insensitive). Derived from registry location records; not every record declares a location. */ country?: string; /** Minimum normalized enrollment (actual where reported, otherwise target). */ minEnrollment?: number; /** Maximum normalized enrollment. */ maxEnrollment?: number; /** Restrict to trials with (true) or without (false) results posted. See /api/v1/trials/landscape for how each registry's results flag is derived. */ resultsAvailable?: boolean; /** true = only trials registered in more than one registry; false = only single-registry trials. */ crossRegistry?: boolean; /** Only trials whose registry record was last updated on or after this date. */ updatedSince?: string; /** Only trials with a study start date on or after this date. */ startedAfter?: string; /** Only trials with a study start date on or before this date. */ startedBefore?: string; /** Only trials with a completion date on or after this date. */ completionAfter?: string; /** Only trials with a completion date on or before this date. */ completionBefore?: string; }): Promise { const path = '/api/v1/trials/landscape'; const qs = new URLSearchParams(); if (opts?.q !== undefined) qs.set('q', String(opts.q)); if (opts?.condition !== undefined) qs.set('condition', String(opts.condition)); if (opts?.topic !== undefined) qs.set('topic', String(opts.topic)); if (opts?.registry !== undefined) qs.set('registry', String(opts.registry)); if (opts?.status !== undefined) qs.set('status', String(opts.status)); if (opts?.phase !== undefined) qs.set('phase', String(opts.phase)); if (opts?.studyType !== undefined) qs.set('studyType', String(opts.studyType)); if (opts?.intervention !== undefined) qs.set('intervention', String(opts.intervention)); if (opts?.sponsor !== undefined) qs.set('sponsor', String(opts.sponsor)); if (opts?.country !== undefined) qs.set('country', String(opts.country)); if (opts?.minEnrollment !== undefined) qs.set('minEnrollment', String(opts.minEnrollment)); if (opts?.maxEnrollment !== undefined) qs.set('maxEnrollment', String(opts.maxEnrollment)); if (opts?.resultsAvailable !== undefined) qs.set('resultsAvailable', String(opts.resultsAvailable)); if (opts?.crossRegistry !== undefined) qs.set('crossRegistry', String(opts.crossRegistry)); if (opts?.updatedSince !== undefined) qs.set('updatedSince', String(opts.updatedSince)); if (opts?.startedAfter !== undefined) qs.set('startedAfter', String(opts.startedAfter)); if (opts?.startedBefore !== undefined) qs.set('startedBefore', String(opts.startedBefore)); if (opts?.completionAfter !== undefined) qs.set('completionAfter', String(opts.completionAfter)); if (opts?.completionBefore !== undefined) qs.set('completionBefore', String(opts.completionBefore)); return this.request(path, qs); } /** * World trials — where else is this trial registered * * Given any registry identifier, every registry in which that same real-world trial is registered, the exact identifier evidence linking them, and which registries it is absent from. * * When to use: You have one identifier from an outside source and need to know whether it is the same study as another identifier you are holding. * * Note: Absence from a registry is reported as absence from OUR index of that registry, which is not proof the trial was never registered there. * * Example: /api/v1/trials/registrations?id=NCT07093359 */ async getTrialRegistrations(opts: { /** Any known identifier, e.g. NCT07093359 or ISRCTN13533177. */ id: string; }): Promise { const path = '/api/v1/trials/registrations'; const qs = new URLSearchParams(); if (opts?.id !== undefined) qs.set('id', String(opts.id)); return this.request(path, qs); } /** * World trials — counts with attribution * * Source-record count, deduplicated canonical trial count, active count, the curated topic list, and per-registry attribution. Counts are never summed across registries. * * When to use: First call when orienting on the trial corpus: how big it is, which registries contribute, and what topic ids exist for filtering. * * Example: /api/v1/trials/stats */ async getTrialStats(): Promise { const path = '/api/v1/trials/stats'; const qs = new URLSearchParams(); return this.request(path, qs); } /** * Registry sources — provenance, freshness and contract status * * Every upstream registry: base URL, API type, licence, last successful sync, record count, whether the source is currently degraded, and the live contract-test verdict. * * When to use: Before presenting trial evidence as current or worldwide. This endpoint tells you which registries are healthy, when each last synchronised, and which are degraded. * * Note: A registry with `degraded: true` or a stale `lastSuccessAt` means results are partial. Say so rather than implying full worldwide coverage. * * Example: /api/v1/sources */ async getSources(): Promise { const path = '/api/v1/sources'; const qs = new URLSearchParams(); return this.request(path, qs); } /** * Status — service health * * Operational status, dependency health, the live endpoint catalog, and rolling SLO compliance per route. * * When to use: A call failed and you need to know whether the platform is degraded before retrying or reporting an error. * * Example: /api/v1/status */ async getStatus(): Promise { const path = '/api/v1/status'; const qs = new URLSearchParams(); return this.request(path, qs); } /** * Key usage & settings * * GET returns per-key call volume, error rates, and latency over 7- and 30-day windows plus your current rate-limit state. PATCH lets you tune your key's rate limit (120–10 000) or rename it. * * When to use: You need to know your own consumption or want to raise your own rate limit. */ async getUsage(opts?: { /** Reporting window for the usage breakdown (GET). */ window?: string; }): Promise { const path = '/api/v1/keys/usage'; const qs = new URLSearchParams(); if (opts?.window !== undefined) qs.set('window', String(opts.window)); return this.request(path, qs); } /** Mint a new API key (self-serve). */ async createKey(label: string): Promise { return this.mutate(`/api/v1/keys`, 'POST', { label }); } /** Update the rate limit or label for the current API key. */ async updateKeySettings(settings: { rateLimit?: number; label?: string }): Promise { return this.mutate(`/api/v1/keys/usage`, 'PATCH', settings); } private async mutate(path: string, method: string, payload: any): Promise { const headers: Record = { 'Content-Type': 'application/json', Accept: 'application/json', }; if (this.apiKey) headers['x-api-key'] = this.apiKey; const res = await this.fetchFn(`${this.baseUrl}${path}`, { method, headers, body: JSON.stringify(payload), }); const body: any = await res.json(); if (!res.ok) { throw new ApiError( body?.error?.message ?? `HTTP ${res.status}`, res.status, body?.error?.code, body?.error?.hint, body?._meta?.correlationId, ); } return body; } } export default LastEconomyWire;