19,545 linked objects as static JSON, with no key and permissive CORS, plus an MCP server for assistants. Below: the spec, the types, three recipes you can paste, embeddable cards, and what attribution the licence asks of you.
/api/v1/, generated at build time from the file layout, importable into Postman, Stoplight or an SDK generator (an older hand-written copy is at /openapi.yaml).npm run mcp exposes search, get_entity, list_kind, for_cancer and rank tools over stdio.The schema file has no dependencies beyond Zod. Copy it (or add the repo as a git submodule) and infer the types; the JSON under /api/v1/ parses with it unchanged.
// types/onco.ts: copy src/lib/schema.ts from the repo next to this file, then
import { EntitySchema, type Entity, type Drug, type Trial, KIND_META } from "./schema";
export type Neighbours = Record<string, Array<{ id: string; kind: string; name: string; route: string }>>;
export type EntityResponse = { entity: Entity; route: string; neighbours: Neighbours };
export async function getEntity(id: string): Promise<EntityResponse> {
const r = await fetch(`https://onco.cc/api/v1/entities/${id}.json`);
if (!r.ok) throw new Error(`OnCo: ${id} not found`);
const data = (await r.json()) as EntityResponse;
data.entity = EntitySchema.parse(data.entity); // validates and applies defaults
return data;
}
export async function listKind<K extends Entity["kind"]>(kind: K): Promise<Extract<Entity, { kind: K }>[]> {
const r = await fetch(`https://onco.cc/api/v1/${KIND_META[kind].plural}.json`);
return (await r.json()) as Extract<Entity, { kind: K }>[];
}Take a cancer and a set of biomarkers, find the products aimed at those targets in that cancer, then pull live recruiting studies from ClinicalTrials.gov for each product. Two static fetches plus the public registry API; no server needed.
const SITE = "https://onco.cc";
const cancerId = "tnbc";
const biomarkers = ["trop2", "pdl1", "brca"]; // OnCo target ids; see /targets/
const { neighbours } = await fetch(`${SITE}/api/v1/entities/${cancerId}.json`).then((r) => r.json());
const products = (neighbours.drug ?? []) as Array<{ id: string; name: string }>;
const drugs = await fetch(`${SITE}/api/v1/drugs.json`).then((r) => r.json());
const byId = new Map(drugs.map((d: { id: string }) => [d.id, d]));
const matches = products
.map((p) => byId.get(p.id))
.filter((d) => d && d.targets.some((t: string) => biomarkers.includes(t)) && d.status !== "withdrawn");
for (const d of matches) {
const q = new URLSearchParams({ "query.intr": d.name.replace(/\s*\(.*?\)\s*/g, " ").trim(), "query.cond": "triple negative breast cancer", "filter.overallStatus": "RECRUITING", pageSize: "5", format: "json" });
const studies = await fetch(`https://clinicaltrials.gov/api/v2/studies?${q}`).then((r) => r.json());
console.log(d.name, d.status, studies.studies?.map((s: { protocolSection: { identificationModule: { nctId: string } } }) => s.protocolSection.identificationModule.nctId));
}The site’s own tumour board and navigator use the same approach; see src/lib/biomarker-match.ts and src/lib/ctgov.ts in the repo for the edge cases (aliases, combination names, condition wording).
One fetch of all.json gives every object plus a backlink map, enough to compute counts, funnels and rankings client-side. This counts products by modality and status for a target.
const { entities, incoming } = await fetch("https://onco.cc/api/v1/all.json").then((r) => r.json());
const drugs = entities.filter((e) => e.kind === "drug" && e.targets.includes("her2"));
const funnel = {};
for (const d of drugs) funnel[d.status ?? "unknown"] = (funnel[d.status ?? "unknown"] ?? 0) + 1;
console.table(funnel);
// Everything that links to HER2, grouped by kind (trials, cancers, pathways, papers...)
const linkers = incoming["her2"].reduce((m, x) => ({ ...m, [x.kind]: (m[x.kind] ?? 0) + 1 }), {});
console.table(linkers);
// Refresh: meta.json carries the build time; re-fetch when it changes
const { built, counts } = await fetch("https://onco.cc/api/v1/meta.json").then((r) => r.json());all.json is a few megabytes; for a production dashboard fetch the per-kind files you need and cache on meta.json’s built timestamp. The pages under count what exists, and where, pipeline and scorecards are built this way at build time.
The MCP server gives an assistant grounded tools instead of a scraped context window. Clone the repo, install, and point your MCP-capable client at it.
git clone https://github.com/judegomila/OnCo.git && cd OnCo && npm ci
# Claude Desktop, Cursor, or any MCP client: add a stdio server
{
"mcpServers": {
"onco": { "command": "npm", "args": ["run", "mcp"], "cwd": "/path/to/OnCo" }
}
}
# Tools exposed: search(query, limit), get_entity(id), list_kind(kind),
# for_cancer(cancerId), rank(...). Every answer carries the object url so the
# assistant can cite onco.cc and the reader can check the source.Without MCP, the same grounding works with search.json (compact id, kind, name, tldr, route documents) as a retrieval index and entities/<id>.json as the fetch step.
Every object has an iframe card at /embed/<id>/: kind, status, name and plain-English summary, linking back to the page. Cards are noindex and inherit nothing from your page.
<iframe src="https://onco.cc/embed/trop2/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe><iframe src="https://onco.cc/embed/tnbc/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe><iframe src="https://onco.cc/embed/trastuzumab-deruxtecan/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe><iframe src="https://onco.cc/embed/merck/" width="360" height="190" style="border:0;border-radius:12px" loading="lazy"></iframe>| Path | Contents |
|---|---|
| /api/v1/all.json | All 19,545 entities plus an incoming backlink map |
| /api/v1/search.json | Compact search documents |
| /api/v1/<plural>.json | cancers, fronts, technologies, targets, drugs, companies, institutions, pathways, terms, trials, pairings, roadmaps, ideas, collections, people, bottlenecks, key papers, journals, biomarkers, years |
| /api/v1/entities/<id>.json | One entity with route and neighbours by kind |
| /api/v1/my-cancers.json | Every cancer as id, name, route and hub group (the chooser list) |
| /api/v1/spotlight.json | Home page spotlight: one hero record per kind in the daily rotation |
| /api/v1/ranking.json | Institution ranking with score components |
| /api/v1/benchmark.json | The open evaluation question set |
| /api/v1/meta.json | Build time, counts, licence, attribution text |
| /catalysts/feed.ics | iCalendar feed of catalysts and readouts |
| /trials/index.json, /trials/<drugId>.json | Weekly ClinicalTrials.gov phase 2/3 study counts and lists per product |
| /globocan/countries.json | GLOBOCAN 2022 incidence and mortality by country and site |
The data are CC BY-NC 4.0 (free for non-commercial use; commercial use needs a licence from OnCo) and the code is MIT. Name OnCo and link to onco.cc wherever the data or text derived from it appears, for example Data from OnCo (onco.cc), CC BY 4.0. The same notice is in /api/v1/meta.json so it can travel with the data.
Logos remain their owners’ trademarks; molecule structures keep their PubChem and RCSB terms; GLOBOCAN data keep IARC’s terms; ClinicalTrials.gov data are public domain. Nothing here is medical advice, and every record carries the date it was last checked: show it.
Found an error while building? Suggest an edit or open a pull request; corrections are logged at /corrections/.