Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Query STRING REST API for PPIs (59M proteins, 20B interactions, 5000+ species). Retrieve networks, run GO/KEGG enrichment, find partners, test PPI significance, visualize networks, analyze homology. For chemical interactions use chembl-database-bioactivity; pathways use kegg-database.
.claude/skills/jaechang-hits-string-database-ppi/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-12 | ✗→✓ | ▲ Improved | 123% | 0% |
| case-21 | ✓→✗ | ▼ Worse | 192% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 264% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 222% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 252% | 0% |
Query the STRING protein-protein interaction database (59M proteins, 20B+ interactions, 5000+ species) via REST API. Covers network retrieval, functional enrichment (GO, KEGG, Pfam), interaction partner discovery, PPI enrichment testing, network visualization, and homology analysis.
bashuv pip install requests pandas
Rate limiting: No strict rate limit, but wait ~1 second between API calls. For proteome-scale analyses, use bulk downloads from https://string-db.org/cgi/download instead of the API.
pythonimport requests import time STRING_API = "https://string-db.org/api" def string_query(endpoint, params, fmt="tsv"): """Reusable helper for all STRING API calls.""" url = f"{STRING_API}/{fmt}/{endpoint}" params.setdefault("caller_identity", "python_script") response = requests.get(url, params=params) response.raise_for_status() return response.text # Map gene names to STRING IDs (always do this first) result = string_query("get_string_ids", { "identifiers": "TP53\nBRCA1\nEGFR", "species": 9606 }) print(result) # Get interaction network time.sleep(1) network = string_query("network", { "identifiers": "TP53%0dBRCA1%0dMDM2", "species": 9606, "required_score": 400 }) print(network[:500])
| Organism | Common Name | Taxon ID | |----------|-------------|----------| | Homo sapiens | Human | 9606 | | Mus musculus | Mouse | 10090 | | Rattus norvegicus | Rat | 10116 | | Drosophila melanogaster | Fruit fly | 7227 | | Caenorhabditis elegans | C. elegans | 6239 | | Saccharomyces cerevisiae | Yeast | 4932 | | Arabidopsis thaliana | Thale cress | 3702 | | Escherichia coli K-12 | E. coli | 511145 | | Danio rerio | Zebrafish | 7955 | | Gallus gallus | Chicken | 9031 |
Full species list: https://string-db.org/cgi/input?input_page_active_form=organisms
STRING uses Ensembl protein IDs with taxon prefix: {taxonId}.{ensemblProteinId} (e.g., 9606.ENSP00000269305 for human TP53). Always map gene names to STRING IDs first via get_string_ids for faster subsequent queries.
Combined scores (0-1000) integrating 7 evidence channels:
| Channel | Code | Source | |---------|------|--------| | Neighborhood | nscore | Conserved genomic neighborhood | | Fusion | fscore | Gene fusion events | | Phylogenetic profile | pscore | Co-occurrence across species | | Coexpression | ascore | Correlated RNA expression | | Experimental | escore | Biochemical/genetic experiments | | Database | dscore | Curated pathway/complex databases | | Text-mining | tscore | Literature co-occurrence and NLP |
Recommended thresholds:
Replace /tsv/ in the URL with the desired format:
/json/)/image/)python# Map gene names to STRING IDs result = string_query("get_string_ids", { "identifiers": "TP53\nBRCA1\nEGFR", "species": 9606, "limit": 1, # matches per identifier "echo_query": 1 # include query term in output }) # Parse the mapping import pandas as pd import io df = pd.read_csv(io.StringIO(result), sep='\t') id_map = dict(zip(df['queryItem'], df['stringId'])) print(id_map) # {'TP53': '9606.ENSP00000269305', 'BRCA1': '9606.ENSP00000...', ...}
python# Get PPI network with confidence scores network = string_query("network", { "identifiers": "TP53%0dBRCA1%0dMDM2%0dATM%0dCHEK2", "species": 9606, "required_score": 400, "network_type": "functional" # or "physical" }) # Parse network edges time.sleep(1) df = pd.read_csv(io.StringIO(network), sep='\t') print(f"Found {len(df)} interactions") print(df[['preferredName_A', 'preferredName_B', 'score']].head()) # Expand network with additional interactors expanded = string_query("network", { "identifiers": "TP53", "species": 9606, "add_nodes": 10, # add 10 most connected proteins "required_score": 700 })
python# Get PNG network image url = f"{STRING_API}/image/network" params = { "identifiers": "TP53%0dMDM2%0dATM%0dCHEK2%0dBRCA1", "species": 9606, "required_score": 700, "network_flavor": "evidence", # "evidence", "confidence", or "actions" "caller_identity": "python_script" } response = requests.get(url, params=params) with open("network.png", "wb") as f: f.write(response.content)
python# Discover top interaction partners partners = string_query("interaction_partners", { "identifiers": "TP53", "species": 9606, "limit": 20, "required_score": 700 }) df = pd.read_csv(io.StringIO(partners), sep='\t') print(f"Top 20 TP53 interactors:") print(df[['preferredName_B', 'score']].head(10))
python# GO, KEGG, Pfam, InterPro, SMART, UniProt Keywords enrichment # Statistical method: Fisher's exact test with Benjamini-Hochberg FDR correction enrichment = string_query("enrichment", { "identifiers": "TP53%0dMDM2%0dATM%0dCHEK2%0dBRCA1%0dATR%0dTP73", "species": 9606 }) df = pd.read_csv(io.StringIO(enrichment), sep='\t') significant = df[df['fdr'] < 0.05] print(f"Significant terms: {len(significant)}") # Group by annotation category for cat, group in significant.groupby('category'): print(f"\n{cat}: {len(group)} terms") for _, row in group.head(3).iterrows(): print(f" {row['description']} (FDR={row['fdr']:.2e})")
pythonimport json # Test if proteins form a significant functional module result = string_query("ppi_enrichment", { "identifiers": "TP53%0dMDM2%0dATM%0dCHEK2%0dBRCA1", "species": 9606, "required_score": 400 }, fmt="json") data = json.loads(result) print(f"Observed edges: {data['number_of_edges']}") print(f"Expected edges: {data['expected_number_of_edges']}") print(f"P-value: {data['p_value']}") # p < 0.05 → proteins form a significantly enriched network
python# Get homology/similarity between proteins homology = string_query("homology", { "identifiers": "TP53%0dTP63%0dTP73", "species": 9606 }) print(homology)
pythonimport requests, pandas as pd, io, json, time STRING_API = "https://string-db.org/api" def string_query(endpoint, params, fmt="tsv"): url = f"{STRING_API}/{fmt}/{endpoint}" params.setdefault("caller_identity", "python_script") response = requests.get(url, params=params) response.raise_for_status() time.sleep(1) return response.text genes = "TP53%0dBRCA1%0dATM%0dCHEK2%0dMDM2%0dATR%0dBRCA2" # Step 1: Map identifiers mapping = string_query("get_string_ids", {"identifiers": genes.replace("%0d", "\n"), "species": 9606}) # Step 2: Get interaction network network = string_query("network", {"identifiers": genes, "species": 9606, "required_score": 400}) net_df = pd.read_csv(io.StringIO(network), sep='\t') print(f"Network: {len(net_df)} interactions") # Step 3: Test PPI enrichment ppi = json.loads(string_query("ppi_enrichment", {"identifiers": genes, "species": 9606}, fmt="json")) print(f"PPI enrichment p-value: {ppi['p_value']}") # Step 4: Functional enrichment enrich = string_query("enrichment", {"identifiers": genes, "species": 9606}) enrich_df = pd.read_csv(io.StringIO(enrich), sep='\t') sig = enrich_df[enrich_df['fdr'] < 0.05] print(f"Significant GO/KEGG terms: {len(sig)}") # Step 5: Save network image img_resp = requests.get(f"{STRING_API}/image/network", params={ "identifiers": genes, "species": 9606, "required_score": 400, "network_flavor": "evidence", "caller_identity": "python_script" }) with open("protein_network.png", "wb") as f: f.write(img_resp.content)
python# Start with seed proteins, discover connected functional modules seed = "TP53" # Step 1: Get high-confidence interaction partners partners = string_query("interaction_partners", { "identifiers": seed, "species": 9606, "limit": 30, "required_score": 700 }) df = pd.read_csv(io.StringIO(partners), sep='\t') all_proteins = list(set(df['preferredName_A'].tolist() + df['preferredName_B'].tolist())) print(f"Expanded network: {len(all_proteins)} proteins") # Step 2: Enrichment on expanded set expanded_ids = "%0d".join(all_proteins[:50]) enrichment = string_query("enrichment", {"identifiers": expanded_ids, "species": 9606}) enrich_df = pd.read_csv(io.StringIO(enrichment), sep='\t') modules = enrich_df[enrich_df['fdr'] < 0.001] print(f"Highly significant terms: {len(modules)}")
python# Compare protein interactions across species for species, name, gene in [(9606, "Human", "TP53"), (10090, "Mouse", "Trp53")]: network = string_query("network", { "identifiers": gene, "species": species, "required_score": 700, "add_nodes": 5 }) df = pd.read_csv(io.StringIO(network), sep='\t') print(f"{name} ({gene}): {len(df)} interactions at score >= 700")
pythonimport pandas as pd, io enrichment_tsv = string_query("enrichment", { "identifiers": "TP53%0dBRCA1%0dATM", "species": 9606 }) df = pd.read_csv(io.StringIO(enrichment_tsv), sep='\t') # Columns: category, term, description, number_of_genes, p_value, fdr kegg = df[df['category'] == 'KEGG'].sort_values('fdr') print(kegg[['description', 'fdr']].head(5))
pythonimport time protein_lists = [["TP53", "MDM2"], ["EGFR", "ERBB2"], ["BRCA1", "BRCA2"]] results = [] for proteins in protein_lists: ids = "%0d".join(proteins) network = string_query("network", {"identifiers": ids, "species": 9606}) results.append(network) time.sleep(1) # respect rate limits
pythonversion = string_query("version", {}) print(f"STRING version: {version.strip()}") # Include in methods section: "STRING v{version}, accessed {date}"
| Parameter | Endpoint | Default | Description | |-----------|----------|---------|-------------| | identifiers | All | — | Protein IDs, %0d-separated for URL or \n-separated for POST | | species | All | — | NCBI taxon ID (9606=human, 10090=mouse) | | required_score | network, partners, ppi_enrichment | 400 | Confidence threshold 0-1000 | | network_type | network | functional | functional (all evidence) or physical (direct binding) | | add_nodes | network, image | 0 | Additional connected proteins to include (0-10) | | limit | get_string_ids, partners | 1/10 | Max results per query | | network_flavor | image | evidence | evidence, confidence, or actions |
| Problem | Cause | Solution | |---------|-------|----------| | No proteins found | Wrong species or identifier typo | Verify species taxon ID; use get_string_ids to check identifier mapping | | Empty network | Too strict confidence threshold | Lower required_score; verify proteins actually interact in STRING | | Timeout on large queries | Too many proteins in single request | Split into batches of 50-100; use bulk downloads for proteome-scale | | "Species required" error | Missing species for >10 protein networks | Always include species parameter | | Unexpected results | Wrong network type or STRING version | Check network_type (functional vs physical); verify version with /version | | 400 Bad Request | Malformed identifiers | Use %0d separator in URL or \n in POST body; URL-encode special characters | | Enrichment returns no terms | Too few input proteins | Enrichment needs 5+ proteins for meaningful results |
get_string_ids() before other operations; STRING IDs (e.g., 9606.ENSP00000269305) are faster than gene namestime.sleep(1) between API callsstring_version() for reproducibilitynetworkx-graph-analysis — Graph analysis and visualization of STRING interaction networkskegg-database — Pathway-centric queries complementary to STRING enrichmentbioservices-multi-database — Alternative access to STRING via the PSICQUIC interfaceMain SKILL.md + 1 reference file. Original total: 990 lines (SKILL.md 534 + string_reference.md 456). Scripts: 370 lines (string_api.py).
references/api_advanced.md: Advanced API features (values/ranks enrichment, bulk upload, R/Cytoscape integration), output format details, HTTP error codes, data license — content from original string_reference.md that exceeds Core API scope.
Original file disposition:
SKILL.md (534 lines) → Core API modules 1-7, Workflows 1-3, Quick Start helper function, Key Concepts (species table, score thresholds, network types). "Common Use Cases" per-operation subsections consolidated into Core API module descriptions (rule 7b): each operation's "When to use" and "Use cases" → Core API intro text. "Detailed Reference" stub section → removed, content consolidated inlinereferences/string_reference.md (456 lines) → Partially consolidated inline: API endpoints → Core API modules with code blocks; species table → Key Concepts; confidence scores → Key Concepts; identifier format → Key Concepts. Advanced features (values/ranks enrichment, bulk upload), integration examples (R STRINGdb, Cytoscape), output format details, HTTP error codes, data license → migrated to references/api_advanced.mdscripts/string_api.py (370 lines) → Helper function pattern absorbed into Quick Start (string_query reusable function). Per-function disposition: string_map_ids → Core API Module 1; string_network → Module 2; string_network_image → Module 3; string_interaction_partners → Module 4; string_enrichment → Module 5; string_ppi_enrichment → Module 6; string_homology → Module 7; string_version → Recipe. All were thin wrappers around urllib; replaced with requests-based string_query helperRetention: ~460 lines (SKILL.md) + ~180 lines (reference) = ~640 / 990 original = ~65%.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 8,152 | 41,857 | +413% | 1 | 1 | 0% | 1,699 | 6,186 | +264% | 0 | 0 | — |
case-02 | pass→pass | 9,895 | 3,815 | -61% | 1 | 1 | 0% | 1,727 | 5,557 | +222% | 0 | 0 | — |
case-03 | pass→pass | 8,936 | 4,617 | -48% | 1 | 1 | 0% | 1,628 | 5,734 | +252% | 0 | 0 | — |
case-04 | pass→pass | 5,034 | 2,978 | -41% | 1 | 1 | 0% | 868 | 5,468 | +530% | 0 | 0 | — |
case-05 | pass→pass | 9,740 | 5,525 | -43% | 1 | 1 | 0% | 2,071 | 5,963 | +188% | 0 | 0 | — |
case-06 | pass→pass | 9,444 | 3,798 | -60% | 1 | 1 | 0% | 1,913 | 5,745 | +200% | 0 | 0 | — |
case-07 | pass→pass | 5,945 | 4,928 | -17% | 1 | 1 | 0% | 1,251 | 5,876 | +370% | 0 | 0 | — |
case-08 | pass→pass | 1,561 | 1,656 | +6% | 1 | 1 | 0% | 267 | 5,184 | +1842% | 0 | 0 | — |
case-09 | pass→pass | 5,553 | 2,570 | -54% | 1 | 1 | 0% | 1,080 | 5,436 | +403% | 0 | 0 | — |
case-10 | pass→pass | 4,865 | 3,516 | -28% | 1 | 1 | 0% | 952 | 5,536 | +482% | 0 | 0 | — |
case-11 | pass→pass | 9,158 | 6,856 | -25% | 1 | 1 | 0% | 2,059 | 6,432 | +212% | 0 | 0 | — |
case-12 | fail→pass | 14,373 | 3,808 | -74% | 1 | 1 | 0% | 2,546 | 5,687 | +123% | 0 | 0 | — |
case-21 | pass→fail | 12,536 | 13,931 | +11% | 1 | 1 | 0% | 2,764 | 8,072 | +192% | 0 | 0 | — |
case-13 | pass→pass | 6,737 | 4,369 | -35% | 1 | 1 | 0% | 1,288 | 5,688 | +342% | 0 | 0 | — |
case-14 | pass→pass | 8,102 | 4,739 | -42% | 1 | 1 | 0% | 1,450 | 5,874 | +305% | 0 | 0 | — |
case-15 | pass→pass | 1,806 | 2,095 | +16% | 1 | 1 | 0% | 307 | 5,285 | +1621% | 0 | 0 | — |
case-16 | pass→pass | 4,364 | 2,474 | -43% | 1 | 1 | 0% | 885 | 5,300 | +499% | 0 | 0 | — |
case-17 | pass→pass | 3,247 | 2,362 | -27% | 1 | 1 | 0% | 547 | 5,286 | +866% | 0 | 0 | — |
case-18 | pass→pass | 10,974 | 4,257 | -61% | 1 | 1 | 0% | 786 | 5,712 | +627% | 0 | 0 | — |
case-19 | pass→pass | 3,969 | 3,023 | -24% | 1 | 1 | 0% | 826 | 5,578 | +575% | 0 | 0 | — |
case-20 | pass→pass | 5,414 | 4,072 | -25% | 1 | 1 | 0% | 1,032 | 5,758 | +458% | 0 | 0 | — |
case-22 | pass→pass | 16,384 | 13,417 | -18% | 1 | 1 | 0% | 3,781 | 7,772 | +106% | 0 | 0 | — |
case-23 | pass→pass | 9,622 | 8,218 | -15% | 1 | 1 | 0% | 2,185 | 6,632 | +204% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.