Skip to content
Back to skills

Alterlab Gtex

ASecurity

Query the GTEx (Genotype-Tissue Expression) portal v2 REST API for tissue-specific gene expression (median TPM across 54 human tissues), expression QTLs (eQTLs), and splicing QTLs (sQTLs). Use when checking which tissues express a gene, finding which gene a non-coding/GWAS variant regulates via eQTLs, or interpreting variant regulatory effects across tissues. NOT for curated trait-variant associations (use alterlab-gwas), population allele frequencies or variant constraint (use alterlab-gnoma...

  • 68 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added May 27, 2026
data-aipythongobashexpresstestinggitapidatabasedocumentation

Works with

  • api

Security analysis

A96/100
  • mediumUses curl or wget to download content

Pro scans all 2 files and shows the line behind each finding

Scanned September 23, 2026

npx -y skills add AlterLab-IEU/AlterLab-Academic-Skills --skill alterlab-gtex --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Alterlab Gtex?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Alterlab Gtex
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/alterlab-ieu-alterlab-gtex/badge)](https://www.skillsdirectory.com/skills/alterlab-ieu-alterlab-gtex)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: alterlab-gtex
description: Query the GTEx (Genotype-Tissue Expression) portal v2 REST API for tissue-specific gene expression (median TPM across 54 human tissues), expression QTLs (eQTLs), and splicing QTLs (sQTLs). Use when checking which tissues express a gene, finding which gene a non-coding/GWAS variant regulates via eQTLs, or interpreting variant regulatory effects across tissues. NOT for curated trait-variant associations (use alterlab-gwas), population allele frequencies or variant constraint (use alterlab-gnomad), or gene/transcript structure and ID mapping (use alterlab-ensembl). Part of the AlterLab Academic Skills suite.
license: CC-BY-4.0
allowed-tools: Read WebFetch Bash(curl:*) Bash(python:*)
compatibility: Keyless GTEx Portal REST API v2 (no authentication required); datasets gtex_v10 (GENCODE v39) and gtex_v8 (GENCODE v26)
metadata:
    skill-author: AlterLab
    version: "1.0.1"
    last_updated: "2026-09-23"
---

# 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:**
- GTEx Portal: https://gtexportal.org/
- GTEx API v2: https://gtexportal.org/api/v2/
- Data downloads: https://gtexportal.org/home/downloads/adult-gtex/
- Documentation: https://gtexportal.org/home/documentationPage

## 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

| Scenario | Use Instead |
|----------|-------------|
| Curated trait-variant associations, reported traits, study provenance | `alterlab-gwas` |
| Population allele frequencies, LoF constraint (pLI/LOEUF) | `alterlab-gnomad` |
| Gene/transcript models, VEP consequences, ID mapping | `alterlab-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.

```python
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

```python
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

```python
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

```python
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)

```python
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

```python
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)

```python
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

```python
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

| Endpoint | Description |
|----------|-------------|
| `/expression/medianGeneExpression` | Median TPM by tissue for a gene |
| `/expression/geneExpression` | Full distribution of expression per tissue |
| `/association/singleTissueEqtl` | Significant eQTL associations |
| `/association/singleTissueSqtl` | Significant sQTL associations |
| `/association/egene` | eGenes in a tissue |
| `/dataset/tissueSiteDetail` | Available tissues with metadata |
| `/reference/gene` | Gene metadata; resolves a symbol to the dataset's versioned GENCODE ID |
| `/dataset/variant` | Variant lookup by `snpId` (rsID) or `variantId`; returns the b38 GTEx ID |

## Datasets Available

| ID | Description |
|----|-------------|
| `gtex_v10` | GTEx v10 (current; 946 donors, 54 tissues, eQTLs mapped in 50; GENCODE v39) |
| `gtex_v8` | GTEx 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:

```bash
# 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

- **GTEx Portal**: https://gtexportal.org/
- **API documentation**: https://gtexportal.org/api/v2/
- **Data downloads**: https://gtexportal.org/home/downloads/adult-gtex/
- **GitHub**: https://github.com/broadinstitute/gtex-pipeline
- **Citation**: GTEx Consortium (2020) Science. PMID: 32913098

## Scripts

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

```bash
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
```

Files in this skill

  • SKILL.md10.8 KB
  • references/api_reference.md4.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.