mcpField
A
alterlab-gtex

alterlab-gtex

github.com/alterlab-ieuData & databases7866 installsUpdated 5w ago
Repository

About alterlab-gtex

alterlab-gtex is a research skill for querying GTEx tissue-expression and genetic-regulation data. It helps researchers examine gene expression, eQTLs, and sQTLs through the GTEx REST API and data downloads.

Key features

  1. 01Query median gene expression by tissue
  2. 02Look up gene- and variant-level eQTLs
  3. 03Retrieve sQTL associations and eGenes
  4. 04List tissues and resolve gene identifiers
  5. 05Use GTEx v10 and v8 datasets

Use cases

  1. 01Identify genes regulated by a GWAS variant
  2. 02Compare a gene’s expression across tissues
  3. 03Find tissue-specific eQTL or sQTL associations

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/alterlab-ieu/alterlab-academic-skills --skill alterlab-gtex

After installing, type/alterlab-gtexin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

evals/
evals.json
references/
api_reference.md
scripts/
query_gtex.py

Details

From the project README

GTEx Database

Overview

The Genotype-Tissue Expression (GTEx) project provides a comprehensive resource for studying tissue-specific gene expression and genetic regulation across 54 non-diseased human tissues from nearly 1,000 individuals. GTEx v10 (the latest release) enables researchers to understand how genetic variants regulate gene expression (eQTLs) and splicing (sQTLs) in a tissue-specific manner, which is critical for interpreting GWAS loci and identifying regulatory mechanisms.

Key resources:

When to Use This Skill

Use GTEx when:

  • GWAS locus interpretation: Identifying which gene a non-coding GWAS variant regulates via eQTLs
  • Tissue-specific expression: Comparing gene expression levels across 54 human tissues
  • eQTL colocalization: Testing if a GWAS signal and an eQTL signal share the same causal variant
  • Multi-tissue eQTL analysis: Finding variants that regulate expression in multiple tissues
  • Splicing QTLs (sQTLs): Identifying variants that affect splicing ratios
  • Tissue specificity analysis: Determining which tissues express a gene of interest
  • Gene expression exploration: Retrieving normalized expression levels (TPM) per tissue
Does NOT Trigger
ScenarioUse Instead
Curated trait-variant associations, reported traits, study provenancealterlab-gwas
Population allele frequencies, LoF constraint (pLI/LOEUF)alterlab-gnomad
Gene/transcript models, VEP consequences, ID mappingalterlab-ensembl
Genetics-backed target-disease evidence scores (incl. colocalisation/L2G)alterlab-opentargets
Single-cell expression atlases (cell-type resolution)alterlab-cellxgene

Core Capabilities

1. GTEx REST API v2

Base URL: https://gtexportal.org/api/v2/

The API returns JSON and does not require authentication. All endpoints support pagination.

import requests

BASE_URL = "https://gtexportal.org/api/v2"

def gtex_get(endpoint, params=None):
    """Make a GET request to the GTEx API."""
    url = f"{BASE_URL}/{endpoint}"
    response = requests.get(url, params=params, headers={"Accept": "application/json"})
    response.raise_for_status()
    return response.json()
2. Gene Expression by Tissue
import requests
import pandas as pd

def get_gene_expression_by_tissue(gencode_id, dataset_id="gtex_v10"):
    """Get median gene expression across all tissues (versioned GENCODE ID required)."""
    url = "https://gtexportal.org/api/v2/expression/medianGeneExpression"
    params = {
        "gencodeId": gencode_id,
        "datasetId": dataset_id,
        "itemsPerPage": 100
    }
    response = requests.get(url, params=params)
    data = response.json()

    records = data.get("data", [])
    df = pd.DataFrame(records)
    if not df.empty:
        # v2 returns tissueSiteDetailId (not a display-name column), median, unit, geneSymbol
        df = df[["geneSymbol", "tissueSiteDetailId", "median", "unit"]].sort_values(
            "median", ascending=False
        )
    return df

# Example: get expression of APOE across tissues (v10 = GENCODE v39; APOE keeps .10 here)
df = get_gene_expression_by_tissue("ENSG00000130203.10")  # APOE GENCODE ID
print(df.head(10))
# Output: tissueSiteDetailId, median TPM, sorted by highest expression
3. eQTL Lookup
import requests
import pandas as pd

def query_eqtl(gene_id, tissue_id=None, dataset_id="gtex_v10"):
    """Query significant eQTLs for a gene, optionally filtered by tissue."""
    url = "https://gtexportal.org/api/v2/association/singleTissueEqtl"
    params = {
        "gencodeId": gene_id,
        "datasetId": dataset_id,
        "itemsPerPage": 250
    }
    if tissue_id:
        params["tissueSiteDetailId"] = tissue_id

    all_results = []
    page = 0
    while True:
        params["page"] = page
        response = requests.get(url, params=params)
        data = response.json()
        results = data.get("data", [])
        if not results:
            break
        all_results.extend(results)
        if len(results) < params["itemsPerPage"]:
            break
        page += 1

    df = pd.DataFrame(all_results)
    if not df.empty:
        df = df.sort_values("pValue", ascending=True)
    return df

# Example: Find eQTLs for PCSK9 (v10 GENCODE ID — version suffix must match the dataset)
df = query_eqtl("ENSG00000169174.11")
# v2 single-tissue eQTL fields: snpId, variantId, tissueSiteDetailId, nes, pValue, gencodeId
# (nes = normalized effect size of the alt allele; there is no separate slope/qval/maf here)
print(df[["snpId", "tissueSiteDetailId", "nes", "pValue", "gencodeId"]].head(20))
4. Single-Tissue eQTL by Variant
import requests

def query_variant_eqtl(variant_id, tissue_id=None, dataset_id="gtex_v10"):
    """Get all eQTL associations for a specific variant."""
    url = "https://gtexportal.org/api/v2/association/singleTissueEqtl"
    params = {
        "variantId": variant_id,  # e.g., "chr1_55516888_G_GA_b38"
        "datasetId": dataset_id,
        "itemsPerPage": 250
    }
    if tissue_id:
        params["tissueSiteDetailId"] = tissue_id

    response = requests.get(url, params=params)
    return response.json()

# GTEx variant ID format: chr{chrom}_{pos}_{ref}_{alt}_b38
# Example: "chr17_43094692_G_A_b38"
5. Multi-Tissue eQTL (eGenes)
import requests

def get_egenes(tissue_id, dataset_id="gtex_v10"):
    """Get all eGenes (genes with at least one significant eQTL) in a tissue."""
    url = "https://gtexportal.org/api/v2/association/egene"
    params = {
        "tissueSiteDetailId": tissue_id,
        "datasetId": dataset_id,
        "itemsPerPage": 500
    }

    all_egenes = []
    page = 0
    while True:
        params["page"] = page
        response = requests.get(url, params=params)
        data = response.json()
        batch = data.get("data", [])
        if not batch:
            break
        all_egenes.extend(batch)
        if len(batch) < params["itemsPerPage"]:
            break
        page += 1
    return all_egenes

# Example: all eGenes in whole blood
egenes = get_egenes("Whole_Blood")
print(f"Found {len(egenes)} eGenes in Whole Blood")
6. Tissue List
import requests

def get_tissues(dataset_id="gtex_v10"):
    """Get all available tissues with metadata."""
    url = "https://gtexportal.org/api/v2/dataset/tissueSiteDetail"
    params = {"datasetId": dataset_id, "itemsPerPage": 100}
    response = requests.get(url, params=params)
    return response.json()["data"]

tissues = get_tissues()
# Key fields: tissueSiteDetailId, tissueSiteDetail, colorHex, samplingSite
# Common tissue IDs:
# Whole_Blood, Brain_Cortex, Liver, Kidney_Cortex, Heart_Left_Ventricle,
# Lung, Muscle_Skeletal, Adipose_Subcutaneous, Colon_Transverse, ...
7. sQTL (Splicing QTLs)
import requests

def query_sqtl(gene_id, tissue_id=None, dataset_id="gtex_v10"):
    """Query significant sQTLs for a gene."""
    url = "https://gtexportal.org/api/v2/association/singleTissueSqtl"
    params = {
        "gencodeId": gene_id,
        "datasetId": dataset_id,
        "itemsPerPage": 250
    }
    if tissue_id:
        params["tissueSiteDetailId"] = tissue_id

    response = requests.get(url, params=params)
    return response.json()

Query Workflows

Workflow 1: Interpreting a GWAS Variant via eQTLs
  1. Identify the GWAS variant (rs ID or chromosome position)
  2. Convert to GTEx variant ID format (chr{chrom}_{pos}_{ref}_{alt}_b38)
  3. Query all eQTL associations for that variant across tissues
  4. Check effect direction: is the GWAS risk allele the same as the eQTL effect allele?
  5. Prioritize tissues: select tissues biologically relevant to the disease
  6. Consider colocalization using coloc (R package) with full summary statistics
import requests, pandas as pd

def interpret_gwas_variant(variant_id, dataset_id="gtex_v10"):
    """Find all genes regulated by a GWAS variant."""
    url = "https://gtexportal.org/api/v2/association/singleTissueEqtl"
    params = {"variantId": variant_id, "datasetId": dataset_id, "itemsPerPage": 500}
    response = requests.get(url, params=params)
    data = response.json()

    df = pd.DataFrame(data.get("data", []))
    if df.empty:
        return df
    return df[["geneSymbol", "tissueSiteDetailId", "nes", "pValue"]].sort_values("pValue")

# Example: rs2228145 (IL6R p.Asp358Ala). Resolve an rsID to the GTEx ID with
# GET /dataset/variant?snpId=rs2228145&datasetId=gtex_v10 -> "chr1_154454494_A_C_b38"
results = interpret_gwas_variant("chr1_154454494_A_C_b38")
if not results.empty:
    print(results.groupby("geneSymbol")["tissueSiteDetailId"].count().sort_values(ascending=False))
Workflow 2: Gene Expression Atlas
  1. Get median expression for a gene across all tissues
  2. Identify the primary expression site(s)
  3. Compare with disease-relevant tissues
  4. Download raw data for statistical comparisons
Workflow 3: Tissue-Specific eQTL Analysis
  1. Select tissues relevant to your disease
  2. Query all eGenes in that tissue
  3. Cross-reference with GWAS-significant loci
  4. Identify co-localized signals

Key API Endpoints

EndpointDescription
/expression/medianGeneExpressionMedian TPM by tissue for a gene
/expression/geneExpressionFull distribution of expression per tissue
/association/singleTissueEqtlSignificant eQTL associations
/association/singleTissueSqtlSignificant sQTL associations
/association/egeneeGenes in a tissue
/dataset/tissueSiteDetailAvailable tissues with metadata
/reference/geneGene metadata; resolves a symbol to the dataset's versioned GENCODE ID
/dataset/variantVariant lookup by snpId (rsID) or variantId; returns the b38 GTEx ID

Datasets Available

IDDescription
gtex_v10GTEx v10 (current; 946 donors, 54 tissues, eQTLs mapped in 50; GENCODE v39)
gtex_v8GTEx v8 (948 donors, 838 genotyped; eQTLs in 49 of 54 tissues; GENCODE v26) — older but widely cited

Best Practices

  • GENCODE version suffix must match the dataset (biggest gotcha): v10 maps to GENCODE v39, v8 maps to GENCODE v26, and the same gene gets a different .version in each. PCSK9 is ENSG00000169174.11 in v10 but .10 in v8 — querying the wrong suffix silently returns zero results. Resolve the correct ID per dataset with /reference/gene?geneId=SYMBOL&gencodeVersion=v39 (use v26 for v8). The expression and QTL endpoints only accept versioned gencodeId values — a bare symbol such as APOE also returns zero rows.
  • Always pass datasetId: some endpoints (/association/singleTissueSqtl, /dataset/tissueSiteDetail, /dataset/variant) still default to gtex_v8 when it is omitted, so you silently get v8 data — and a v10 GENCODE ID returns nothing.
  • GTEx variant IDs use the format chr{chrom}_{pos}_{ref}_{alt}_b38 (GRCh38) — different from rs IDs
  • Handle pagination: Large queries (e.g., all eGenes) require iterating through pages
  • Tissue nomenclature: Use tissueSiteDetailId (e.g., Whole_Blood) not display names for API calls
  • FDR threshold: GTEx calls eGenes/eQTLs at FDR < 0.05. The single-tissue eQTL/sQTL responses are already filtered to significant pairs and return pValue + nes (no per-row qval); the per-gene qValue lives on the /association/egene endpoint.
  • Effect size: the QTL response field is nes (normalized effect size of the alternative allele), not slope; positive nes = higher expression with the alt allele.

Data Downloads (for large-scale analysis)

For genome-wide analyses, download full summary statistics rather than using the API:

# All significant eQTLs (v10)
wget https://storage.googleapis.com/adult-gtex/bulk-qtl/v10/single-tissue-cis-qtl/GTEx_Analysis_v10_eQTL.tar

# Gene read counts (use ..._gene_tpm.gct.gz for TPM, ..._gene_median_tpm.gct.gz for per-tissue medians)
wget https://storage.googleapis.com/adult-gtex/bulk-gex/v10/rna-seq/GTEx_Analysis_v10_RNASeQCv2.4.2_gene_reads.gct.gz

Additional Resources

Scripts

scripts/query_gtex.py — runnable helper for the GTEx Portal API v2 (no key):

python scripts/query_gtex.py gene APOE                     # symbol -> versioned GENCODE ID for gtex_v10
python scripts/query_gtex.py expression ENSG00000130203.10
python scripts/query_gtex.py eqtl ENSG00000169174.11 --tissue Liver   # v10 GENCODE ID
python scripts/query_gtex.py tissues

Comments

Markdown supported