---
name: "mouse-phenome-database"
description: "Retrieve quantitative phenotypes across inbred mouse strains from MPD: metabolic, behavioral, physiological traits. Query strain means and raw measurements for body weight, glucose, blood pressure, behavioral assays, 40+ procedures. Use for QTL support, cross-strain comparison, mouse model selection. Use monarch-database for gene-disease associations; ensembl-database for genome annotations."
license: "CC-BY-4.0"
---

# mouse-phenome-database

## Overview

The Mouse Phenome Database (MPD) at the Jackson Laboratory catalogs standardized phenotype measurements across inbred, recombinant inbred, and collaborative cross mouse strains. It aggregates data from 700+ projects covering 40+ phenotype categories including body composition, metabolic parameters, cardiovascular, behavioral, and hematological measures. The REST API at `https://phenome.jax.org/api` provides programmatic access to strain summaries, individual animal data, and measurement protocols. No authentication is required; data is freely available under CC-BY-4.0.

## When to Use

- Identifying which inbred strains show extreme phenotypes (highest/lowest body weight, glucose, blood pressure) for selection as experimental models
- Retrieving phenotype data for QTL analysis using BXD, AXB, or DO panel strains
- Comparing strain means and distributions across metabolic traits for a hypothesis about genetic background effects
- Finding published MPD projects measuring a specific trait category (e.g., anxiety behavior, bone density, immune cell counts)
- Downloading individual-level measurement data for statistical modeling or power calculations
- Use `monarch-database` instead when you need disease-gene-phenotype knowledge graph associations (gene ontology, HPO phenotypes, human disease links)
- Use `ensembl-database` instead for genomic coordinate, transcript, and gene annotation lookups for specific mouse genes

## Prerequisites

- **Python packages**: `requests`, `pandas`, `matplotlib`
- **Data requirements**: strain names (e.g., `C57BL/6J`), measure IDs (e.g., `10001`), or project IDs (e.g., `Jaxwest1`)
- **Environment**: internet connection; no API key required
- **Rate limits**: no official published limits; use `time.sleep(0.5)` between batch requests; avoid bursts over 5 requests/second

```bash
pip install requests pandas matplotlib
```

## Quick Start

```python
import requests
import pandas as pd

MPD_API = "https://phenome.jax.org/api"

def mpd_get(endpoint: str, params: dict = None) -> dict:
    """GET request to MPD API; raises on HTTP errors."""
    r = requests.get(f"{MPD_API}{endpoint}", params=params, timeout=30)
    r.raise_for_status()
    return r.json()

# Retrieve strain summary for C57BL/6J
strain_info = mpd_get("/strain/C57BL%2F6J")
print(f"Strain: {strain_info.get('strainname')}")
print(f"Stock number: {strain_info.get('stocknum')}")
print(f"Background: {strain_info.get('category')}")

# Query body weight measurements across strains
result = mpd_get("/pheno/query", params={
    "measnum": "10001",    # body weight measure ID
    "sex": "m",
    "strain": "C57BL/6J,DBA/2J,BALB/cJ"
})
df = pd.DataFrame(result.get("data", []))
print(f"\nBody weight data: {len(df)} records")
if len(df) > 0:
    print(df[["strainname", "sex", "mean", "sd", "n"]].head())
```

## Core API

### Query 1: List Available Measurement Procedures

Browse available phenotype measurement categories and their procedure IDs. Use to discover what data exists before querying.

```python
import requests
import pandas as pd

MPD_API = "https://phenome.jax.org/api"

def mpd_get(endpoint, params=None):
    r = requests.get(f"{MPD_API}{endpoint}", params=params, timeout=30)
    r.raise_for_status()
    return r.json()

# List all available procedures (measurement categories)
procedures = mpd_get("/procedure")
print(f"Total procedures: {len(procedures)}")
df_proc = pd.DataFrame(procedures)
print(df_proc[["procedureid", "procedure", "category"]].head(15).to_string(index=False))
# procedureid  procedure                      category
#      10001   Body weight                    Morphology
#      10002   Body length                    Morphology
#      10010   Fasting plasma glucose         Metabolism
#      10020   Total cholesterol              Metabolism
#      10030   Triglycerides                  Metabolism
```

```python
# Filter procedures by category
metabolism_procs = [p for p in procedures if "Metabol" in p.get("category", "")]
print(f"Metabolic procedures: {len(metabolism_procs)}")
for p in metabolism_procs[:8]:
    print(f"  {p.get('procedureid'):>6}  {p.get('procedure')}")
```

### Query 2: Strain Phenotype Measurements

Query strain-level summary statistics (mean, SD, N) for a specific measurement across strains.

```python
def get_strain_phenotype(measnum: int, sex: str = "m",
                         strains: list = None) -> pd.DataFrame:
    """Retrieve strain-level phenotype summaries for a measure."""
    params = {"measnum": measnum, "sex": sex}
    if strains:
        params["strain"] = ",".join(strains)
    result = mpd_get("/pheno/query", params=params)
    return pd.DataFrame(result.get("data", []))

# Body weight (measnum=10001) in male common strains
common_strains = ["C57BL/6J", "DBA/2J", "BALB/cJ", "A/J", "C3H/HeJ",
                  "FVB/NJ", "SJL/J", "129S1/SvImJ", "NOD/ShiLtJ"]

df = get_strain_phenotype(measnum=10001, sex="m", strains=common_strains)
print(f"Body weight data (male, {len(df)} strain-project combinations):")
if len(df) > 0:
    print(df[["strainname", "mean", "sd", "n"]].sort_values("mean", ascending=False).head(8).to_string(index=False))
# strainname       mean    sd   n
# C57BL/6J         27.4   2.1  24
# NOD/ShiLtJ       25.8   2.8  18
```

### Query 3: Measurement Protocol Details

Retrieve detailed protocol metadata for a measurement including units, age at collection, and measurement description.

```python
def get_measurement_details(measnum: int) -> dict:
    """Retrieve measurement protocol metadata."""
    result = mpd_get(f"/measurement/{measnum}")
    return result

# Get details for fasting plasma glucose (10010)
meta = get_measurement_details(10010)
print(f"Measurement: {meta.get('measnum')} — {meta.get('varname')}")
print(f"Description: {meta.get('description', '')[:120]}")
print(f"Units: {meta.get('units')}")
print(f"Category: {meta.get('category')}")
print(f"Protocol notes: {meta.get('protocoldesc', '')[:150]}")
```

```python
# Batch-fetch metadata for multiple measures
measure_ids = [10001, 10010, 10020, 10030, 10040]
meta_rows = []
for mid in measure_ids:
    m = get_measurement_details(mid)
    meta_rows.append({
        "measnum": m.get("measnum"),
        "varname": m.get("varname"),
        "units": m.get("units"),
        "category": m.get("category"),
    })
df_meta = pd.DataFrame(meta_rows)
print(df_meta.to_string(index=False))
```

### Query 4: Individual Animal Data

Download raw per-animal measurements for a project and measure, enabling distribution analysis and mixed-effects modeling.

```python
def get_individual_data(project_id: str, measnum: int,
                        sex: str = None) -> pd.DataFrame:
    """Retrieve individual-level phenotype measurements for a project."""
    params = {"measnum": measnum}
    if sex:
        params["sex"] = sex
    result = mpd_get(f"/project/{project_id}/data", params=params)
    return pd.DataFrame(result.get("data", []))

# Individual body weight measurements from Jaxwest1 project
df_ind = get_individual_data("Jaxwest1", measnum=10001, sex="m")
print(f"Individual records: {len(df_ind)}")
if len(df_ind) > 0:
    print(f"Columns: {df_ind.columns.tolist()}")
    print(df_ind[["strainname", "sex", "value", "age"]].head(8).to_string(index=False))
    print(f"\nStrain means from raw data:")
    print(df_ind.groupby("strainname")["value"].agg(["mean", "std", "count"]).round(2).head(8))
```

### Query 5: Project Discovery

List available projects (studies) and browse them by phenotype category or trait keyword.

```python
def list_projects(category: str = None, limit: int = 50) -> pd.DataFrame:
    """List MPD projects; optionally filter by category."""
    params = {"limit": limit}
    if category:
        params["category"] = category
    result = mpd_get("/project", params=params)
    records = result if isinstance(result, list) else result.get("data", [])
    return pd.DataFrame(records)

# List all available projects
df_projects = list_projects(limit=200)
print(f"Total projects available: {len(df_projects)}")
if len(df_projects) > 0:
    print(df_projects.columns.tolist())
    print(df_projects.head(5).to_string(index=False))
```

```python
# Search projects by keyword in description
import requests

def search_projects(keyword: str) -> list:
    """Find projects containing a keyword in name or description."""
    r = requests.get(f"{MPD_API}/project", timeout=30)
    r.raise_for_status()
    projects = r.json() if isinstance(r.json(), list) else r.json().get("data", [])
    keyword_lower = keyword.lower()
    return [p for p in projects
            if keyword_lower in str(p.get("projsym", "")).lower()
            or keyword_lower in str(p.get("title", "")).lower()]

glucose_projects = search_projects("glucose")
print(f"Projects with glucose data: {len(glucose_projects)}")
for p in glucose_projects[:5]:
    print(f"  {p.get('projsym'):<15}  {p.get('title', '')[:60]}")
```

### Query 6: Strain Details

Retrieve metadata for a specific inbred strain including stock number, origin, and sub-strain details.

```python
import urllib.parse

def get_strain_info(strain_name: str) -> dict:
    """Retrieve strain metadata from MPD."""
    encoded = urllib.parse.quote(strain_name, safe="")
    result = mpd_get(f"/strain/{encoded}")
    return result

strains_of_interest = ["C57BL/6J", "DBA/2J", "NOD/ShiLtJ"]
for strain in strains_of_interest:
    info = get_strain_info(strain)
    print(f"{strain}:")
    print(f"  Stock: {info.get('stocknum', 'N/A')}")
    print(f"  Category: {info.get('category', 'N/A')}")
    print(f"  Origin: {info.get('origin', 'N/A')[:80]}")
    print()
```

## Key Concepts

### MPD Measure Numbering

Each phenotype measurement has a unique integer `measnum`. Commonly used IDs:

| measnum | Phenotype | Units |
|---------|-----------|-------|
| 10001 | Body weight | g |
| 10002 | Body length (nose-to-rump) | cm |
| 10010 | Fasting plasma glucose | mg/dL |
| 10020 | Total cholesterol | mg/dL |
| 10030 | Triglycerides | mg/dL |
| 10040 | HDL cholesterol | mg/dL |
| 10100 | Systolic blood pressure | mmHg |
| 10200 | Open field total distance | cm |
| 10210 | Open field center time | s |

Use `GET /procedure` to discover the full list, then `GET /measurement/{measnum}` for detailed protocol metadata.

### Strain Name Formatting

MPD uses canonical JAX strain names (e.g., `C57BL/6J`, `DBA/2J`). When passing strain names as URL path parameters, URL-encode the slash: `C57BL%2F6J`. For query parameters in `requests`, use `params={"strain": "C57BL/6J"}` — `requests` handles encoding automatically.

### Project vs Measurement Units

A single `measnum` (phenotype measurement type) may be measured by multiple independent projects using slightly different protocols. When combining data across projects, verify that units and age at measurement are consistent using the `/measurement/{measnum}` and `/project/{project_id}` endpoints.

## Common Workflows

### Workflow 1: Strain Survey for Metabolic Trait Selection

**Goal**: Retrieve and rank inbred strains by fasting glucose, cholesterol, and body weight to select appropriate models for a metabolic study.

```python
import requests
import pandas as pd
import time
import matplotlib.pyplot as plt

MPD_API = "https://phenome.jax.org/api"

def mpd_get(endpoint, params=None):
    r = requests.get(f"{MPD_API}{endpoint}", params=params, timeout=30)
    r.raise_for_status()
    return r.json()

# Target measures: fasting glucose, total cholesterol, body weight
measures = {
    10010: "fasting_glucose_mgdL",
    10020: "total_cholesterol_mgdL",
    10001: "body_weight_g",
}

target_strains = [
    "C57BL/6J", "DBA/2J", "BALB/cJ", "A/J", "C3H/HeJ",
    "FVB/NJ", "SJL/J", "NOD/ShiLtJ", "NZO/HlLtJ", "AKR/J"
]

dfs = []
for measnum, col_name in measures.items():
    result = mpd_get("/pheno/query", params={
        "measnum": measnum,
        "sex": "m",
        "strain": ",".join(target_strains)
    })
    df = pd.DataFrame(result.get("data", []))
    if len(df) > 0:
        strain_means = df.groupby("strainname")["mean"].mean().reset_index()
        strain_means.columns = ["strain", col_name]
        dfs.append(strain_means)
    time.sleep(0.5)

# Merge all traits
from functools import reduce
if dfs:
    df_merged = reduce(lambda a, b: pd.merge(a, b, on="strain", how="outer"), dfs)
    df_merged = df_merged.sort_values("fasting_glucose_mgdL", ascending=False)
    print("Strain metabolic survey (sorted by fasting glucose):")
    print(df_merged.to_string(index=False))
    df_merged.to_csv("strain_metabolic_survey.csv", index=False)

    # Bar chart of fasting glucose by strain
    fig, ax = plt.subplots(figsize=(11, 5))
    df_plot = df_merged.dropna(subset=["fasting_glucose_mgdL"]).sort_values("fasting_glucose_mgdL")
    bars = ax.barh(df_plot["strain"], df_plot["fasting_glucose_mgdL"], color="#E65100")
    ax.bar_label(bars, fmt="%.0f", padding=3, fontsize=9)
    ax.set_xlabel("Fasting Plasma Glucose (mg/dL)")
    ax.set_title("Fasting Glucose by Inbred Strain (MPD, male)")
    plt.tight_layout()
    plt.savefig("mpd_fasting_glucose_strains.png", dpi=150, bbox_inches="tight")
    print("Saved mpd_fasting_glucose_strains.png")
```

### Workflow 2: Multi-Trait Strain Comparison and Correlation

**Goal**: Retrieve body weight and blood pressure for a strain panel, test their correlation, and identify outliers.

```python
import requests
import pandas as pd
import matplotlib.pyplot as plt
import time

MPD_API = "https://phenome.jax.org/api"

def mpd_get(endpoint, params=None):
    r = requests.get(f"{MPD_API}{endpoint}", params=params, timeout=30)
    r.raise_for_status()
    return r.json()

panel_strains = [
    "C57BL/6J", "DBA/2J", "BALB/cJ", "A/J", "C3H/HeJ",
    "FVB/NJ", "SJL/J", "NOD/ShiLtJ", "NZO/HlLtJ", "AKR/J",
    "CBA/J", "C57L/J", "LP/J", "P/J", "SM/J"
]

# Fetch body weight (10001) and systolic blood pressure (10100)
records = {}
for measnum, trait in [(10001, "body_weight_g"), (10100, "systolic_bp_mmHg")]:
    result = mpd_get("/pheno/query", params={
        "measnum": measnum,
        "sex": "m",
        "strain": ",".join(panel_strains)
    })
    df = pd.DataFrame(result.get("data", []))
    if len(df) > 0:
        records[trait] = df.groupby("strainname")["mean"].mean()
    time.sleep(0.5)

if len(records) == 2:
    df_corr = pd.DataFrame(records).dropna()
    print(f"Strains with both measures: {len(df_corr)}")

    # Pearson correlation
    r_val = df_corr["body_weight_g"].corr(df_corr["systolic_bp_mmHg"])
    print(f"Pearson r (weight vs BP): {r_val:.3f}")

    # Scatter plot
    fig, ax = plt.subplots(figsize=(7, 6))
    ax.scatter(df_corr["body_weight_g"], df_corr["systolic_bp_mmHg"],
               s=60, color="#1565C0", alpha=0.8)
    for strain, row in df_corr.iterrows():
        ax.annotate(strain, (row["body_weight_g"], row["systolic_bp_mmHg"]),
                    fontsize=7, ha="left", xytext=(3, 2), textcoords="offset points")
    ax.set_xlabel("Body Weight (g)")
    ax.set_ylabel("Systolic Blood Pressure (mmHg)")
    ax.set_title(f"Body Weight vs Systolic BP Across Strains\n(r={r_val:.3f})")
    plt.tight_layout()
    plt.savefig("mpd_weight_vs_bp.png", dpi=150, bbox_inches="tight")
    print("Saved mpd_weight_vs_bp.png")

    df_corr.reset_index().rename(columns={"strainname": "strain"}).to_csv(
        "mpd_weight_bp_correlation.csv", index=False
    )
```

### Workflow 3: Individual-Level Data Export for QTL Analysis

**Goal**: Download individual animal measurements from a project and format them for QTL mapping software (R/qtl2, R/qtl).

```python
import requests
import pandas as pd

MPD_API = "https://phenome.jax.org/api"

def mpd_get(endpoint, params=None):
    r = requests.get(f"{MPD_API}{endpoint}", params=params, timeout=30)
    r.raise_for_status()
    return r.json()

# Download individual data (fasting glucose) from a specific project
project_id = "Jaxwest1"   # replace with target project
measnum = 10010            # fasting plasma glucose

result = mpd_get(f"/project/{project_id}/data", params={"measnum": measnum})
df = pd.DataFrame(result.get("data", []))
print(f"Individual records from {project_id}: {len(df)}")

if len(df) > 0:
    print(f"Columns: {df.columns.tolist()}")
    # Summary statistics per strain
    strain_stats = df.groupby(["strainname", "sex"])["value"].agg(
        ["mean", "std", "count"]
    ).round(2)
    print(f"\nStrain statistics (fasting glucose):")
    print(strain_stats.head(10))

    # Export in R/qtl-compatible format: rows = individuals, columns = phenotype + covariate
    df_qtl = df[["animalid", "strainname", "sex", "age", "value"]].copy()
    df_qtl.columns = ["id", "strain", "sex", "age_weeks", "fasting_glucose_mgdL"]
    df_qtl.to_csv(f"{project_id}_glucose_qtl_input.csv", index=False)
    print(f"\nExported {len(df_qtl)} animals to {project_id}_glucose_qtl_input.csv")
```

## Key Parameters

| Parameter | Function/Endpoint | Default | Range / Options | Effect |
|-----------|-------------------|---------|-----------------|--------|
| `measnum` | `/pheno/query`, `/project/{id}/data` | (required) | integer measure ID | Selects the phenotype measurement to retrieve |
| `sex` | `/pheno/query`, `/project/{id}/data` | (all sexes) | `"m"`, `"f"`, `"b"` (both) | Filters by animal sex |
| `strain` | `/pheno/query` | (all strains) | comma-separated strain names | Restricts results to specified strains |
| `project_id` | `/project/{id}/data` | (required) | MPD project symbol string | Specifies which study to retrieve individual data from |
| `strain_name` | `/strain/{name}` | (required) | URL-encoded strain name | Returns metadata for a specific inbred strain |
| `limit` | `/project` | varies | integer | Maximum records returned in project list |
| `category` | `/procedure` | (all) | category string | Filters procedure list by phenotype category |

## Best Practices

1. **Use `/procedure` to discover measure IDs**: MPD has hundreds of measure IDs. Query the procedure list first and filter by category to find relevant `measnum` values before fetching data.

2. **Check units and protocols before combining data across projects**: Multiple projects may measure the same trait with different protocols (fasted vs fed glucose, different ages). Use `GET /measurement/{measnum}` to verify protocol consistency before merging.

3. **Prefer strain-level summaries for initial screening, individual data for modeling**: The `/pheno/query` endpoint returns pre-computed strain means (fast); use `/project/{id}/data` for individual-level data only when statistical modeling requires it.

4. **URL-encode strain names with slashes when using path parameters**: Use `urllib.parse.quote("C57BL/6J", safe="")` → `C57BL%2F6J` for `/strain/{name}` endpoint; `requests` handles encoding automatically in `params` dictionaries.

5. **Handle missing data explicitly**: Not all strains have data for all measures. After querying, check for `NaN` values and note which strains lack data for your trait of interest before drawing conclusions about strain differences.

## Common Recipes

### Recipe: Find Strains with Extreme Phenotype Values

When to use: Select high- and low-phenotype strains for controlled genetic studies or F2 cross design.

```python
import requests
import pandas as pd

MPD_API = "https://phenome.jax.org/api"

def get_extreme_strains(measnum: int, sex: str = "m",
                        n_extremes: int = 5) -> pd.DataFrame:
    """Return top-N and bottom-N strains for a phenotype."""
    r = requests.get(f"{MPD_API}/pheno/query",
                     params={"measnum": measnum, "sex": sex},
                     timeout=30)
    r.raise_for_status()
    df = pd.DataFrame(r.json().get("data", []))
    if df.empty:
        return df
    strain_means = df.groupby("strainname")["mean"].mean().reset_index()
    strain_means.columns = ["strain", "mean_value"]
    strain_means = strain_means.sort_values("mean_value")
    high = strain_means.tail(n_extremes).assign(rank="high")
    low = strain_means.head(n_extremes).assign(rank="low")
    return pd.concat([low, high]).reset_index(drop=True)

df_extremes = get_extreme_strains(measnum=10010, sex="m", n_extremes=5)
print("Extreme strains for fasting glucose (male):")
print(df_extremes.to_string(index=False))
# strain           mean_value  rank
# A/J              101.2       low
# SJL/J            105.8       low
# ...
# NZO/HlLtJ        245.3       high
# NOD/ShiLtJ       198.7       high
```

### Recipe: Phenotype Distribution Plot for a Single Strain

When to use: Visualize the distribution of individual measurements within a strain for a QC or power analysis.

```python
import requests
import matplotlib.pyplot as plt
import pandas as pd

MPD_API = "https://phenome.jax.org/api"

def plot_strain_distribution(project_id: str, measnum: int,
                              strain: str, sex: str = "m",
                              units: str = ""):
    """Plot histogram of individual measurements for one strain."""
    r = requests.get(f"{MPD_API}/project/{project_id}/data",
                     params={"measnum": measnum, "sex": sex},
                     timeout=30)
    r.raise_for_status()
    df = pd.DataFrame(r.json().get("data", []))
    if df.empty:
        print(f"No data for project {project_id}, measnum {measnum}")
        return
    subset = df[df["strainname"] == strain]
    if subset.empty:
        print(f"Strain {strain} not found in project {project_id}")
        return
    vals = subset["value"].dropna()
    fig, ax = plt.subplots(figsize=(6, 4))
    ax.hist(vals, bins=15, color="#1B5E20", edgecolor="white", alpha=0.8)
    ax.axvline(vals.mean(), color="red", linestyle="--", label=f"Mean={vals.mean():.1f}")
    ax.set_xlabel(units or "Value")
    ax.set_ylabel("Count")
    ax.set_title(f"{strain} — measure {measnum} ({project_id})\nn={len(vals)}, SD={vals.std():.1f}")
    ax.legend()
    plt.tight_layout()
    fname = f"{strain.replace('/', '_')}_{measnum}_dist.png"
    plt.savefig(fname, dpi=150, bbox_inches="tight")
    print(f"Saved {fname}")

plot_strain_distribution("Jaxwest1", measnum=10001, strain="C57BL/6J",
                          sex="m", units="Body weight (g)")
```

### Recipe: Batch Multi-Trait Summary Table

When to use: Build a wide-format table of multiple phenotypes per strain for comparative analysis or heatmap generation.

```python
import requests
import pandas as pd
import time

MPD_API = "https://phenome.jax.org/api"

def build_phenotype_table(measure_ids: dict, strains: list,
                           sex: str = "m") -> pd.DataFrame:
    """
    Build a wide-format DataFrame: rows=strains, columns=phenotypes.
    measure_ids: dict of {measnum: column_name}
    """
    frames = []
    for measnum, col_name in measure_ids.items():
        r = requests.get(f"{MPD_API}/pheno/query",
                         params={"measnum": measnum, "sex": sex,
                                 "strain": ",".join(strains)},
                         timeout=30)
        r.raise_for_status()
        df = pd.DataFrame(r.json().get("data", []))
        if not df.empty:
            avg = df.groupby("strainname")["mean"].mean().rename(col_name)
            frames.append(avg)
        time.sleep(0.5)
    if not frames:
        return pd.DataFrame()
    wide = pd.concat(frames, axis=1).reset_index().rename(columns={"strainname": "strain"})
    return wide

panel = ["C57BL/6J", "DBA/2J", "BALB/cJ", "A/J", "NOD/ShiLtJ", "NZO/HlLtJ"]
measures = {10001: "body_wt_g", 10010: "fast_glucose_mgdL",
            10020: "cholesterol_mgdL", 10100: "systolic_bp_mmHg"}

df_wide = build_phenotype_table(measures, panel)
print(df_wide.round(1).to_string(index=False))
df_wide.to_csv("mpd_multi_trait_table.csv", index=False)
print(f"\nSaved mpd_multi_trait_table.csv ({df_wide.shape[0]} strains x {df_wide.shape[1]-1} traits)")
```

## Troubleshooting

| Problem | Cause | Solution |
|---------|-------|----------|
| Empty `data` list from `/pheno/query` | Measure ID not present for requested strains | Confirm `measnum` is valid via `/procedure`; not all strains have all measures |
| `404 Not Found` for `/strain/{name}` | Slash not URL-encoded in path | Use `urllib.parse.quote("C57BL/6J", safe="")` → `C57BL%2F6J`; or use query param approach |
| `requests.exceptions.Timeout` | Slow API response for large strain lists | Reduce strain list size; increase `timeout=60`; split large batches |
| Project `/data` endpoint returns no data | Project symbol is wrong or project has no individual data | Verify project symbol using `/project` endpoint; some projects only have summary data |
| Units inconsistent across projects for same measnum | Different protocols in different labs | Confirm units via `/measurement/{measnum}`; filter to a single project when protocol consistency is critical |
| Strain name mismatch | MPD uses specific JAX strain nomenclature | Search for similar names using the `/strain` listing; verify exact spelling with JAX stock number |
| `KeyError` when accessing response keys | API response structure varies by endpoint | Print `r.json().keys()` or `r.json()` to inspect actual structure before parsing |

## Related Skills

- `monarch-database` — disease-gene-phenotype knowledge graph with HPO annotations and cross-species gene associations
- `ensembl-database` — mouse genome annotation, gene coordinates, and transcript information
- `gwas-database` — GWAS Catalog for human SNP-trait associations (parallel to MPD mouse QTL data)
- `gseapy-gene-enrichment` — pathway enrichment analysis on gene lists derived from QTL-driven candidate gene sets

## References

- [Mouse Phenome Database](https://phenome.jax.org/) — main portal with dataset browser and download interface
- [MPD REST API Documentation](https://phenome.jax.org/api) — interactive Swagger documentation for all endpoints
- [Bogue et al., Mammalian Genome 2020](https://doi.org/10.1007/s00335-020-09839-9) — MPD overview paper (data content, strain coverage, use cases)
- [Jackson Laboratory Inbred Strain Catalog](https://www.jax.org/inbred-strains) — canonical strain names and stock numbers
- [MPD Data Use Policy](https://phenome.jax.org/about/datause) — CC-BY-4.0 license terms and citation requirements
