Claude Skill

alterlab-cbioportal

Query cBioPortal via its keyless REST API for cancer genomics across TCGA, GENIE, MSK-IMPACT and hundreds of studies — somatic mutations, copy-number alterations (GISTIC), mRNA/protein expression, structural variants, and patient-level clinical/survival data. Use when asked how o

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alterlab-ieu-alterlab-academic-skills-skills_databases_alterlab-cbioportal-e4836c0.zip · 11 KB
Part of alterlab-ieu/alterlab-academic-skills — 94 skills

Install

skills CLI npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/databases/alterlab-cbioportal
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
Git git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

cBioPortal Database

Overview

cBioPortal for Cancer Genomics (https://www.cbioportal.org/) is an open-access resource for exploring, visualizing, and analyzing multidimensional cancer genomics data. It hosts data from The Cancer Genome Atlas (TCGA), AACR Project GENIE, MSK-IMPACT, and hundreds of other cancer studies — covering mutations, copy number alterations (CNA), structural variants, mRNA/protein expression, methylation, and clinical data for thousands of cancer samples.

Key resources:

When to Use This Skill

Use cBioPortal when:

  • Mutation landscape: What fraction of a cancer type has mutations in a specific gene?
  • Oncogene/TSG validation: Is a gene frequently mutated, amplified, or deleted in cancer?
  • Co-mutation patterns: Are mutations in gene A and gene B mutually exclusive or co-occurring?
  • Survival analysis: Do mutations in a gene associate with better or worse patient outcomes?
  • Alteration profiles: What types of alterations (missense, truncating, amplification, deletion) affect a gene?
  • Pan-cancer analysis: Compare alteration frequencies across cancer types
  • Clinical associations: Link genomic alterations to clinical variables (stage, grade, treatment response)
  • TCGA/GENIE exploration: Systematic access to TCGA and clinical sequencing datasets

Does NOT Trigger

Scenario Use Instead
Germline variant pathogenicity / clinical significance alterlab-clinvar
COSMIC Cancer Gene Census, mutational signatures (SBS), curated somatic catalog alterlab-cosmic
CRISPR/RNAi gene-dependency (essentiality) in cancer cell lines alterlab-depmap
Aggregated target–disease association evidence and tractability alterlab-opentargets

Core Capabilities

1. cBioPortal REST API

Base URL: https://www.cbioportal.org/api

The API is RESTful, returns JSON, and requires no API key for public data.

import requests

BASE_URL = "https://www.cbioportal.org/api"
HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}

def cbioportal_get(endpoint, params=None):
    url = f"{BASE_URL}/{endpoint}"
    response = requests.get(url, params=params, headers=HEADERS)
    response.raise_for_status()
    return response.json()

def cbioportal_post(endpoint, body):
    url = f"{BASE_URL}/{endpoint}"
    response = requests.post(url, json=body, headers=HEADERS)
    response.raise_for_status()
    return response.json()

2. Browse Studies

def get_all_studies():
    """List all available cancer studies.

    The public portal hosts 540+ studies (2026-09), so a pageSize of 500 would
    silently truncate the list; ask for more than you expect.
    """
    return cbioportal_get("studies", {"pageSize": 100000})

# Each study has:
# studyId: unique identifier (e.g., "brca_tcga")
# name: human-readable name
# description: dataset description
# cancerTypeId: cancer type abbreviation
# referenceGenome: hg19 or hg38
# allSampleCount: samples in the study
# (pass projection=DETAILED to also get pmid, citation, sequencedSampleCount, ...)

studies = get_all_studies()
print(f"Total studies: {len(studies)}")

# Common TCGA study IDs — each cancer has several versions:
#   *_tcga                     original TCGA Firehose Legacy (e.g. brca_tcga)
#   *_tcga_pan_can_atlas_2018  harmonized PanCancer Atlas (preferred for pan-cancer work)
#   *_tcga_gdc                 GDC re-processed data
# e.g. brca_tcga, luad_tcga, coadread_tcga, gbm_tcga, prad_tcga, skcm_tcga

# Filter for TCGA studies
tcga_studies = [s for s in studies if "tcga" in s["studyId"]]
print([s["studyId"] for s in tcga_studies[:10]])

3. Molecular Profiles

Each study has multiple molecular profiles (mutation, CNA, expression, etc.):

def get_molecular_profiles(study_id):
    """Get all molecular profiles for a study."""
    return cbioportal_get(f"studies/{study_id}/molecular-profiles")

profiles = get_molecular_profiles("brca_tcga")
for p in profiles:
    print(f"  {p['molecularProfileId']}: {p['name']} ({p['molecularAlterationType']})")

# Alteration types:
# MUTATION_EXTENDED — somatic mutations
# COPY_NUMBER_ALTERATION — CNA (GISTIC)
# MRNA_EXPRESSION — mRNA expression
# PROTEIN_LEVEL — RPPA protein expression
# STRUCTURAL_VARIANT — fusions/rearrangements

4. Mutation Data

def get_mutations(molecular_profile_id, entrez_gene_ids, sample_list_id=None):
    """Get mutations for specified genes in a molecular profile."""
    body = {
        "entrezGeneIds": entrez_gene_ids,
        "sampleListId": sample_list_id or molecular_profile_id.replace("_mutations", "_all")
    }
    return cbioportal_post(
        f"molecular-profiles/{molecular_profile_id}/mutations/fetch",
        body
    )

# BRCA1 Entrez ID is 672, TP53 is 7157, PTEN is 5728
mutations = get_mutations("brca_tcga_mutations", entrez_gene_ids=[7157])  # TP53

# Each mutation record contains:
# patientId, sampleId, entrezGeneId (the nested gene.hugoGeneSymbol only
#   appears with ?projection=DETAILED on the fetch URL)
# mutationType (Missense_Mutation, Nonsense_Mutation, Frame_Shift_Del, etc.)
# proteinChange (e.g., "R175H"), variantType
# ncbiBuild, chr, startPosition, endPosition, referenceAllele, variantAllele
# mutationStatus (Somatic/Germline)
# tumorAltCount, tumorRefCount (read counts — there is no VAF field; derive it)

import pandas as pd
df = pd.DataFrame(mutations)
df["vaf"] = df["tumorAltCount"] / (df["tumorAltCount"] + df["tumorRefCount"])
print(df[["patientId", "mutationType", "proteinChange", "vaf"]].head())
print(f"\nMutation types:\n{df['mutationType'].value_counts()}")

5. Copy Number Alteration Data

def get_cna(molecular_profile_id, entrez_gene_ids):
    """Get discrete CNA data (GISTIC: -2, -1, 0, 1, 2)."""
    body = {
        "entrezGeneIds": entrez_gene_ids,
        "sampleListId": molecular_profile_id.replace("_gistic", "_all").replace("_cna", "_all")
    }
    return cbioportal_post(
        f"molecular-profiles/{molecular_profile_id}/discrete-copy-number/fetch",
        body
    )

# GISTIC values:
# -2 = Deep deletion (homozygous loss)
# -1 = Shallow deletion (heterozygous loss)
#  0 = Diploid (neutral)
#  1 = Low-level gain
#  2 = High-level amplification

cna_data = get_cna("brca_tcga_gistic", entrez_gene_ids=[1956])  # EGFR
df_cna = pd.DataFrame(cna_data)
print(df_cna["value"].value_counts())

6. Alteration Frequency (OncoPrint-style)

def get_alteration_frequency(study_id, gene_symbols, alteration_types=None):
    """Compute alteration frequencies for genes across a cancer study."""
    import requests, pandas as pd

    # Denominator = samples profiled for mutations (cBioPortal's own convention).
    # 'all_cases_in_study' also counts unsequenced samples and deflates the
    # frequency (brca_tcga: 1,108 samples in study vs 982 sequenced).
    samples = requests.get(
        f"{BASE_URL}/studies/{study_id}/sample-lists",
        headers=HEADERS
    ).json()
    by_category = {s["category"]: s["sampleListId"] for s in samples}
    sample_list_id = (by_category.get("all_cases_with_mutation_data")
                      or by_category.get("all_cases_in_study"))
    total_samples = len(requests.get(
        f"{BASE_URL}/sample-lists/{sample_list_id}/sample-ids",
        headers=HEADERS
    ).json())

    # Get gene Entrez IDs. /genes/fetch takes a plain JSON array of identifiers
    # plus a geneIdType query param; symbols WITHOUT the param resolve to [].
    gene_data = requests.post(
        f"{BASE_URL}/genes/fetch",
        params={"geneIdType": "HUGO_GENE_SYMBOL"},
        json=gene_symbols,
        headers=HEADERS
    ).json()
    # Response order is not guaranteed; map by symbol.
    entrez_by_symbol = {g["hugoGeneSymbol"]: g["entrezGeneId"] for g in gene_data}
    entrez_ids = [entrez_by_symbol[g] for g in gene_symbols if g in entrez_by_symbol]

    # Get mutations
    mutation_profile = f"{study_id}_mutations"
    mutations = get_mutations(mutation_profile, entrez_ids, sample_list_id)

    freq = {}
    for g_symbol, e_id in entrez_by_symbol.items():
        # Count samples (not patients) so numerator and denominator match.
        mutated = len(set(m["sampleId"] for m in mutations if m["entrezGeneId"] == e_id))
        freq[g_symbol] = mutated / total_samples * 100

    return freq

# Example
freq = get_alteration_frequency("brca_tcga", ["TP53", "PIK3CA", "BRCA1", "BRCA2"])
for gene, pct in sorted(freq.items(), key=lambda x: -x[1]):
    print(f"  {gene}: {pct:.1f}%")

7. Clinical Data

The global /clinical-data/fetch endpoint is POST-only (a GET returns HTTP 405). The simplest path for one study is the per-study GET endpoint, which returns a list of {patientId, studyId, clinicalAttributeId, value} records:

def get_patient_clinical_data(study_id, attribute_ids):
    """Patient-level clinical data via the per-study GET endpoint.

    GET /studies/{studyId}/clinical-data?clinicalDataType=PATIENT&attributeId=...
    accepts a single attributeId, so we query each and concatenate.
    """
    records = []
    for attr in attribute_ids:
        records += cbioportal_get(
            f"studies/{study_id}/clinical-data",
            {"clinicalDataType": "PATIENT", "attributeId": attr, "pageSize": 100000},
        )
    return records

# Clinical attributes include:
# OS_STATUS, OS_MONTHS, DFS_STATUS, DFS_MONTHS (survival)
# AJCC_PATHOLOGIC_TUMOR_STAGE, GRADE, AGE, SEX, RACE
# Study-specific attributes vary — list them with get_clinical_attributes().
# GOTCHA: OS_STATUS / DFS_STATUS are encoded "1:DECEASED" / "0:LIVING"
# (event:label), not bare 0/1 — split on ":" before survival analysis.

def get_clinical_attributes(study_id):
    """List all available clinical attributes for a study."""
    return cbioportal_get(f"studies/{study_id}/clinical-attributes")

Query Workflows

Workflow 1: Gene Alteration Profile in a Cancer Type

import requests, pandas as pd

def alteration_profile(study_id, gene_symbol):
    """Full alteration profile for a gene in a cancer study."""

    # 1. Get gene Entrez ID (plain array body + geneIdType param)
    gene_info = requests.post(
        f"{BASE_URL}/genes/fetch",
        params={"geneIdType": "HUGO_GENE_SYMBOL"},
        json=[gene_symbol],
        headers=HEADERS
    ).json()[0]
    entrez_id = gene_info["entrezGeneId"]

    # 2. Get mutations
    mutations = get_mutations(f"{study_id}_mutations", [entrez_id])
    mut_df = pd.DataFrame(mutations) if mutations else pd.DataFrame()

    # 3. Get CNAs
    cna = get_cna(f"{study_id}_gistic", [entrez_id])
    cna_df = pd.DataFrame(cna) if cna else pd.DataFrame()

    # 4. Summary
    n_mut = len(set(mut_df["patientId"])) if not mut_df.empty else 0
    n_amp = len(cna_df[cna_df["value"] == 2]) if not cna_df.empty else 0
    n_del = len(cna_df[cna_df["value"] == -2]) if not cna_df.empty else 0

    return {"mutations": n_mut, "amplifications": n_amp, "deep_deletions": n_del}

result = alteration_profile("brca_tcga", "PIK3CA")
print(result)

Workflow 2: Pan-Cancer Gene Mutation Frequency

import requests, pandas as pd

def pan_cancer_mutation_freq(gene_symbol, cancer_study_ids=None):
    """Mutation frequency of a gene across multiple cancer types."""
    studies = get_all_studies()
    if cancer_study_ids:
        studies = [s for s in studies if s["studyId"] in cancer_study_ids]

    results = []
    for study in studies[:20]:  # Limit for demo
        try:
            freq = get_alteration_frequency(study["studyId"], [gene_symbol])
            results.append({
                "study": study["studyId"],
                "cancer": study.get("cancerTypeId", ""),
                "mutation_pct": freq.get(gene_symbol, 0)
            })
        except Exception:
            pass

    df = pd.DataFrame(results).sort_values("mutation_pct", ascending=False)
    return df

Workflow 3: Survival Analysis by Mutation Status

import requests, pandas as pd

def survival_by_mutation(study_id, gene_symbol):
    """Get survival data split by mutation status."""
    # This workflow fetches clinical and mutation data for downstream analysis

    gene_info = requests.post(
        f"{BASE_URL}/genes/fetch",
        params={"geneIdType": "HUGO_GENE_SYMBOL"},
        json=[gene_symbol],
        headers=HEADERS
    ).json()[0]
    entrez_id = gene_info["entrezGeneId"]

    mutations = get_mutations(f"{study_id}_mutations", [entrez_id])
    mutated_patients = set(m["patientId"] for m in mutations)

    # Patient-level survival via the per-study GET endpoint (clinical-data/fetch
    # is POST-only — a GET there returns HTTP 405).
    clinical = get_patient_clinical_data(study_id, ["OS_MONTHS", "OS_STATUS"])
    clinical_df = pd.DataFrame(clinical)

    os_wide = clinical_df.pivot(index="patientId", columns="clinicalAttributeId", values="value")
    # OS_STATUS is encoded as "1:DECEASED" / "0:LIVING"; split off the 0/1 event flag.
    if "OS_STATUS" in os_wide:
        os_wide["OS_EVENT"] = os_wide["OS_STATUS"].str.startswith("1").astype("Int64")
    os_wide["OS_MONTHS"] = pd.to_numeric(os_wide.get("OS_MONTHS"), errors="coerce")
    os_wide["mutated"] = os_wide.index.isin(mutated_patients)

    return os_wide

Key API Endpoints Summary

Endpoint Description
GET /studies List all studies
GET /studies/{studyId}/molecular-profiles Molecular profiles for a study
POST /molecular-profiles/{profileId}/mutations/fetch Get mutation data
POST /molecular-profiles/{profileId}/discrete-copy-number/fetch Get CNA data
POST /molecular-profiles/{profileId}/molecular-data/fetch Get expression data
GET /studies/{studyId}/clinical-attributes Available clinical variables
GET /studies/{studyId}/clinical-data Clinical data for one study (one attributeId per call)
POST /clinical-data/fetch?clinicalDataType=PATIENT Clinical data across studies (POST-only; GET → 405)
POST /genes/fetch?geneIdType=HUGO_GENE_SYMBOL Resolve symbols → Entrez IDs (body is a plain JSON array, e.g. ["TP53"])
GET /studies/{studyId}/sample-lists Sample lists

Best Practices

  • Know your study IDs: Use the Swagger UI or GET /studies to find the correct study ID
  • Use sample lists: Each study has an all sample list and subsets; always specify the appropriate one
  • TCGA vs. GENIE: TCGA data is comprehensive but older; GENIE has more recent clinical sequencing data, but its consortium releases live on the separate https://genie.cbioportal.org portal (login required), not on the keyless public API
  • Entrez gene IDs: The API uses Entrez IDs — convert from symbols with POST /genes/fetch?geneIdType=HUGO_GENE_SYMBOL. The body must be a plain JSON array (["TP53","KRAS"]); the object form [{"hugoGeneSymbol":...}] returns HTTP 400, and omitting geneIdType silently returns [] for symbols. Response order is not guaranteed — map results back by hugoGeneSymbol.
  • Handle 404s: Some molecular profiles may not exist for all studies
  • Rate limiting: Add delays for bulk queries; consider downloading data files for large-scale analyses

Data Downloads

For large-scale analyses, download study data directly (the older cbioportal-datahub.s3.amazonaws.com links now return 403):

# Download TCGA BRCA (PanCancer Atlas) data
wget https://datahub.assets.cbioportal.org/brca_tcga_pan_can_atlas_2018.tar.gz

Additional Resources

Scripts

scripts/query_cbioportal.py — runnable helper for the cBioPortal REST API (public, no key):

python scripts/query_cbioportal.py studies --filter tcga
python scripts/query_cbioportal.py profiles brca_tcga
python scripts/query_cbioportal.py mutations brca_tcga_mutations --genes 7157,672
Files (alterlab-academic-skills)
  • evals
    • evals.json 5.7 KB
      {
        "skill": "alterlab-cbioportal",
        "evals": [
          {
            "id": "mutation-frequency-tcga",
            "prompt": "What fraction of TCGA breast cancer samples carry a TP53 mutation? I want the percentage and a breakdown of the mutation types (missense, nonsense, frameshift).",
            "expected_output": "Triggers the cBioPortal skill. Resolves TP53 to its Entrez ID (7157) via POST /genes/fetch?geneIdType=HUGO_GENE_SYMBOL, then fetches mutations from the brca_tcga_mutations molecular profile (POST molecular-profiles/{id}/mutations/fetch), computes the mutated-sample fraction over the samples profiled for mutations (the all_cases_with_mutation_data / brca_tcga_sequenced list, not every sample in the study), and tabulates mutationType counts (Missense_Mutation, Nonsense_Mutation, Frame_Shift_Del, etc.). Uses the public REST API at https://www.cbioportal.org/api with no API key.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "cbioportal" },
              { "type": "behavior", "value": "Resolves the gene symbol to an Entrez ID via /genes/fetch and queries a study mutation molecular profile rather than guessing a frequency." },
              { "type": "behavior", "value": "Computes alteration frequency as mutated samples over the samples sequenced for mutations (all_cases_with_mutation_data sample list)." }
            ]
          },
          {
            "id": "copy-number-amplification",
            "prompt": "Is EGFR amplified in glioblastoma? Pull the GISTIC copy-number calls from the TCGA GBM study and tell me how many samples show high-level amplification versus deep deletion.",
            "expected_output": "Triggers cBioPortal. Uses the discrete copy-number endpoint (POST molecular-profiles/{profileId}/discrete-copy-number/fetch) on the gbm_tcga GISTIC profile for EGFR (Entrez 1956), interprets GISTIC values where 2 = high-level amplification and -2 = deep deletion, and counts samples in each category. Recognizes EGFR amplification as a hallmark GBM event.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "GISTIC" },
              { "type": "behavior", "value": "Uses the discrete-copy-number fetch endpoint and correctly maps GISTIC value 2 to amplification and -2 to deep deletion." }
            ]
          },
          {
            "id": "pan-cancer-alteration",
            "prompt": "Show me the mutation frequency of PIK3CA across multiple cancer types in cBioPortal so I can see which tumors are most dependent on it.",
            "expected_output": "Triggers cBioPortal. Iterates across multiple studies (e.g. via GET /studies, filtering the per-cancer TCGA PanCancer Atlas studies ending in _tcga_pan_can_atlas_2018), computes per-study PIK3CA mutation frequency, and returns a ranked pan-cancer table sorted by mutation percentage. Notes that the API uses Entrez IDs and that frequencies are computed over each study's sequenced-sample list.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "behavior", "value": "Loops over multiple cancer studies and returns a ranked pan-cancer mutation-frequency comparison rather than a single study result." }
            ]
          },
          {
            "id": "survival-by-mutation",
            "prompt": "In TCGA lung adenocarcinoma, do KEAP1-mutant patients have worse overall survival than KEAP1 wild-type? Get me the OS_MONTHS and OS_STATUS split by mutation status.",
            "expected_output": "Triggers cBioPortal. Fetches KEAP1 mutations for luad_tcga to define the mutant patient set, pulls patient clinical data for OS_MONTHS and OS_STATUS (per-study GET /studies/{studyId}/clinical-data, or POST /clinical-data/fetch — that endpoint is POST-only), splits OS_STATUS like '1:DECEASED' into an event flag, joins on patientId, and returns survival data split by mutation status ready for Kaplan-Meier analysis. Notes that the skill provides the data extraction and downstream KM/log-rank is performed separately.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "OS_STATUS" },
              { "type": "behavior", "value": "Joins clinical survival attributes to the mutated-patient set by patientId to enable a survival comparison." }
            ]
          },
          {
            "id": "near-miss-clinvar",
            "prompt": "Is the BRCA1 c.5266dupC variant classified as pathogenic for hereditary breast cancer? I need the germline clinical significance and review status for this specific variant.",
            "expected_output": "Should NOT trigger cBioPortal. This asks for germline variant clinical-significance interpretation (pathogenic/benign classification, review status) for a specific reported variant, which is ClinVar's territory, not cancer somatic genomics aggregation. cBioPortal reports somatic mutation frequencies and patient-level alterations across tumor cohorts, not curated germline pathogenicity calls. Defers to alterlab-clinvar.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "clinvar" }
            ]
          },
          {
            "id": "near-miss-cosmic",
            "prompt": "I want the COSMIC mutational signatures (SBS catalogue) contributing to a melanoma sample's mutation spectrum. Which signatures dominate?",
            "expected_output": "Should NOT trigger cBioPortal. Mutational signature decomposition against the COSMIC SBS catalogue is COSMIC's domain. cBioPortal serves per-gene somatic mutations, CNAs, and clinical outcomes across studies, not signature catalogues or SBS contribution analysis. Defers to alterlab-cosmic.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "cosmic" }
            ]
          }
        ]
      }
      
  • references
    • study_exploration.md 5.1 KB
      # cBioPortal Study Exploration Reference
      
      ## Major Study Collections
      
      ### TCGA (The Cancer Genome Atlas)
      
      | Study ID | Cancer Type | Samples |
      |----------|-------------|---------|
      | `brca_tcga` | Breast Cancer | ~1,000 |
      | `luad_tcga` | Lung Adenocarcinoma | ~500 |
      | `lusc_tcga` | Lung Squamous Cell Carcinoma | ~500 |
      | `coadread_tcga` | Colorectal Cancer | ~600 |
      | `gbm_tcga` | Glioblastoma | ~600 |
      | `prad_tcga` | Prostate Cancer | ~500 |
      | `skcm_tcga` | Skin Cutaneous Melanoma | ~450 |
      | `blca_tcga` | Bladder Urothelial Carcinoma | ~400 |
      | `hnsc_tcga` | Head and Neck Squamous | ~500 |
      | `lihc_tcga` | Liver Hepatocellular Carcinoma | ~370 |
      | `stad_tcga` | Stomach Adenocarcinoma | ~440 |
      | `ucec_tcga` | Uterine Endometrial Carcinoma | ~550 |
      | `ov_tcga` | Ovarian Serous Carcinoma | ~580 |
      | `kirc_tcga` | Kidney Renal Clear Cell Carcinoma | ~530 |
      | `thca_tcga` | Thyroid Cancer | ~500 |
      | `paad_tcga` | Pancreatic Adenocarcinoma | ~180 |
      | `laml_tcga` | Acute Myeloid Leukemia | ~200 |
      | `acc_tcga` | Adrenocortical Carcinoma | ~90 |
      
      The IDs above are the original "Firehose Legacy" studies. Most cancers also have a
      harmonized PanCancer Atlas version (`{cancer}_tcga_pan_can_atlas_2018`) and a GDC
      re-processed version (`{cancer}_tcga_gdc`); check `GET /studies` for the exact IDs.
      
      ### TCGA PanCancer Atlas
      
      There is **no single combined pan-cancer study ID** — the PanCancer Atlas is published as
      32 per-cancer studies named `{cancer}_tcga_pan_can_atlas_2018` (e.g.
      `brca_tcga_pan_can_atlas_2018`, `luad_tcga_pan_can_atlas_2018`). For a pan-cancer query,
      loop over them (filter `GET /studies` on the `_tcga_pan_can_atlas_2018` suffix).
      
      ### MSK-IMPACT (Memorial Sloan Kettering)
      
      | Study ID | Description (samples, 2026-09) |
      |----------|-------------|
      | `msk_impact_2017` | MSK-IMPACT Clinical Sequencing Cohort (Nat Med 2017; ~11K) |
      | `msk_met_2021` | MSK MetTropism (Cell 2021; ~26K) |
      | `msk_chord_2024` | MSK-CHORD, genomics + clinical outcomes (Nature 2024; ~25K) |
      | `msk_impact_50k_2026` | MSK-IMPACT 50K Clinical Sequencing Cohort (Cancer Cell 2026; ~54K) |
      
      ### AACR Project GENIE
      
      The consortium releases are served from a separate portal, https://genie.cbioportal.org
      (its API requires a logged-in account); the public portal only carries individual
      GENIE-derived studies. For bulk GENIE data use the Synapse release files.
      
      ## Molecular Profile ID Naming Conventions
      
      Molecular profile IDs are structured as `{studyId}_{type}`:
      
      | Type Suffix | Alteration Type |
      |-------------|----------------|
      | `_mutations` | Somatic mutations (MAF) |
      | `_gistic` | Copy number (GISTIC discrete: -2, -1, 0, 1, 2) |
      | `_linear_CNA` | Copy number (continuous log2 ratio; older studies may use `_log2CNA`) |
      | `_mrna` | mRNA expression (z-scores or log2) |
      | `_rna_seq_v2_mrna` | RNA-seq (RSEM) |
      | `_rna_seq_v2_mrna_median_Zscores` | RNA-seq z-scores relative to normals |
      | `_rppa` | RPPA protein expression |
      | `_rppa_Zscores` | RPPA z-scores |
      | `_sv` | Structural variants/fusions |
      | `_methylation_hm450` | DNA methylation (450K array) |
      
      **Example:** For `brca_tcga`:
      - `brca_tcga_mutations` — mutation data
      - `brca_tcga_gistic` — CNA data
      - `brca_tcga_rna_seq_v2_mrna` — RNA-seq expression
      
      ## Sample List Categories
      
      Each study has sample lists of different subsets:
      
      | Category | sampleListId Pattern | Contents |
      |----------|---------------------|----------|
      | `all_cases_in_study` | `{studyId}_all` | All samples |
      | `all_cases_with_mutation_data` | `{studyId}_sequenced` | Sequenced samples only |
      | `all_cases_with_cna_data` | `{studyId}_cna` | Samples with CNA data |
      | `all_cases_with_mrna_data` | `{studyId}_mrna` | Samples with expression |
      | `all_cases_with_rppa_data` | `{studyId}_rppa` | Samples with RPPA |
      | `all_complete_cases` | `{studyId}_complete` | Complete multiplatform data |
      
      ## Common Gene Entrez IDs
      
      | Gene | Entrez ID | Role |
      |------|-----------|------|
      | TP53 | 7157 | Tumor suppressor |
      | PIK3CA | 5290 | Oncogene |
      | KRAS | 3845 | Oncogene |
      | BRCA1 | 672 | Tumor suppressor |
      | BRCA2 | 675 | Tumor suppressor |
      | PTEN | 5728 | Tumor suppressor |
      | EGFR | 1956 | Oncogene |
      | MYC | 4609 | Oncogene |
      | RB1 | 5925 | Tumor suppressor |
      | APC | 324 | Tumor suppressor |
      | CDKN2A | 1029 | Tumor suppressor |
      | IDH1 | 3417 | Oncogene (mutant) |
      | BRAF | 673 | Oncogene |
      | CDH1 | 999 | Tumor suppressor |
      | VHL | 7428 | Tumor suppressor |
      
      ## Mutation Type Classifications
      
      | mutationType | Description |
      |-------------|-------------|
      | `Missense_Mutation` | Amino acid change |
      | `Nonsense_Mutation` | Premature stop codon |
      | `Frame_Shift_Del` | Frameshift deletion |
      | `Frame_Shift_Ins` | Frameshift insertion |
      | `Splice_Site` | Splice site mutation |
      | `In_Frame_Del` | In-frame deletion |
      | `In_Frame_Ins` | In-frame insertion |
      | `Translation_Start_Site` | Start codon mutation |
      | `Nonstop_Mutation` | Stop codon mutation |
      | `Silent` | Synonymous |
      | `5'Flank` | 5' flanking |
      | `3'UTR` | 3' UTR |
      
      ## OncoPrint Color Legend
      
      cBioPortal uses consistent colors in OncoPrint:
      - **Red**: Amplification
      - **Blue (dark)**: Deep deletion
      - **Green**: Missense mutation
      - **Black**: Truncating mutation
      - **Purple**: Fusion
      - **Orange**: mRNA upregulation
      - **Teal**: mRNA downregulation
      
  • scripts
    • query_cbioportal.py 2.7 KB
      #!/usr/bin/env python3
      """Query the cBioPortal public REST API (no API key required for public data).
      
      Base: https://www.cbioportal.org/api  (RESTful JSON)
        GET  studies
        GET  studies/{studyId}/molecular-profiles
        POST molecular-profiles/{profileId}/mutations/fetch
      
      Smoke test:
          uv run python query_cbioportal.py studies --filter tcga
          uv run python query_cbioportal.py profiles brca_tcga
          uv run python query_cbioportal.py mutations brca_tcga_mutations --genes 7157,672
      """
      import argparse
      import json
      
      import requests
      
      BASE = "https://www.cbioportal.org/api"
      HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
      
      
      def get_studies(name_filter: str | None = None) -> list:
          """List public cancer studies (optionally filter by substring in studyId)."""
          r = requests.get(f"{BASE}/studies", params={"pageSize": 100000},
                           headers=HEADERS, timeout=60)
          r.raise_for_status()
          studies = r.json()
          if name_filter:
              studies = [s for s in studies if name_filter.lower() in s["studyId"].lower()]
          return studies
      
      
      def get_profiles(study_id: str) -> list:
          """List molecular profiles for a study."""
          r = requests.get(f"{BASE}/studies/{study_id}/molecular-profiles",
                           headers=HEADERS, timeout=60)
          r.raise_for_status()
          return r.json()
      
      
      def get_mutations(profile_id: str, entrez_ids: list[int],
                        sample_list_id: str | None = None) -> list:
          """Fetch mutations for given Entrez gene IDs in a molecular profile."""
          body = {
              "entrezGeneIds": entrez_ids,
              "sampleListId": sample_list_id or profile_id.replace("_mutations", "_all"),
          }
          r = requests.post(f"{BASE}/molecular-profiles/{profile_id}/mutations/fetch",
                            json=body, headers=HEADERS, timeout=120)
          r.raise_for_status()
          return r.json()
      
      
      def main() -> None:
          p = argparse.ArgumentParser(description="Query cBioPortal (public, no key).")
          sub = p.add_subparsers(dest="cmd", required=True)
      
          ps = sub.add_parser("studies")
          ps.add_argument("--filter", dest="name_filter", default=None)
      
          pp = sub.add_parser("profiles")
          pp.add_argument("study_id")
      
          pm = sub.add_parser("mutations")
          pm.add_argument("profile_id")
          pm.add_argument("--genes", required=True, help="comma-separated Entrez IDs, e.g. 7157,672")
          pm.add_argument("--sample-list", default=None)
      
          args = p.parse_args()
          if args.cmd == "studies":
              out = get_studies(args.name_filter)
          elif args.cmd == "profiles":
              out = get_profiles(args.study_id)
          else:
              ids = [int(x) for x in args.genes.split(",") if x.strip()]
              out = get_mutations(args.profile_id, ids, args.sample_list)
          print(json.dumps(out, indent=2))
      
      
      if __name__ == "__main__":
          main()
      
  • SKILL.md 17.4 KB
    ---
    name: alterlab-cbioportal
    description: Query cBioPortal via its keyless REST API for cancer genomics across TCGA, GENIE, MSK-IMPACT and hundreds of studies — somatic mutations, copy-number alterations (GISTIC), mRNA/protein expression, structural variants, and patient-level clinical/survival data. Use when asked how often a gene is mutated/amplified/deleted in a tumor type, to profile oncogenes or tumor suppressors across cancers (pan-cancer alteration frequency), to pull patient-level mutations joined to OS/clinical outcomes, or to validate a cancer target from cohort genomics. For germline variant pathogenicity use alterlab-clinvar; for mutational-signature (SBS) decomposition use alterlab-cosmic; for CRISPR/RNAi gene-dependency use alterlab-depmap; for aggregated target-disease evidence use alterlab-opentargets. Part of the AlterLab Academic Skills suite.
    license: LGPL-3.0
    allowed-tools: Read WebFetch Bash(curl:*) Bash(python:*)
    compatibility: Keyless cBioPortal REST API for public data (no authentication required)
    metadata:
        skill-author: AlterLab
        version: "1.1.0"
        last_updated: "2026-09-23"
    ---
    
    # cBioPortal Database
    
    ## Overview
    
    cBioPortal for Cancer Genomics (https://www.cbioportal.org/) is an open-access resource for exploring, visualizing, and analyzing multidimensional cancer genomics data. It hosts data from The Cancer Genome Atlas (TCGA), AACR Project GENIE, MSK-IMPACT, and hundreds of other cancer studies — covering mutations, copy number alterations (CNA), structural variants, mRNA/protein expression, methylation, and clinical data for thousands of cancer samples.
    
    **Key resources:**
    - cBioPortal website: https://www.cbioportal.org/
    - REST API: https://www.cbioportal.org/api/swagger-ui/index.html
    - API docs (Swagger): https://www.cbioportal.org/api/swagger-ui/index.html
    - Python client: `bravado` or `requests`
    - GitHub: https://github.com/cBioPortal/cbioportal
    
    ## When to Use This Skill
    
    Use cBioPortal when:
    
    - **Mutation landscape**: What fraction of a cancer type has mutations in a specific gene?
    - **Oncogene/TSG validation**: Is a gene frequently mutated, amplified, or deleted in cancer?
    - **Co-mutation patterns**: Are mutations in gene A and gene B mutually exclusive or co-occurring?
    - **Survival analysis**: Do mutations in a gene associate with better or worse patient outcomes?
    - **Alteration profiles**: What types of alterations (missense, truncating, amplification, deletion) affect a gene?
    - **Pan-cancer analysis**: Compare alteration frequencies across cancer types
    - **Clinical associations**: Link genomic alterations to clinical variables (stage, grade, treatment response)
    - **TCGA/GENIE exploration**: Systematic access to TCGA and clinical sequencing datasets
    
    ### Does NOT Trigger
    
    | Scenario | Use Instead |
    |----------|-------------|
    | Germline variant pathogenicity / clinical significance | `alterlab-clinvar` |
    | COSMIC Cancer Gene Census, mutational signatures (SBS), curated somatic catalog | `alterlab-cosmic` |
    | CRISPR/RNAi gene-dependency (essentiality) in cancer cell lines | `alterlab-depmap` |
    | Aggregated target–disease association evidence and tractability | `alterlab-opentargets` |
    
    ## Core Capabilities
    
    ### 1. cBioPortal REST API
    
    Base URL: `https://www.cbioportal.org/api`
    
    The API is RESTful, returns JSON, and requires no API key for public data.
    
    ```python
    import requests
    
    BASE_URL = "https://www.cbioportal.org/api"
    HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
    
    def cbioportal_get(endpoint, params=None):
        url = f"{BASE_URL}/{endpoint}"
        response = requests.get(url, params=params, headers=HEADERS)
        response.raise_for_status()
        return response.json()
    
    def cbioportal_post(endpoint, body):
        url = f"{BASE_URL}/{endpoint}"
        response = requests.post(url, json=body, headers=HEADERS)
        response.raise_for_status()
        return response.json()
    ```
    
    ### 2. Browse Studies
    
    ```python
    def get_all_studies():
        """List all available cancer studies.
    
        The public portal hosts 540+ studies (2026-09), so a pageSize of 500 would
        silently truncate the list; ask for more than you expect.
        """
        return cbioportal_get("studies", {"pageSize": 100000})
    
    # Each study has:
    # studyId: unique identifier (e.g., "brca_tcga")
    # name: human-readable name
    # description: dataset description
    # cancerTypeId: cancer type abbreviation
    # referenceGenome: hg19 or hg38
    # allSampleCount: samples in the study
    # (pass projection=DETAILED to also get pmid, citation, sequencedSampleCount, ...)
    
    studies = get_all_studies()
    print(f"Total studies: {len(studies)}")
    
    # Common TCGA study IDs — each cancer has several versions:
    #   *_tcga                     original TCGA Firehose Legacy (e.g. brca_tcga)
    #   *_tcga_pan_can_atlas_2018  harmonized PanCancer Atlas (preferred for pan-cancer work)
    #   *_tcga_gdc                 GDC re-processed data
    # e.g. brca_tcga, luad_tcga, coadread_tcga, gbm_tcga, prad_tcga, skcm_tcga
    
    # Filter for TCGA studies
    tcga_studies = [s for s in studies if "tcga" in s["studyId"]]
    print([s["studyId"] for s in tcga_studies[:10]])
    ```
    
    ### 3. Molecular Profiles
    
    Each study has multiple molecular profiles (mutation, CNA, expression, etc.):
    
    ```python
    def get_molecular_profiles(study_id):
        """Get all molecular profiles for a study."""
        return cbioportal_get(f"studies/{study_id}/molecular-profiles")
    
    profiles = get_molecular_profiles("brca_tcga")
    for p in profiles:
        print(f"  {p['molecularProfileId']}: {p['name']} ({p['molecularAlterationType']})")
    
    # Alteration types:
    # MUTATION_EXTENDED — somatic mutations
    # COPY_NUMBER_ALTERATION — CNA (GISTIC)
    # MRNA_EXPRESSION — mRNA expression
    # PROTEIN_LEVEL — RPPA protein expression
    # STRUCTURAL_VARIANT — fusions/rearrangements
    ```
    
    ### 4. Mutation Data
    
    ```python
    def get_mutations(molecular_profile_id, entrez_gene_ids, sample_list_id=None):
        """Get mutations for specified genes in a molecular profile."""
        body = {
            "entrezGeneIds": entrez_gene_ids,
            "sampleListId": sample_list_id or molecular_profile_id.replace("_mutations", "_all")
        }
        return cbioportal_post(
            f"molecular-profiles/{molecular_profile_id}/mutations/fetch",
            body
        )
    
    # BRCA1 Entrez ID is 672, TP53 is 7157, PTEN is 5728
    mutations = get_mutations("brca_tcga_mutations", entrez_gene_ids=[7157])  # TP53
    
    # Each mutation record contains:
    # patientId, sampleId, entrezGeneId (the nested gene.hugoGeneSymbol only
    #   appears with ?projection=DETAILED on the fetch URL)
    # mutationType (Missense_Mutation, Nonsense_Mutation, Frame_Shift_Del, etc.)
    # proteinChange (e.g., "R175H"), variantType
    # ncbiBuild, chr, startPosition, endPosition, referenceAllele, variantAllele
    # mutationStatus (Somatic/Germline)
    # tumorAltCount, tumorRefCount (read counts — there is no VAF field; derive it)
    
    import pandas as pd
    df = pd.DataFrame(mutations)
    df["vaf"] = df["tumorAltCount"] / (df["tumorAltCount"] + df["tumorRefCount"])
    print(df[["patientId", "mutationType", "proteinChange", "vaf"]].head())
    print(f"\nMutation types:\n{df['mutationType'].value_counts()}")
    ```
    
    ### 5. Copy Number Alteration Data
    
    ```python
    def get_cna(molecular_profile_id, entrez_gene_ids):
        """Get discrete CNA data (GISTIC: -2, -1, 0, 1, 2)."""
        body = {
            "entrezGeneIds": entrez_gene_ids,
            "sampleListId": molecular_profile_id.replace("_gistic", "_all").replace("_cna", "_all")
        }
        return cbioportal_post(
            f"molecular-profiles/{molecular_profile_id}/discrete-copy-number/fetch",
            body
        )
    
    # GISTIC values:
    # -2 = Deep deletion (homozygous loss)
    # -1 = Shallow deletion (heterozygous loss)
    #  0 = Diploid (neutral)
    #  1 = Low-level gain
    #  2 = High-level amplification
    
    cna_data = get_cna("brca_tcga_gistic", entrez_gene_ids=[1956])  # EGFR
    df_cna = pd.DataFrame(cna_data)
    print(df_cna["value"].value_counts())
    ```
    
    ### 6. Alteration Frequency (OncoPrint-style)
    
    ```python
    def get_alteration_frequency(study_id, gene_symbols, alteration_types=None):
        """Compute alteration frequencies for genes across a cancer study."""
        import requests, pandas as pd
    
        # Denominator = samples profiled for mutations (cBioPortal's own convention).
        # 'all_cases_in_study' also counts unsequenced samples and deflates the
        # frequency (brca_tcga: 1,108 samples in study vs 982 sequenced).
        samples = requests.get(
            f"{BASE_URL}/studies/{study_id}/sample-lists",
            headers=HEADERS
        ).json()
        by_category = {s["category"]: s["sampleListId"] for s in samples}
        sample_list_id = (by_category.get("all_cases_with_mutation_data")
                          or by_category.get("all_cases_in_study"))
        total_samples = len(requests.get(
            f"{BASE_URL}/sample-lists/{sample_list_id}/sample-ids",
            headers=HEADERS
        ).json())
    
        # Get gene Entrez IDs. /genes/fetch takes a plain JSON array of identifiers
        # plus a geneIdType query param; symbols WITHOUT the param resolve to [].
        gene_data = requests.post(
            f"{BASE_URL}/genes/fetch",
            params={"geneIdType": "HUGO_GENE_SYMBOL"},
            json=gene_symbols,
            headers=HEADERS
        ).json()
        # Response order is not guaranteed; map by symbol.
        entrez_by_symbol = {g["hugoGeneSymbol"]: g["entrezGeneId"] for g in gene_data}
        entrez_ids = [entrez_by_symbol[g] for g in gene_symbols if g in entrez_by_symbol]
    
        # Get mutations
        mutation_profile = f"{study_id}_mutations"
        mutations = get_mutations(mutation_profile, entrez_ids, sample_list_id)
    
        freq = {}
        for g_symbol, e_id in entrez_by_symbol.items():
            # Count samples (not patients) so numerator and denominator match.
            mutated = len(set(m["sampleId"] for m in mutations if m["entrezGeneId"] == e_id))
            freq[g_symbol] = mutated / total_samples * 100
    
        return freq
    
    # Example
    freq = get_alteration_frequency("brca_tcga", ["TP53", "PIK3CA", "BRCA1", "BRCA2"])
    for gene, pct in sorted(freq.items(), key=lambda x: -x[1]):
        print(f"  {gene}: {pct:.1f}%")
    ```
    
    ### 7. Clinical Data
    
    The global `/clinical-data/fetch` endpoint is **POST-only** (a GET returns HTTP 405).
    The simplest path for one study is the per-study GET endpoint, which returns a list
    of `{patientId, studyId, clinicalAttributeId, value}` records:
    
    ```python
    def get_patient_clinical_data(study_id, attribute_ids):
        """Patient-level clinical data via the per-study GET endpoint.
    
        GET /studies/{studyId}/clinical-data?clinicalDataType=PATIENT&attributeId=...
        accepts a single attributeId, so we query each and concatenate.
        """
        records = []
        for attr in attribute_ids:
            records += cbioportal_get(
                f"studies/{study_id}/clinical-data",
                {"clinicalDataType": "PATIENT", "attributeId": attr, "pageSize": 100000},
            )
        return records
    
    # Clinical attributes include:
    # OS_STATUS, OS_MONTHS, DFS_STATUS, DFS_MONTHS (survival)
    # AJCC_PATHOLOGIC_TUMOR_STAGE, GRADE, AGE, SEX, RACE
    # Study-specific attributes vary — list them with get_clinical_attributes().
    # GOTCHA: OS_STATUS / DFS_STATUS are encoded "1:DECEASED" / "0:LIVING"
    # (event:label), not bare 0/1 — split on ":" before survival analysis.
    
    def get_clinical_attributes(study_id):
        """List all available clinical attributes for a study."""
        return cbioportal_get(f"studies/{study_id}/clinical-attributes")
    ```
    
    ## Query Workflows
    
    ### Workflow 1: Gene Alteration Profile in a Cancer Type
    
    ```python
    import requests, pandas as pd
    
    def alteration_profile(study_id, gene_symbol):
        """Full alteration profile for a gene in a cancer study."""
    
        # 1. Get gene Entrez ID (plain array body + geneIdType param)
        gene_info = requests.post(
            f"{BASE_URL}/genes/fetch",
            params={"geneIdType": "HUGO_GENE_SYMBOL"},
            json=[gene_symbol],
            headers=HEADERS
        ).json()[0]
        entrez_id = gene_info["entrezGeneId"]
    
        # 2. Get mutations
        mutations = get_mutations(f"{study_id}_mutations", [entrez_id])
        mut_df = pd.DataFrame(mutations) if mutations else pd.DataFrame()
    
        # 3. Get CNAs
        cna = get_cna(f"{study_id}_gistic", [entrez_id])
        cna_df = pd.DataFrame(cna) if cna else pd.DataFrame()
    
        # 4. Summary
        n_mut = len(set(mut_df["patientId"])) if not mut_df.empty else 0
        n_amp = len(cna_df[cna_df["value"] == 2]) if not cna_df.empty else 0
        n_del = len(cna_df[cna_df["value"] == -2]) if not cna_df.empty else 0
    
        return {"mutations": n_mut, "amplifications": n_amp, "deep_deletions": n_del}
    
    result = alteration_profile("brca_tcga", "PIK3CA")
    print(result)
    ```
    
    ### Workflow 2: Pan-Cancer Gene Mutation Frequency
    
    ```python
    import requests, pandas as pd
    
    def pan_cancer_mutation_freq(gene_symbol, cancer_study_ids=None):
        """Mutation frequency of a gene across multiple cancer types."""
        studies = get_all_studies()
        if cancer_study_ids:
            studies = [s for s in studies if s["studyId"] in cancer_study_ids]
    
        results = []
        for study in studies[:20]:  # Limit for demo
            try:
                freq = get_alteration_frequency(study["studyId"], [gene_symbol])
                results.append({
                    "study": study["studyId"],
                    "cancer": study.get("cancerTypeId", ""),
                    "mutation_pct": freq.get(gene_symbol, 0)
                })
            except Exception:
                pass
    
        df = pd.DataFrame(results).sort_values("mutation_pct", ascending=False)
        return df
    ```
    
    ### Workflow 3: Survival Analysis by Mutation Status
    
    ```python
    import requests, pandas as pd
    
    def survival_by_mutation(study_id, gene_symbol):
        """Get survival data split by mutation status."""
        # This workflow fetches clinical and mutation data for downstream analysis
    
        gene_info = requests.post(
            f"{BASE_URL}/genes/fetch",
            params={"geneIdType": "HUGO_GENE_SYMBOL"},
            json=[gene_symbol],
            headers=HEADERS
        ).json()[0]
        entrez_id = gene_info["entrezGeneId"]
    
        mutations = get_mutations(f"{study_id}_mutations", [entrez_id])
        mutated_patients = set(m["patientId"] for m in mutations)
    
        # Patient-level survival via the per-study GET endpoint (clinical-data/fetch
        # is POST-only — a GET there returns HTTP 405).
        clinical = get_patient_clinical_data(study_id, ["OS_MONTHS", "OS_STATUS"])
        clinical_df = pd.DataFrame(clinical)
    
        os_wide = clinical_df.pivot(index="patientId", columns="clinicalAttributeId", values="value")
        # OS_STATUS is encoded as "1:DECEASED" / "0:LIVING"; split off the 0/1 event flag.
        if "OS_STATUS" in os_wide:
            os_wide["OS_EVENT"] = os_wide["OS_STATUS"].str.startswith("1").astype("Int64")
        os_wide["OS_MONTHS"] = pd.to_numeric(os_wide.get("OS_MONTHS"), errors="coerce")
        os_wide["mutated"] = os_wide.index.isin(mutated_patients)
    
        return os_wide
    ```
    
    ## Key API Endpoints Summary
    
    | Endpoint | Description |
    |----------|-------------|
    | `GET /studies` | List all studies |
    | `GET /studies/{studyId}/molecular-profiles` | Molecular profiles for a study |
    | `POST /molecular-profiles/{profileId}/mutations/fetch` | Get mutation data |
    | `POST /molecular-profiles/{profileId}/discrete-copy-number/fetch` | Get CNA data |
    | `POST /molecular-profiles/{profileId}/molecular-data/fetch` | Get expression data |
    | `GET /studies/{studyId}/clinical-attributes` | Available clinical variables |
    | `GET /studies/{studyId}/clinical-data` | Clinical data for one study (one `attributeId` per call) |
    | `POST /clinical-data/fetch?clinicalDataType=PATIENT` | Clinical data across studies (POST-only; GET → 405) |
    | `POST /genes/fetch?geneIdType=HUGO_GENE_SYMBOL` | Resolve symbols → Entrez IDs (body is a plain JSON array, e.g. `["TP53"]`) |
    | `GET /studies/{studyId}/sample-lists` | Sample lists |
    
    ## Best Practices
    
    - **Know your study IDs**: Use the Swagger UI or `GET /studies` to find the correct study ID
    - **Use sample lists**: Each study has an `all` sample list and subsets; always specify the appropriate one
    - **TCGA vs. GENIE**: TCGA data is comprehensive but older; GENIE has more recent clinical sequencing data, but its consortium releases live on the separate https://genie.cbioportal.org portal (login required), not on the keyless public API
    - **Entrez gene IDs**: The API uses Entrez IDs — convert from symbols with `POST /genes/fetch?geneIdType=HUGO_GENE_SYMBOL`. The body must be a **plain JSON array** (`["TP53","KRAS"]`); the object form `[{"hugoGeneSymbol":...}]` returns HTTP 400, and omitting `geneIdType` silently returns `[]` for symbols. Response order is not guaranteed — map results back by `hugoGeneSymbol`.
    - **Handle 404s**: Some molecular profiles may not exist for all studies
    - **Rate limiting**: Add delays for bulk queries; consider downloading data files for large-scale analyses
    
    ## Data Downloads
    
    For large-scale analyses, download study data directly (the older
    `cbioportal-datahub.s3.amazonaws.com` links now return 403):
    ```bash
    # Download TCGA BRCA (PanCancer Atlas) data
    wget https://datahub.assets.cbioportal.org/brca_tcga_pan_can_atlas_2018.tar.gz
    ```
    
    ## Additional Resources
    
    - **cBioPortal website**: https://www.cbioportal.org/
    - **API Swagger UI**: https://www.cbioportal.org/api/swagger-ui/index.html
    - **Documentation**: https://docs.cbioportal.org/
    - **GitHub**: https://github.com/cBioPortal/cbioportal
    - **Data hub**: https://www.cbioportal.org/datasets
    - **Citation**: Cerami E et al. (2012) Cancer Discovery. PMID: 22588877
    - **API clients**: https://docs.cbioportal.org/web-api-and-clients/
    
    ## Scripts
    
    `scripts/query_cbioportal.py` — runnable helper for the cBioPortal REST API (public, no key):
    
    ```bash
    python scripts/query_cbioportal.py studies --filter tcga
    python scripts/query_cbioportal.py profiles brca_tcga
    python scripts/query_cbioportal.py mutations brca_tcga_mutations --genes 7157,672
    ```
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related