API reference
Every BioQ answer is available as JSON over HTTPS. Use the same endpoints as the app, from any language.
Quickstart
Create an API key in your account, keep it in an environment variable, and call any endpoint with it as a bearer token. Responses are JSON. Every response that reads data says where it was read from.
export BIOQ_KEY="bq_..." curl "https://bioq.bio/api/v1/depmap?gene=KRAS" -H "Authorization: Bearer $BIOQ_KEY"
Python, with nothing but requests:
import os, requests
BIOQ = "https://bioq.bio/api/v1"
H = {"Authorization": f"Bearer {os.environ['BIOQ_KEY']}"}
dep = requests.get(f"{BIOQ}/depmap", params={"gene": "KRAS"}, headers=H).json()
for row in dep["by_lineage"][:5]:
print(row["lineage"], row["n_dependent"], "/", row["n"])R, with httr2:
library(httr2)
bioq <- function(path, ...) {
request(paste0("https://bioq.bio/api/v1", path)) |>
req_url_query(...) |>
req_auth_bearer_token(Sys.getenv("BIOQ_KEY")) |>
req_perform() |>
resp_body_json()
}
v <- bioq("/variant", gene = "BRCA1", pathogenic_only = 1)
v$significance_histogramAuthentication
Send Authorization: Bearer <key> with every request. Keys are created and revoked on the API keys page; the full key is shown once. A key acts as you and shares your plan’s daily limits. Requests from a signed-in browser session are also accepted, so the same URLs work in the address bar while you are signed in.
Missing or revoked keys get 401 with {"error": "sign_in_required"}. GET /api/v1/status is the only endpoint that works without a key.
Limits and errors
Limits are counted per account per UTC day, in three separate buckets: database queries, sequence searches (/search), and agent questions (/agent; the queries the agent makes on your behalf are not counted again).
| Plan | Queries | Sequence searches | Agent questions |
|---|---|---|---|
| Free | 500 | 25 | 20 |
| Pro | 20,000 | 1,000 | 500 |
| Lab | 100,000 | 5,000 | 2,500 |
Every response carries X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (ISO time) and X-Request-Id. Bursts above 30 requests in 10 seconds get 429 with Retry-After.
| Status | Meaning |
|---|---|
| 200 | Answered. Read the body: several endpoints answer 200 with an unresolved list or a status other than found. |
| 202 | Sequence search queued. Poll /search/result?id=… after poll_after_seconds. |
| 400 | A parameter is missing or malformed. The body names it. |
| 401 | No valid key or session. |
| 404 | The identifier is not in that layer. The body says what was searched. |
| 429 | quota_exceeded (daily limit, see resets_at) or slow_down (burst). |
| 503 / 504 | The layer is not loaded, or the engine took too long. Retry; long lookups are cached once they finish. |
Some lookups are slow the first time. A UniProt TrEMBL record is read from a large compressed file and can take a few minutes on first access; the same record is instant afterwards. Set your client timeout to at least 200 seconds for /lookup and /whereis.
Identity & sequences
/api/v1/whereis
Ask every database layer at once whether it knows an identifier or a protein sequence.
Give either an identifier (MGnify MGYP id, UniProt accession, PDB code, Pfam accession or gene symbol) or a protein sequence, never both. Each layer answers with a status (hit, miss, unavailable, timeout, error, not_applicable) and a claim type: same_bytes means the exact record or sequence is present, similar_sequence means an aligner found a relative (not presence), annotation and coverage are statements about the identifier. A miss is a real absence; unavailable or timeout means nothing was searched, and not_applicable means that layer cannot address this kind of identifier at all. The call returns 200 even when nothing is found; read summary.absent_everywhere_is_proven before concluding absence. A homolog search that misses the default 8 s deadline keeps running and is cached, so retrying (or raising timeout_ms) usually returns it.
Parameters
| Name | Type | Description |
|---|---|---|
| id | string | Identifier to locate: MGYP…, UniProt accession, PDB code, Pfam accession (PF00069) or gene symbol. Give id or seq, not both. |
| seq | string | Protein sequence in one-letter code (whitespace ignored). Give id or seq, not both. |
| gene | string | Gene symbol used to consult the symbol-keyed layers (affinity, ClinVar). An accession cannot be turned into a symbol automatically. |
| timeout_ms | integer | Per-layer deadline in milliseconds. Values outside 50-180000 are clamped. default: 8000 |
| max_hits | integer | Sequence mode only: maximum homolog hits. Clamped to 1-200. default: 10 |
| homolog_timeout_ms | integer | Sequence mode only: separate deadline for the homolog layer. Defaults to timeout_ms. |
Example request
curl "https://bioq.bio/api/v1/whereis?id=P04637" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
verdictid mode: found | known_by_annotation_only | not_found_anywhere_loaded | not_found_in_what_answered; sequence mode: exact_sequence_present | not_found_anywhere_loaded | not_found_in_what_answeredanswers.<layer>.statushit | miss | unavailable | timeout | error | not_applicable | queued; only hit and miss are answersanswers.<layer>.claimsame_bytes | similar_sequence | annotation | coverage; similar_sequence is never presencesummary.absent_everywhere_is_proventrue only when every applicable layer answered and none had itsummary.unavailable_or_incompletelayers that searched nothing; their absence is unmeasuredanswerone plain-language sentence summarising what the layers establishedquery.id_kindhow the identifier was classified (mgnify, uniprot, pfam, pdb, gene, unknown)claim_legend, rules, readyalso returned (omitted from the sample): definitions of each claim type and the engine's loaded-layer map
Example response
{
"endpoint": "/whereis",
"mode": "id",
"query": {
"id": "P04637",
"id_kind": "uniprot",
"gene": null
},
"verdict": "known_by_annotation_only",
"answers": {
"trembl": {
"claim": "same_bytes",
"claim_meaning": "this UniProt accession is a record in TrEMBL, sequence reconstructed from the indexed byte offsets",
"status": "miss",
"count": 0,
"searched": "TrEMBL accession index",
"means": "the TrEMBL index was searched and does not carry this accession. TrEMBL is the UNREVIEWED half of UniProt — a reviewed (Swiss-Prot) accession correctly misses here, and that is an answer, not an error",
"latency_ms": 0.2
},
"structure": {
"claim": "annotation",
"claim_meaning": "the structure index holds metadata about this accession (an AlphaFold model: pLDDT and residue count, no residues). That is a statement about the identifier, not its bytes",
"status": "hit",
"count": 1,
"structure_id": "P04637",
"evidence": {
"kind": "predicted",
"method": "ALPHAFOLD",
"model_version": "4",
"variant": "F1",
"mean_plddt": 74.33,
"frac_plddt_gt90": 0.514,
"frac_plddt_gt70": 0.5776,
"quality_call": "confident",
"quality_rule": "confident if mean_plddt >= 70; low_confidence if >= 50; very_low_confidence below 50",
"usable_for_claims": true,
"comparable_with": "nothing experimental — pLDDT is a model's self-estimate, not a measurement error"
},
"sequence_available": false,
"latency_ms": 0.7
},
"mgnify_c2": {
"claim": "same_bytes",
"claim_meaning": "this exact identifier is a record in the MGnify30-C2 representative store, and the sequence was read back from it",
"status": "not_applicable",
"count": null,
"why": "the C2 store is keyed by MGnify protein id (MGYP...). This identifier is not one, so no lookup is possible now or after mounting it",
"means": "no lookup was possible, so this is neither a hit nor a miss. Mounting the layer would not change that.",
"latency_ms": 0.01
}
},
"summary": {
"present_in": [
"structure"
],
"absent_from": [
"trembl"
],
"unavailable_or_incomplete": [],
"not_applicable": [
"mgnify_c2",
"mgnify_members",
"pfam_domains",
"pfam_reverse",
"assemblies",
"affinity",
"clinvar"
],
"n_layers": 9,
"n_hit": 1,
"n_miss": 1,
"n_incomplete": 0,
"absent_everywhere_is_proven": false,
"absent_everywhere": null
},
"answer": "no layer holds this with the same bytes; structure know(s) it by annotation. An annotation or coverage answer is a statement ABOUT the identifier, not the presence of its sequence",
"elapsed_seconds": 0.001,
"timeout_ms": 8000
}/api/v1/lookup
Fetch the stored sequence for an MGnify protein id or a UniProt TrEMBL accession.
Returns the exact sequence read back from the source record. MGYP ids are looked up in the MGnify representative store first and then in the full 5.74-billion-protein member table; other accessions are looked up in UniProt TrEMBL (the unreviewed half of UniProt). Reviewed Swiss-Prot accessions such as P69905 are not in this lookup layer and answer 404 — use /search or /structure for those. A 404 says whether it is proof of absence (not_found_is_proof_of_absence) or only 'not in what is loaded'. TrEMBL lookups replay a compressed stream, so latency grows with the record's position and can reach minutes; the response carries a cost_model saying so.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequired | string | MGnify protein accession (MGYP000000149140) or UniProt TrEMBL accession. |
Example request
curl "https://bioq.bio/api/v1/lookup?id=MGYP000000149140" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
hit.sequencethe stored amino-acid sequence, read back from the recordhit.sourcemgnify30-c2 (representative store), mgnify-members or tremblhit.lengthsequence length in residueshit.provenancewhere in the source file the record was readhit.cachedtrue when served from the engine's in-memory cachehit.c2_missedMGYP only: true when the representative store missed and the full member table answeredhit.cost_modelTrEMBL only: latency grows with record offset; a fast answer does not mean the next one will be fast
Example response
{
"hit": {
"accession": "MGYP000000149140",
"sequence": "MQGLGDWGFPQKFKKSRIVRDDPLTNFDLFKNSIWVPKCYACQKKRGLAPQSRNSRTEVM…",
"length": 88,
"source": "mgnify30-c2",
"provenance": {
"part": 0,
"anchor_offset": 0,
"window_bytes": 63313,
"anchor_id": 149140
},
"c2_missed": false,
"cached": true,
"elapsed_seconds": 0.000005
}
}/api/v1/search
Find proteins similar to a query sequence with an MMseqs2 homology search.
Aligns your protein sequence against a named corpus (currently sprot, UniProt Swiss-Prot) and returns hits with e-value, bit score, percent identity and coverage. Hits are relatives, not proof that your exact sequence is in the corpus. Identical requests are answered from a cache: served_in_seconds is the lookup cost and computed_in_seconds is what the search actually took. If a corpus cannot be searched inside the request the engine answers 202 with a job_id; poll /search/result until it returns 200. hits is null whenever no answer was produced (queued, failed, layer off), and is never an empty list in those cases. Sequence searches count against a separate, smaller daily quota.
Parameters
| Name | Type | Description |
|---|---|---|
| seqrequired | string | Protein sequence in one-letter code (whitespace and case ignored). Letters outside ACDEFGHIKLMNPQRSTVWYXBZUO* are rejected. |
| corpus | string | Target corpus id. Unknown ids are refused with the list of available corpora, never silently replaced. default: sprot |
| sensitivity | number | MMseqs2 sensitivity (-s). Higher is slower and finds more distant relatives. default: 5.7 |
| max_seqs | integer | Maximum number of target hits to return. default: 50 |
| evalue | number | E-value threshold; hits above it are dropped. default: 0.001 |
| sync | boolean | Only matters for corpora that cannot be searched inline: 1 asks the engine to wait (needs max_wait_seconds at least the estimate), 0 never waits. |
| max_wait_seconds | number | Your own deadline for an inline search; honoured over the server default. |
| refresh | boolean | Bypass the cache and recompute. Pass 1 to enable. default: 0 |
Example request
curl "https://bioq.bio/api/v1/search?seq=MVLSPADKTNVKAAWGKVGAHAGEYGAEALERMFLSFPTTKTYFPHFDLSH" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
hits[]target, evalue, bits, alnlen, pident (percent, 0-100), qcov and tcov (fractions 0-1), qstart/qend/tstart/tend (1-based)hit_countnumber of hits returnedparametersthe exact search settings that produced the hitscorpus / corpus_idthe population searched; a miss says nothing about other corporacachedtrue when replayed from cache; then see cache_detail.explaincomputed_in_seconds vs served_in_secondsreal search cost vs cost of this responsestatusok (200), queued (202); with a 202 also job_id, poll and poll_after_seconds
Example response
{
"hits": [
{
"target": "P69905",
"evalue": 2.023e-29,
"bits": 111,
"alnlen": 51,
"pident": 100,
"qcov": 1,
"tcov": 0.359,
"qstart": 1,
"qend": 51,
"tstart": 1,
"tend": 51
},
{
"target": "P69906",
"evalue": 2.023e-29,
"bits": 111,
"alnlen": 51,
"pident": 100,
"qcov": 1,
"tcov": 0.359,
"qstart": 1,
"qend": 51,
"tstart": 1,
"tend": 51
},
{
"target": "P69907",
"evalue": 2.023e-29,
"bits": 111,
"alnlen": 51,
"pident": 100,
"qcov": 1,
"tcov": 0.359,
"qstart": 1,
"qend": 51,
"tstart": 1,
"tend": 51
}
],
"hit_count": 50,
"query_length": 51,
"parameters": {
"sensitivity": 5.7,
"max_seqs": 50,
"evalue": 0.001,
"threads": 8,
"db_load_mode": 2
},
"db": "sprot",
"search_seconds": 8.603,
"cached": true,
"computed_in_seconds": 8.763,
"served_in_seconds": 0.0006,
"corpus": "sprot",
"index": true,
"corpus_detail": {
"id": "sprot",
"indexed": true,
"target_db": "sprot",
"db_bytes": 2931031187,
"fingerprint": "sha256:5cf05454c9bbe1ec2c49e7b515441898",
"fingerprint_kind": "stat-change-detector",
"index_note": "createindex output present; a query reads the k-mer table"
},
"cache_detail": {
"outcome": "hit",
"age_seconds": 34377.505,
"computed_at": "2026-10-22T15:47:39+00:00",
"computed_by": "sync",
"cached_from": "2026-10-22T15:47:39+00:00",
"cached_computed_in_seconds": 8.763,
"explain": "this answer took 8.76 s to compute on 2026-10-22T15:47:39+00:00 (sync) and is replayed from cache. The 0.0006 s above is the cost of the cache lookup, NOT the cost of the search."
},
"status": "ok",
"request_id": "9cea3c8a8db0",
"corpus_id": "sprot",
"elapsed_seconds": 0.001,
"source": "MMseqs2"
}/api/v1/search/result
Poll a queued sequence search by its job id.
Use the job_id returned by a 202 from /search. While the job is queued or running the answer is 202 with hits null; when done it is 200 with the same body shape as /search plus the job record, cost_of_first_query_seconds and corpus_current. If the corpus changed after the job ran, corpus_warning says the answer describes the build that was searched. A failed job answers 503 with hits null, which is not a negative result. Pass the same corpus you submitted with, because job ids are only unique within a corpus.
Parameters
| Name | Type | Description |
|---|---|---|
| idrequired | string | job_id from the 202 response of /search. |
| corpus | string | Corpus the job was submitted to. default: sprot |
Example request
curl "https://bioq.bio/api/v1/search/result?id=0" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
statusqueued | running (202), ok (200), failed | done_but_unreadable (503)hitsnull until the search completes; then the same hit objects as /searchwaited_seconds / estimated_secondsprogress information while queued or runningcost_of_first_query_secondshow long the original search tookcorpus_currentfalse when the corpus was rebuilt after the job ran (see corpus_warning)- The sample shows the 404 for an unknown job id; real ids come from a 202 on /search
Example response
{
"error": "unknown job id",
"id": "0",
"hits": null
}Genes & disease
/api/v1/gene
Summarise what the engine holds about a human gene symbol, starting with its ClinVar records.
Returns the ClinVar significance histogram for the gene, a five-row sample of records, and a discovery_readiness verdict. ClinVar counts are submissions, not independent observations, so the verdict says whether a distribution may be described at all. clusters is always null: MGnify proteins carry no gene symbol, so a gene cannot be mapped to metagenomic clusters by name. An unknown symbol still answers 200, with the reason in unresolved and verdict absent.
Parameters
| Name | Type | Description |
|---|---|---|
| symbolrequired | string | Gene symbol, e.g. EGFR, BRCA1 (case-insensitive). |
Example request
curl "https://bioq.bio/api/v1/gene?symbol=EGFR" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
variants.n_matchedClinVar records for this symbol (submissions, not patients)variants.significance_histogramcounts per clinical significance over all matched recordsvariants.sampleup to 5 records (chrom, pos, gene, sig, review)clustersalways null; see unresolvedunresolved[]what could not be answered and whydiscovery_readiness.verdicttestable | weak | no_claim | absent | unknown; claim_allowed is true only for testable and weak
Example response
{
"gene": "EGFR",
"variants": {
"n_returned": 20,
"n_matched": 4141,
"truncated": true,
"significance_histogram": {
"Uncertain_significance": 1899,
"Likely_benign": 1527,
"Conflicting_classifications_of_pathogenicity": 254,
"Benign/Likely_benign": 132,
"Benign": 95,
"": 72,
"Pathogenic": 60,
"drug_response": 48,
"Likely_pathogenic": 39,
"not_provided": 7,
"Likely_pathogenic|drug_response": 6,
"Uncertain_significance|drug_response": 1,
"Pathogenic|drug_response": 1
},
"sample": [
{
"chrom": "7",
"pos": 55018796,
"gene": "EGFR",
"sig": "Likely_benign",
"review": "criteria_provided,_single_submitter"
},
{
"chrom": "7",
"pos": 55019062,
"gene": "EGFR",
"sig": "Benign/Likely_benign",
"review": "criteria_provided,_multiple_submitters,_no_conflicts"
}
]
},
"clusters": null,
"unresolved": [
"MGnify clusters carry no gene symbol, so a gene cannot be mapped to clusters by name; use /lookup with a cluster id, or a homology search"
],
"discovery_readiness": {
"verdict": "testable",
"claim_allowed": true,
"n_records": 4141,
"n_distinct_values": 12,
"min_records_required": 3,
"why": "4141 records with 12 distinct values: describing the distribution is possible, but these are submissions, not independent samples",
"can_do": [
"the 4141 records can describe a distribution (e.g. the pathogenic fraction)",
"these are submission counts, not independent samples; do not use them as n for confidence intervals or significance tests"
]
}
}/api/v1/variant
List ClinVar variants for a gene or around a GRCh38 position.
Query by gene symbol, or by chromosome plus position with a window in base pairs. A chromosome without a position (or the reverse) is refused rather than answered with unrelated rows. matched and significance_histogram cover every matching record; results is one page of at most 50 rows sorted by position, and truncated says when more exist. Uncertain significance (VUS) and conflicting classifications are not pathogenic, and review status is shown because evidence tiers differ.
Parameters
| Name | Type | Description |
|---|---|---|
| gene | string | Gene symbol (case-insensitive). |
| chrom | string | Chromosome name as in ClinVar, e.g. 17 or X. Requires pos. |
| pos | integer | 1-based GRCh38 position. Requires chrom. |
| window | integer | Half-width in base pairs around pos. default: 100 |
| pathogenic_only | boolean | Keep only Pathogenic, Likely_pathogenic and Pathogenic/Likely_pathogenic records (1 or true). default: 0 |
Example request
curl "https://bioq.bio/api/v1/variant?gene=BRCA1&pathogenic_only=1" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
matchednumber of records that met the filter (not the page size)n_returnedrows in this page (at most 50)truncatedtrue when matched exceeds the pagesignificance_histogramcounts per significance over all matched records; sums to matchedresults[]chrom, pos, gene, sig (clinical significance), review (review status)caveatreminder that VUS and conflicting records are not pathogenic
Example response
{
"matched": 4118,
"n_returned": 50,
"truncated": true,
"significance_histogram": {
"Pathogenic": 3613,
"Likely_pathogenic": 277,
"Pathogenic/Likely_pathogenic": 228
},
"caveat": "VUS and Conflicting are not pathogenic; review status is shown because evidence tiers differ",
"results": [
{
"chrom": "17",
"pos": 43041660,
"gene": "BRCA1",
"sig": "Pathogenic",
"review": "no_assertion_criteria_provided"
},
{
"chrom": "17",
"pos": 43041662,
"gene": "BRCA1",
"sig": "Pathogenic",
"review": "criteria_provided,_single_submitter"
},
{
"chrom": "17",
"pos": 43044293,
"gene": "BRCA1",
"sig": "Pathogenic",
"review": "criteria_provided,_single_submitter"
}
]
}/api/v1/depmap
Look up CRISPR dependency (DepMap Chronos gene effect) by gene, by lineage, or both.
With gene only, returns the gene's dependency profile across all cell lines and per lineage (most dependent lineage first). With lineage only, ranks genes by how lineage-specific the dependency is (selectivity), excluding common-essential genes unless asked. With both, lists the individual cell lines and their effect. mutations=1 lists cell lines with a HIGH-impact somatic variant in the gene. More negative gene effect means knocking the gene out hurt viability more; a line counts as dependent at effect <= -0.5, a named threshold rather than a property of the data. Unknown genes or lineages answer 404 with what was searched.
Parameters
| Name | Type | Description |
|---|---|---|
| gene | string | Gene symbol (case-insensitive). |
| entrez | integer | Entrez gene id, as an alternative to gene. |
| lineage | string | Lineage name; exact, case-insensitive, or a unique substring (e.g. Pancreas, lung). |
| top | integer | Rows to return (lineages, genes or cell lines depending on mode). Clamped to 1-500. default: 25 |
| all | boolean | Gene mode: return every lineage instead of the top ones. Pass 1 to enable. default: 0 |
| include_pan_essential | boolean | Lineage mode: keep common-essential genes in the ranking. Pass 1 to enable. default: 0 |
| mutations | boolean | List lines with a HIGH-impact somatic variant in gene (requires gene; lineage optional). Pass 1 to enable. default: 0 |
Example request
curl "https://bioq.bio/api/v1/depmap?gene=KRAS" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
corpus.n_dependent / n_lineslines at or below the dependency threshold, over lines assayedcorpus.profile.binshistogram of gene effect across all linesby_lineage[]n, n_dependent, frac_dependent, mean, median, sd, selectivity per lineage, most negative mean firstselectivitylineage mean minus all-lines mean; near zero for pan-essential genesthresholds.dependent_if_effect_lethe dependency cut-off (-0.5)- Lineage mode returns genes[] ranked by selectivity; gene+lineage returns lines[] with effect and is_dependent; mutations=1 returns lines[] (up to 100)
Example response
{
"gene": {
"symbol": "KRAS",
"entrez": 3845,
"gene_idx": 7807,
"is_common_essential": false,
"is_nonessential": false
},
"corpus": {
"n_lines": 1178,
"n_dependent": 399,
"mean_effect": -0.5572,
"median_effect": -0.3732,
"profile": {
"n": 1178,
"bins": {
"[-9,-1.0)": 168,
"[-1.0,-0.5)": 231,
"[-0.5,-0.1)": 698,
"[-0.1,0.1)": 72,
"[0.1,9)": 9
},
"min": -4.143,
"max": 0.326
}
},
"by_lineage": [
{
"lineage": "Pancreas",
"n": 47,
"n_dependent": 43,
"frac_dependent": 0.9149,
"mean": -1.7898,
"median": -1.758,
"sd": 0.8157,
"selectivity": -1.2326
},
{
"lineage": "Bowel",
"n": 63,
"n_dependent": 48,
"frac_dependent": 0.7619,
"mean": -1.1779,
"median": -1.0111,
"sd": 0.797,
"selectivity": -0.6206
},
{
"lineage": "Eye",
"n": 15,
"n_dependent": 6,
"frac_dependent": 0.4,
"mean": -0.7888,
"median": -0.39,
"sd": 0.7294,
"selectivity": -0.2315
}
],
"thresholds": {
"dependent_if_effect_le": -0.5,
"min_lines_for_lineage_row": 5,
"mutation_impacts_kept": [
"HIGH"
]
},
"meaning": "mean is the Chronos gene effect; MORE NEGATIVE means knocking the gene out reduced viability more. selectivity is the lineage mean minus the corpus mean, so a pan-essential gene has a very negative mean but selectivity near zero.",
"elapsed_ms": 0.21,
"index": "depmap_24q4 — Chronos gene effect; a dependency is effect <= dependent_if_effect_le, which is a named constant, not a property of the data"
}/api/v1/omics
Get a gene's expression or copy number across DepMap cell lines, or correlate two genes.
layer=expr is expression as log2(TPM+1); layer=cn is absolute copy number on a linear scale. They are different quantities and are never pooled. With one gene you get the distribution overall, per lineage, and the lowest and highest lines; with two genes you get Pearson r over the lines where both are measured. Unmeasured lines are NaN, never zero, and are reported as n_unmeasured. Some lines carry an empty lineage string. An unknown gene in single-gene mode answers 404, but in two-gene mode it answers 200 with an error field (unknown gene) — check for it.
Parameters
| Name | Type | Description |
|---|---|---|
| layer | string | expr (log2(TPM+1)) or cn (absolute linear copy number). default: expr |
| gene | string | Gene symbol for a single-gene profile (alias: a). |
| b | string | Second gene symbol; with gene/a, returns a correlation. |
| compare | string | Alternative spelling of a two-gene request: compare=ERBB2&compare=EGFR. |
| top | integer | Rows in by_lineage, lowest and highest. Clamped to 1-200. default: 15 |
Example request
curl "https://bioq.bio/api/v1/omics?gene=EGFR&top=5" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
n_measured / n_unmeasuredlines with and without a value for this geneoverallmean, median, sd over measured linesby_lineage[]n, mean, median, sd, min, max per lineagelowest[] / highest[]extreme cell lines (ModelID, lineage, value)value_meaningthe unit of every value in this response- Two-gene mode: pearson_r and n_shared_lines (lines where both genes are measured)
Example response
{
"layer": "expr",
"gene": "EGFR",
"n_models": 1987,
"n_measured": 1673,
"n_unmeasured": 314,
"overall": {
"mean": 3.3837,
"median": 3.9523,
"sd": 2.3654
},
"by_lineage": [
{
"lineage": "Head and Neck",
"n": 61,
"mean": 5.7235,
"median": 5.9991,
"sd": 1.687,
"min": 0.0144,
"max": 9.3546
},
{
"lineage": "Bladder/Urinary Tract",
"n": 34,
"mean": 5.7135,
"median": 5.5229,
"sd": 1.3177,
"min": 3.4777,
"max": 10.0761
}
],
"lowest": [
{
"ModelID": "ACH-003463",
"lineage": "",
"value": 0
},
{
"ModelID": "ACH-003273",
"lineage": "",
"value": 0
}
],
"highest": [
{
"ModelID": "ACH-001523",
"lineage": "Skin",
"value": 10.5319
},
{
"ModelID": "ACH-000741",
"lineage": "Bladder/Urinary Tract",
"value": 10.0761
}
],
"value_meaning": "log2(TPM+1) as shipped by the release",
"elapsed_ms": 19.19,
"index": "depmap_24q4 omics side-car — expr is log2(TPM+1), cn is ABSOLUTE linear copy number; a NaN is an unmeasured line, never a zero"
}Drugs & interactions
/api/v1/affinity
List measured drug-target binding and potency values (ChEMBL, DrugCentral, Guide to Pharmacology).
Query by target gene symbol, by drug name, or both. Every measurement keeps its own type (IC50, Ki, Kd, …), unit and unit_class; molar and log values are never averaged together, and p_value (-log10 molar) is the only column comparable across classes. The list is sorted by p_value, highest first, so a limited page is the most potent slice, not a sample; summary is always computed over all matches (see summary_basis). assay_type separates binding (B) from functional (F) and ADME (A) assays; rows from sources without an assay type are excluded when it is set. An empty list with 200 means the layer answered and nothing matched; a 503 with measurements null means the layer is off.
Parameters
| Name | Type | Description |
|---|---|---|
| gene | string | Target gene symbol (case-insensitive). |
| drug | string | Drug name (case-insensitive exact match). |
| type | string | Measured type, e.g. IC50, Ki, Kd, EC50, pIC50 (case-insensitive). |
| assay_type | string | ChEMBL assay type letter (case-insensitive), e.g. B binding, F functional, A ADME. |
| limit | integer | Rows in the measurement list; 0 or negative returns all rows. Does not affect summary. default: 100 |
Example request
curl "https://bioq.bio/api/v1/affinity?gene=EGFR&drug=gefitinib&limit=5" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
total_matched / n / truncatedall matches vs rows in this pagemeasurements[]measured_type, value, unit, unit_class (molar | log), relation, assay_type, p_value, drug_name, gene, source, source_keyp_value-log10 molar; the only value comparable across unit classessummary.per_unit_classmin/median/max per unit class, never pooledsummary.per_type_and_class, per_type_and_assay_typethe same, split furthersummary_basisstates that the summary covers all matches, not the pageempty_becausepresent when no measurement matched (layer loaded and answered)
Example response
{
"query": {
"gene": "EGFR",
"drug": "gefitinib",
"type": null,
"assay_type": null,
"limit": 5
},
"n": 5,
"total_matched": 328,
"truncated": true,
"measurements": [
{
"target_id": "CHEMBL203",
"target_name": "Epidermal growth factor receptor",
"gene": "EGFR",
"genes_all": [
"EGFR"
],
"drug_name": "gefitinib",
"drug_id": "CHEMBL939",
"measured_type": "IC50",
"value": 0.1,
"unit": "nM",
"unit_class": "molar",
"unit_inferred": null,
"unit_basis": "source_stated_molar_unit",
"relation": "=",
"assay_type": "B",
"standard_flag": null,
"p_value": 10,
"source": "chembl",
"source_key": "chembl_37.db|activity_id=13297481",
"row_ref": 13297481,
"sha256": "4be13df3b68e25dcd0bff44bf094033b5aebe98f415acdc8c1cdf380e0c15142",
"file": "chembl_37.db"
},
{
"target_id": "CHEMBL203",
"target_name": "Epidermal growth factor receptor",
"gene": "EGFR",
"genes_all": [
"EGFR"
],
"drug_name": "gefitinib",
"drug_id": "CHEMBL939",
"measured_type": "IC50",
"value": 0.1,
"unit": "nM",
"unit_class": "molar",
"unit_inferred": null,
"unit_basis": "source_stated_molar_unit",
"relation": "=",
"assay_type": "B",
"standard_flag": null,
"p_value": 10,
"source": "chembl",
"source_key": "chembl_37.db|activity_id=13380887",
"row_ref": 13380887,
"sha256": "4be13df3b68e25dcd0bff44bf094033b5aebe98f415acdc8c1cdf380e0c15142",
"file": "chembl_37.db"
}
],
"summary": {
"n_measurements": 328,
"per_unit_class": {
"log": {
"n": 7,
"min": 7.230000019073486,
"median": 8.300000190734863,
"max": 9.4,
"unit_class": "log",
"span_orders_of_magnitude": 2.17
},
"molar": {
"n": 321,
"min": 0.1,
"median": 14.4,
"max": 22800,
"unit_class": "molar",
"span_orders_of_magnitude": 5.36
}
},
"per_type_and_class": {
"IC50/molar": {
"n": 269,
"min": 0.1,
"median": 20,
"max": 22800,
"unit_class": "molar",
"span_orders_of_magnitude": 5.36
},
"KD/molar": {
"n": 38,
"min": 0.52,
"median": 1.1,
"max": 470,
"unit_class": "molar",
"span_orders_of_magnitude": 2.96
},
"PIC50/log": {
"n": 2,
"min": 7.230000019073486,
"median": 8.149999618530273,
"max": 8.149999618530273,
"unit_class": "log",
"span_orders_of_magnitude": 0.92
}
},
"per_type_and_assay_type": {
"AC50/assay=F": {
"n": 1,
"min": 3.8,
"median": 3.8,
"max": 3.8,
"unit_class": "molar",
"span_orders_of_magnitude": 0
},
"EC50/assay=A": {
"n": 1,
"min": 2162,
"median": 2162,
"max": 2162,
"unit_class": "molar",
"span_orders_of_magnitude": 0
}
},
"p_value": {
"n": 328,
"min": 4.64,
"median": 7.85,
"max": 10,
"unit_class": "log",
"span_orders_of_magnitude": 5.36,
"note": "p_value is -log10 molar, so its span is max-min and is comparable across sources"
},
"warning": "value is NOT comparable across unit_class; never average them. p_value (-log10 molar) is the only cross-class comparable column."
},
"summary_basis": "all 328 matches (the list is a page of 5)",
"layer": {
"rows": 5153526,
"genes": 6682,
"drugs": 29967,
"built_at": "2026-10-21T08:57:39Z",
"sources": {
"chembl": {
"file": "chembl_37.db",
"rows_kept": 5113551,
"rows_read": 5210648,
"rows_refused": 97097,
"sha256": "4be13df3b68e25dcd0bff44bf094033b5aebe98f415acdc8c1cdf380e0c15142"
},
"drugcentral": {
"file": "drugcentral.tsv.gz",
"rows_kept": 20685,
"rows_read": 22364,
"rows_refused": 1679,
"sha256": "1908684983a79a067a44da952e1f6fd26b0f34cc36560cd9be0f0843bbfee1e1"
},
"gtopdb": {
"file": "gtopdb.csv",
"rows_kept": 19290,
"rows_read": 24959,
"rows_refused": 5669,
"sha256": "c1b81895f520bd2b29b5631224320da4047a7d175dc06ba2adcc60f1a9e1ffa1"
}
}
},
"elapsed_seconds": 0.008
}/api/v1/interaction
Get protein-protein interaction partners from STRING v12 or BioGRID, or test a single pair.
By default this reads STRING v12: partners sorted by combined_score, which is a confidence on a 0-1000 scale, not a probability. source=biogrid reads curated BioGRID records instead; each names an experimental system and a publication, and its score is free text ('-' in most rows) that must not be sorted or averaged. The two sources are never merged. A protein that is not in the species dictionary, an ambiguous symbol (candidates are listed) or a pair with no edge answers 404; an absent edge means 'not in this release at this threshold', not evidence of no interaction. STRING interaction is a different claim from metagenomic co-occurrence (/cooccur).
Parameters
| Name | Type | Description |
|---|---|---|
| protein | string | Gene symbol, UniProt accession, Ensembl protein id or STRING id. Aliases: id or gene for STRING; gene or symbol for BioGRID (uppercase exact symbol match). |
| a | string | First protein of a single-pair query (use with b). |
| b | string | Second protein of a single-pair query. |
| source | string | string (STRING v12) or biogrid (BioGRID 4.4). default: string |
| species | string | NCBI taxonomy id (alias: taxid). STRING only; unknown ids answer 404 with the available list. default: 9606 |
| min_score | integer | STRING only: minimum combined_score on the 0-1000 scale (not clamped). default: 0 |
| limit | integer | Maximum partners (STRING) or records (BioGRID). default: 50 |
| channels | boolean | STRING only: include per-channel scores (experimental, database, textmining, …) where available. Pass 1 to enable. default: 0 |
| uniprot | string | BioGRID only: query by UniProt accession. |
| type | string | BioGRID symbol queries only: physical or genetic (exact, lowercase; other values return zero records). Ignored with uniprot. |
Example request
curl "https://bioq.bio/api/v1/interaction?protein=TP53&species=9606&min_score=900&limit=5" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
partners[]partner (STRING id), partner_name, partner_uniprot, combined_scorescore_scalecombined_score is 0-1000 confidence, not a probabilitydegree_total / degree_at_or_above_min_scorepartner counts before the limitresolutionhow the query was matched (symbol, uniprot, ensembl, string_id)- Single pair (a, b): found, combined_score, channels; same_node true when both tokens are the same protein
- BioGRID: interactions[] with system, pubmed, throughput, score_text; by_experimental_system_top10; n_distinct_publications
absence_notean absent edge is not evidence of no interaction
Example response
{
"query": "TP53",
"found": true,
"string_id": "9606.ENSP00000269305",
"preferred_name": "TP53",
"uniprot": "P04637",
"resolution": "symbol",
"degree_total": 7443,
"degree_at_or_above_min_score": 283,
"min_score_filter": 900,
"returned": 5,
"score_name": "combined_score",
"score_scale": "0-1000 (STRING v12 combined_score; a confidence, not a probability)",
"channels_available": [
"neighborhood",
"fusion",
"cooccurence",
"coexpression",
"experimental",
"database",
"textmining"
],
"partners": [
{
"partner": "9606.ENSP00000212015",
"partner_name": "SIRT1",
"partner_uniprot": "Q96EB6",
"score_name": "combined_score",
"combined_score": 999,
"score_scale": "0-1000 (STRING v12 combined_score; a confidence, not a probability)"
},
{
"partner": "9606.ENSP00000254719",
"partner_name": "RPA1",
"partner_uniprot": "P27694",
"score_name": "combined_score",
"combined_score": 999,
"score_scale": "0-1000 (STRING v12 combined_score; a confidence, not a probability)"
},
{
"partner": "9606.ENSP00000258149",
"partner_name": "MDM2",
"partner_uniprot": "Q00987",
"score_name": "combined_score",
"combined_score": 999,
"score_scale": "0-1000 (STRING v12 combined_score; a confidence, not a probability)"
}
],
"layer": "string_ppi",
"source": "STRING v12",
"taxid": "9606",
"claim": "a STRING interaction edge (curated/detected association), NOT an assembly co-occurrence",
"sibling_sources": {
"biogrid": "/interaction?source=biogrid&protein=<SYMBOL> - curated experiments with a PubMed id; keyed by gene symbol and UniProt accession, which is the join our layers use",
"string_node_count": 19699,
"note": "STRING and BioGRID are different evidence types; neither replaces the other and this endpoint never merges them"
},
"absence_note": "an absent edge means 'not in this release at this threshold'; STRING lists positive associations only",
"elapsed_seconds": 0.0002
}Proteins & domains
/api/v1/domains
List the Pfam domains annotated on an MGnify protein.
The Pfam table covers about 2.99 billion of the 5.74 billion MGnify proteins. A protein that is not in it returns domains null and no_annotation true, which means unannotated, not 'has no domains'. Domains are listed in the source's row order and are not ranked by e-value: the index keeps only (protein, domain) pairs, so a marginal hit and a certain one look the same. discovery_readiness says how many independent assemblies the protein was observed in.
Parameters
| Name | Type | Description |
|---|---|---|
| proteinrequired | string | MGnify protein id, as MGYP000000000001 or the bare integer 1 (alias: cluster). |
Example request
curl "https://bioq.bio/api/v1/domains?protein=1" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
domainsPfam accessions, or null when the protein is unannotatedno_annotationtrue when the protein is not in the Pfam tablenotedomains are not ranked by e-valueunitthe ids are proteins (MGYP accessions), not cluster representativesdiscovery_readiness.verdicttestable | weak | no_claim | absent | unknown, with n_assemblies and n_independent_runs
Example response
{
"cluster": 1,
"domains": [
"PF02566"
],
"n_domains": 1,
"unit": "protein (MGYP accession); the index is keyed by annotated protein, 2,985,942,516 of 5,739,566,171, not by the 1,661,142,385 cluster representatives",
"unresolved": [],
"no_annotation": false,
"note": "domains are listed in the source's row order, NOT ranked by e-value — the index keeps only (protein, domain) pairs, so a marginal hit and a certain one are indistinguishable",
"discovery_readiness": {
"verdict": "testable",
"claim_allowed": true,
"n_assemblies": 282,
"n_independent_runs": 59,
"min_assemblies_required": 3,
"why": "282 assemblies across 59 distinct runs: the minimum for a null contrast is met. The enrichment test on this engine is saturated, so only the depletion direction is valid"
}
}/api/v1/domain
List MGnify proteins that carry a given Pfam domain.
Returns MGnify protein ids carrying the domain, with the exact total (n_total). The key is named clusters but the entries are protein (MGYP) ids. Ids come in ascending order: this is neither a ranking by e-value nor a random sample. discovery_readiness is computed only over the returned page, so it is a lower bound (coverage: lower_bound). An accession that is not in Pfam answers 200 with an empty list and the reason in unresolved, not 404.
Parameters
| Name | Type | Description |
|---|---|---|
| accrequired | string | Pfam accession, e.g. PF00069 (bare digits are zero-padded). |
| limit | integer | Maximum ids to return; n_total is always exact. default: 20 |
Example request
curl "https://bioq.bio/api/v1/domain?acc=PF00069&limit=5" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
clustersMGnify protein ids (integers), ascendingn_totalexact number of proteins carrying the domainn_returned / truncatedpage size and whether more existnoteids are not ranked by e-valuediscovery_readinessverdict over the returned page only (coverage: lower_bound)unresolved[]set when the accession is unknown
Example response
{
"domain": "PF00069",
"clusters": [
1344,
14050,
21406,
22244,
23853
],
"unresolved": [],
"n_returned": 5,
"n_total": 6299635,
"truncated": true,
"unit": "protein (MGYP accession). The index is keyed by annotated protein, 2,985,942,516 of 5,739,566,171, not by the 1,661,142,385 cluster representatives",
"note": "ids are in ascending order, NOT ranked by e-value - the index keeps only (protein, domain) pairs, so a marginal hit and a certain one are indistinguishable here",
"discovery_readiness": {
"verdict": "testable",
"claim_allowed": true,
"n_assemblies": 36,
"n_independent_runs": 17,
"min_assemblies_required": 3,
"why": "36 assemblies across 17 distinct runs: the minimum for a null contrast is met. The enrichment test on this engine is saturated, so only the depletion direction is valid",
"coverage": "lower_bound",
"n_total_proteins": 6299635,
"coverage_note": "The assembly count is a lower bound over only the 5 returned proteins (of 6,299,635). They are the first ids in ascending order, not a sample, so do not estimate prevalence from it for large domains. The lower bound already meets the minimum, so reading more cannot overturn this verdict"
}
}/api/v1/structure
Look up an experimental PDB structure or an AlphaFold model and how far it can be trusted.
Accepts a PDB code (experimental) or a UniProt accession (AlphaFold model). evidence.kind says which, and the two are never comparable: an experimental entry carries resolution_a, a model carries mean_plddt and a quality_call (confident if mean pLDDT >= 70, low_confidence if >= 50, otherwise very_low_confidence). pLDDT is the model's own estimate, not a measurement error. Model entries usually carry no sequence (sequence_available false). A 404 means the loaded layer was searched and holds nothing for this id; the body gives the table sizes searched.
Parameters
| Name | Type | Description |
|---|---|---|
| id | string | PDB code (1AKE) or UniProt accession (P69905). |
| inventory | boolean | With no id, return the layer inventory instead. Pass 1 to enable. |
Example request
curl "https://bioq.bio/api/v1/structure?id=P69905" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
entry.evidence.kindexperimental | predicted; never compare across kindsentry.evidence.resolution_aexperimental only: resolution in Ångströmentry.evidence.mean_plddt / quality_call / quality_rulepredicted onlyentry.evidence.usable_for_claimsfalse for low-confidence modelsentry.sequence_length / sequence_availablesequence may be null for modelsentry.fetcharchive member name of the original coordinate file
Example response
{
"entry": {
"id": "P69905",
"evidence": {
"kind": "predicted",
"method": "ALPHAFOLD",
"model_version": "4",
"variant": "F1",
"mean_plddt": 98.05,
"frac_plddt_gt90": 0.993,
"frac_plddt_gt70": 0.993,
"quality_call": "confident",
"quality_rule": "confident if mean_plddt >= 70; low_confidence if >= 50; very_low_confidence below 50",
"usable_for_claims": true,
"comparable_with": "nothing experimental — pLDDT is a model's self-estimate, not a measurement error"
},
"title": null,
"sequence": null,
"sequence_length": 142,
"sequence_available": false,
"fetch": {
"tar": "swissprot_pdb_v4.tar",
"member": "AF-P69905-F1-model_v4.pdb.gz"
}
},
"elapsed_ms": 0.58
}/api/v1/organism
Count UniProt TrEMBL proteins for an NCBI taxonomy id, or list the largest organisms.
count is exact: every record in UniProt TrEMBL (unreviewed entries) was parsed once. accessions is a sample of up to 256, not the full set; accessions_are states how many of the total are shown. Swiss-Prot (reviewed) entries are not part of this count. With top instead of taxid, returns organisms ranked by protein count. A 404 means the taxid had zero records in the full pass — a real absence, not an unloaded layer.
Parameters
| Name | Type | Description |
|---|---|---|
| taxid | integer | NCBI taxonomy id, e.g. 9606. |
| top | integer | Without taxid: number of organisms to list, by protein count. Clamped to 1-1000. Ignored when taxid is given. default: 20 |
Example request
curl "https://bioq.bio/api/v1/organism?taxid=9606" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
countexact number of TrEMBL proteins for this taxidaccessionsa sample (sample_size of count), not the complete listtruncatedtrue when count exceeds the samplecount_is / accessions_areplain statements of what each number meanssource.bytes_scanned / bytes_totalproof the whole file was read- top mode: organisms[] with taxid, organism, count
Example response
{
"taxid": 9606,
"organism": "Homo sapiens",
"count": 190275,
"accessions": [
"Q29VG3",
"Q6VHB0",
"Q6ZWB5",
"U5ZAD9",
"A0A5C2G0E2"
],
"sample_size": 256,
"truncated": true,
"count_is": "exact — every record in the scanned TrEMBL object was parsed",
"accessions_are": "256 of 190,275 accessions — a sample, not the full set",
"source": {
"key": "uniprot_trembl.fasta.gz",
"bytes_scanned": 40628134612,
"bytes_total": 40628134612
}
}Genomes
/api/v1/dna
Fetch nucleotide bases for a region of the human genome (GRCh38) or a RefSeq sequence.
Coordinates are 1-based and inclusive: region=chr11:1000000-1000100 returns 101 bases. Human chromosome names are tried against GRCh38 first; any other name (e.g. NC_045512.2) is served from RefSeq nucleotide FASTA (archaea, fungi, mitochondrion, protozoa, viral, and mouse GRCm39). layer, assembly and division always say which genome the bases came from. Bare names like 1 exist in both human and mouse; ask for mouse as mm39:1. If end runs past the sequence the answer is clamped and marked truncated; a start past the end, or a name neither layer holds, is an error.
Parameters
| Name | Type | Description |
|---|---|---|
| regionrequired | string | name:start-end, 1-based inclusive (commas allowed in numbers). Examples: chr11:1000000-1000100, NC_045512.2:21563-21600, mm39:1:3000000-3000100. |
Example request
curl "https://bioq.bio/api/v1/dna?region=NC_045512.2:21563-21600" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
sequencethe bases as stored in the source FASTAstart / end / lengththe span actually returned (1-based, inclusive)layerhuman-grch38 or refseqassembly / division / organismwhich genome the bases are fromtruncated / returned_endpresent when end exceeded the sequence lengthsequence_lengthRefSeq only: full length of the sequence
Example response
{
"region": "NC_045512.2:21563-21600",
"chrom": "NC_045512.2",
"start": 21563,
"end": 21600,
"length": 38,
"sequence": "ATGTTTGTTTTTCTTGTTTTATTGCCACTAGTCTCTAG",
"elapsed_seconds": 0.0003,
"source": "RefSeq viral",
"layer": "refseq",
"assembly": "NC_045512.2",
"division": "viral",
"line_width": 80,
"sequence_length": 29903
}/api/v1/synteny
Show where an MGnify protein sits on assembled contigs and which proteins lie within n base pairs.
For each placement of the protein (assembly, contig, start, end, strand) the response lists neighbouring proteins on the same contig within the window, with the gap in base pairs. This is genomic context on one contig, a different claim from assembly-level co-occurrence (/cooccur) or curated interaction (/interaction). An empty neighbour list at the default 5 kb window is mostly a property of short metagenomic contigs (about 91% of placements have none), so re-ask with the suggested window before reading it as 'this protein is alone'. A 404 means the protein has no placement at all.
Parameters
| Name | Type | Description |
|---|---|---|
| proteinrequired | string | MGnify protein id, MGYP000187092429 or the bare integer (alias: id). |
| n | integer | Window in base pairs on each side. Values outside 0-1000000 are refused. default: 5000 |
Example request
curl "https://bioq.bio/api/v1/synteny?protein=MGYP000187092429&n=5000" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
placements[]erz (assembly accession), contig, start, end, strand (+1/-1), neighbours[]neighbours[]protein, start, end, strand, gap (bp)placements_total / truncatedat most 64 placements are returnedneighbour_totalneighbours across returned placementswindow_interpretationwhether an empty list is mostly contig sparsity, and a suggested window
Example response
{
"protein": "MGYP000187092429",
"n": 5000,
"placements": [
{
"erz": "ERZ22157947",
"contig": "ERZ22157947.1852784-NODE-1852814-length-597-cov-3.712177",
"start": 2,
"end": 595,
"strand": -1,
"neighbours": []
},
{
"erz": "ERZ22158282",
"contig": "ERZ22158282.115901-NODE-115905-length-597-cov-13.690037",
"start": 2,
"end": 595,
"strand": -1,
"neighbours": []
}
],
"placements_total": 38,
"neighbour_total": 0,
"truncated": false,
"window_interpretation": {
"empty_is_mostly_sparsity": true,
"measured": "gap p50 = 16,355 bp; 91.0% of placements have zero neighbours at n=5000 over 1,911,465 contigs",
"suggested_n_bp": 50000,
"note": "an empty list here is predominantly a property of short metagenomic contigs. Re-ask with the suggested window before reading it as 'this protein is alone'."
},
"ms": 268.337
}Metagenomes
/api/v1/biome
Count MGnify protein clusters by habitat (biome lineage), or list the biome vocabulary.
Counts are exact over all 1.66 billion MGnify clusters. clusters includes every deeper lineage under the matched one; clusters_exactly_at_this_lineage is the part assigned to that lineage itself. sample is a bounded sample of cluster ids, never the complete set. Biome membership overlaps (a cluster can be in several biomes), so never subtract counts to get 'in A and not in B'. An ambiguous name (soil matches two environments) or an unknown one answers 404 with options or candidates — a statement about the index vocabulary, not an empty biome. Without lineage, returns the vocabulary ranked by size.
Parameters
| Name | Type | Description |
|---|---|---|
| lineage | string | Biome lineage, e.g. root:Environmental:Terrestrial:Soil (case-insensitive; a unique suffix also works). Alias: biome. |
| limit | integer | Number of sample cluster ids to return. Clamped to 0-500. default: 50 |
| children | boolean | Include child biomes; 0 to omit. default: 1 |
| top | integer | Vocabulary mode (no lineage): number of biomes. Clamped to 1-500. default: 25 |
| max_depth | integer | Vocabulary mode: deepest lineage level listed. Clamped to 1-12. default: 3 |
Example request
curl "https://bioq.bio/api/v1/biome?lineage=root:Environmental:Terrestrial:Soil&limit=3" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
clustersclusters at this lineage and everything under it (exact)clusters_exactly_at_this_lineagethe disjoint part assigned to this lineage itselfmembersmember proteins in those clusterssamplea bounded sample of cluster ids (sample_is_complete is false)children[]child biomes with their own counts; they overlap, do not subtractcorpus.fraction_of_corpusshare of all clusters404 bodyambiguous, options[] (each with a ready query) or candidates[]
Example response
{
"requested": "root:Environmental:Terrestrial:Soil",
"matched": true,
"how": "exact",
"lineage": "root:environmental:terrestrial:soil",
"lineage_parts": [
"root",
"environmental",
"terrestrial",
"soil"
],
"depth": 4,
"clusters": 49877070,
"clusters_exactly_at_this_lineage": 25293143,
"members": 122487759,
"first_cluster_id": "MGYP000000000012",
"last_cluster_id": "MGYP011140254586",
"sample": [
"MGYP000000000012",
"MGYP011133358521",
"MGYP011134264257"
],
"sample_stride": null,
"sample_thinned": 6755,
"sample_bounded": false,
"sample_is_a_sample": true,
"sample_is_complete": false,
"corpus": {
"total_clusters": 1661142385,
"fraction_of_corpus": 0.030025764
},
"children": [
{
"prefix": "root:environmental:terrestrial:soil:tropical rainforest",
"name": "tropical rainforest",
"clusters": 8188087,
"clusters_exactly_at_this_lineage": 7730222
},
{
"prefix": "root:environmental:terrestrial:soil:forest soil",
"name": "forest soil",
"clusters": 7015265,
"clusters_exactly_at_this_lineage": 5925088
},
{
"prefix": "root:environmental:terrestrial:soil:agricultural",
"name": "agricultural",
"clusters": 5272596,
"clusters_exactly_at_this_lineage": 4538079
}
],
"children_sum_note": "children are DISJOINT subtrees of each other, but their sum can exceed `clusters` and that is not an error: 5.5% of clusters are in two or more top-level biomes at once (measured on 10,777,396 clusters), so biome membership OVERLAPS. Never derive 'in A and not in B', or 'everything else', by subtracting these numbers.",
"prefix_expansion": "lineage prefix expansion is APPLIED: `clusters` counts the matched prefix AND every deeper lineage under it, because the builder indexes every ancestor prefix of every lineage. A query for 'soil' therefore reaches 'soil:Permafrost' and 'soil:Sand'. `clusters_exactly_at_this_lineage` is the disjoint part — clusters whose own lineage is this prefix — so `clusters` >= `clusters_exactly_at_this_lineage` always, and the difference is the subtree.",
"elapsed_seconds": 0.000056
}/api/v1/unexplored
List large MGnify protein clusters with little or no Pfam annotation.
Returns annotation-poor clusters, optionally limited to a habitat, sorted by size (number of member sequences). Size is not a measure of how well a candidate is supported: each row's discovery_readiness counts the independent assemblies it was seen in, and most of these clusters sit in a single assembly. Gate on n_claimable, not on matched. pfam_poor describes annotation coverage, not function. Each row costs an index read, so keep top small.
Parameters
| Name | Type | Description |
|---|---|---|
| biome | string | Case-insensitive text matched inside each cluster's biome lineages, e.g. root:Environmental:Terrestrial:Soil or soil. |
| min_size | integer | Minimum member sequences per cluster. default: 5 |
| top | integer | Rows to return. default: 20 |
Example request
curl "https://bioq.bio/api/v1/unexplored?biome=root:Environmental:Terrestrial:Soil&top=5" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
matchedclusters passing min_size and biomen_claimablereturned rows whose readiness allows a claimresults[].sizemember sequences, not assembliesresults[].biomes / n_biomesevery biome lineage the cluster was seen inresults[].discovery_readinessverdict, claim_allowed, n_assemblies, n_independent_runsmeaning / readiness_notewhat pfam_poor and the ordering do and do not mean
Example response
{
"matched": 49158,
"n_returned": 5,
"truncated": true,
"meaning": "pfam_poor describes annotation coverage, not function",
"n_claimable": 0,
"readiness_note": "size is the number of member sequences in the cluster, not the number of assemblies. Each row's discovery_readiness says how many independent assemblies the candidate was actually observed in; the size ordering is not a ranking of claimability",
"results": [
{
"cluster": "MGYP003380827528",
"size": 2560,
"pfam_poor": true,
"n_biomes": 8,
"biomes": [
"root:Host-associated:Human:Digestive system:Large intestine:Fecal",
"root:Engineered:Biogas plant:Wet fermentation",
"root:Host-associated:Mammals:Respiratory system"
],
"discovery_readiness": {
"verdict": "no_claim",
"claim_allowed": false,
"n_assemblies": 1,
"n_independent_runs": 1,
"min_assemblies_required": 3,
"why": "One assembly: a single independent observation supports no association claim. The partner list is only neighbours inside the same sample"
}
},
{
"cluster": "MGYP003545442403",
"size": 989,
"pfam_poor": true,
"n_biomes": 8,
"biomes": [
"root:Mixed",
"root:Host-associated:Mammals:Digestive system:Fecal",
"root:Host-associated:Birds:Digestive system:Ceca"
],
"discovery_readiness": {
"verdict": "no_claim",
"claim_allowed": false,
"n_assemblies": 1,
"n_independent_runs": 1,
"min_assemblies_required": 3,
"why": "One assembly: a single independent observation supports no association claim. The partner list is only neighbours inside the same sample"
}
}
]
}/api/v1/unexplored_with_known
Pair annotation-poor clusters with the clusters they co-occur with across assemblies.
Takes the largest annotation-poor clusters matching the filters, keeps only those seen in at least two assemblies, and lists their co-occurring partners. A partner is a hypothesis about what an unexplored cluster does, not evidence. Each partner carries pair_readiness: pairs whose shared assemblies all come from one collection run are no_claim. The call examines at least 200 candidates and can take tens of seconds; if no candidate survives, meaning explains that this is the shape of the data.
Parameters
| Name | Type | Description |
|---|---|---|
| biome | string | Case-insensitive text matched inside biome lineages. |
| min_size | integer | Minimum member sequences per candidate cluster. default: 50 |
| limit | integer | Maximum pairs to return (the candidate pool is max(40 x limit, 200)). default: 10 |
Example request
curl "https://bioq.bio/api/v1/unexplored_with_known?biome=root:Environmental:Terrestrial:Soil&limit=2" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
pairs[].unexplored_cluster / size / n_assembliesthe candidate (size = member sequences)pairs[].partners[]cluster, n_both (shared assemblies), n_partner, id_distance, pair_readinesscandidates_matched / candidates_examined / survivorshow the pool was narrowedmin_assembliesassemblies a candidate needs to have partners (2)meaningpresent when no pair was found
Example response
{
"pairs": [
{
"unexplored_cluster": 1004231992,
"size": 304,
"size_meaning": "member sequences in this cluster (MGnify 'size'), not assembly count",
"n_assemblies": 9,
"n_biomes": 8,
"partners": [
{
"cluster": 1004938243,
"n_both": 9,
"n_partner": 9,
"id_distance": 706251,
"pair_readiness": {
"n_shared": 9,
"n_independent_runs": 1,
"verdict": "no_claim",
"claim_allowed": false,
"why": "All 9 assemblies come from the same collection run (ERZ724…): one sampling event, not independent replication"
}
},
{
"cluster": 1004937807,
"n_both": 9,
"n_partner": 32,
"id_distance": 705815,
"pair_readiness": {
"n_shared": 9,
"n_independent_runs": 1,
"verdict": "no_claim",
"claim_allowed": false,
"why": "All 9 assemblies come from the same collection run (ERZ724…): one sampling event, not independent replication"
}
}
],
"biomes": [
"root:Engineered:Wastewater",
"root:Environmental:Terrestrial:Soil:Agricultural",
"root:Host-associated:Fish:Digestive system"
]
}
],
"unresolved": [],
"candidates_examined": 200,
"candidates_matched": 1232,
"min_assemblies": 2,
"survivors": 2
}/api/v1/neighbourhood
Find MGnify proteins that appear in the same assemblies as a given one, with habitat attached.
Partners are scored by shared assemblies (n_both) and come from the queried protein's own index block, i.e. nearby ids. Partners whose shared assemblies span different collection runs (distant) are listed before those explained by one run (adjacent); pairs from one sampling run co-occur because they share genomes, not because they interact. Each partner has its own pair_readiness, and discovery_readiness covers the queried protein. An id absent from the release answers 200 with status other than FOUND, not 404.
Parameters
| Name | Type | Description |
|---|---|---|
| clusterrequired | string | MGnify protein id, MGYP… or the bare integer. |
| min_both | integer | Minimum shared assemblies for a partner. default: 3 |
| top | integer | Maximum partners to return. default: 20 |
Example request
curl "https://bioq.bio/api/v1/neighbourhood?cluster=1&top=5" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
partners[]cluster, n_both, n_partner, id_distance, pair_readinessstatusFOUND, or an absence status with meaningn_partners_found / n_distant / n_adjacentcounts before the top limitn_partners_claimablereturned partners whose pair_readiness allows a claimstrongest_distantbest partner not explained by one collection runbiomes / unresolvedhabitat of the queried protein, or why it is missing
Example response
{
"cluster": 1,
"partners": [
{
"cluster": 80,
"n_both": 234,
"n_partner": 1606,
"id_distance": 79,
"pair_readiness": {
"n_shared": 234,
"n_independent_runs": 55,
"verdict": "testable",
"claim_allowed": true,
"why": "234 assemblies across 55 distinct runs: the minimum for a null contrast is met. The enrichment test on this engine is saturated, so only the depletion direction is valid"
}
},
{
"cluster": 64,
"n_both": 230,
"n_partner": 1679,
"id_distance": 63,
"pair_readiness": {
"n_shared": 230,
"n_independent_runs": 56,
"verdict": "testable",
"claim_allowed": true,
"why": "230 assemblies across 56 distinct runs: the minimum for a null contrast is met. The enrichment test on this engine is saturated, so only the depletion direction is valid"
}
}
],
"unresolved": [
"no biome annotation for this cluster"
],
"discovery_readiness": {
"verdict": "testable",
"claim_allowed": true,
"n_assemblies": 282,
"n_independent_runs": 59,
"min_assemblies_required": 3,
"why": "282 assemblies across 59 distinct runs: the minimum for a null contrast is met. The enrichment test on this engine is saturated, so only the depletion direction is valid",
"detail": "The assembly list is the representative protein's, so it is a lower bound for multi-member clusters"
},
"status": "FOUND",
"n_assemblies": 282,
"n_partners_found": 3399,
"n_adjacent": 764,
"n_distant": 2635,
"n_partners_claimable": 5,
"partners_readiness_note": "Each partner's pair_readiness counts the independent runs among the assemblies the pair shares. However large n_both is, a pair that comes from one run was observed once",
"strongest_distant": {
"cluster": 80,
"n_both": 234,
"id_distance": 79,
"why_it_matters": "no single accession prefix accounts for half the shared assemblies, so one collection run does not explain this pair"
},
"biomes": [],
"note": "id_distance under 1,000,000 usually means the same sampling run; those pairs co-occur because they share genomes, not because they interact"
}/api/v1/cooccur
Count the assemblies an MGnify protein appears in, and its overlap with a second protein.
With only with, returns how many assemblies the protein was observed in and a sample of them. With without as well, returns the overlap (n_both), the assemblies unique to the first (n_only_with), and readiness for each side and for the pair; the weaker side bounds any claim. An observed/expected ratio is given only when you pass universe, the number of assemblies (universe_assemblies in the response), never the cluster count. Only the depletion direction is meaningful on this corpus; enrichment.quotable says whether the ratio may be quoted. Rows are representative proteins, so for a multi-member cluster the assembly list is a lower bound.
Parameters
| Name | Type | Description |
|---|---|---|
| withrequired | string | MGnify protein id (MGYP… or bare integer). |
| without | string | Second protein id for an overlap query. |
| universe | integer | Assembly count to use as the denominator of an observed/expected ratio (e.g. the universe_assemblies value). Non-integer values are ignored. |
Example request
curl "https://bioq.bio/api/v1/cooccur?with=1&without=80" \ -H "Authorization: Bearer $BIOQ_KEY"
Key fields
n_with / n_withoutassemblies each protein was observed inn_both / n_only_withshared and first-only assembliesdiscovery_readiness, pair_readiness, weakest_sideverdicts for each side and the pairenrichmentnull unless universe is given; then observed, expected, ratio, quotableuniverse_assembliesthe assembly count of the corpussample / sample_only_withfirst 20 assembly accessionsunitrows are proteins; assembly lists are lower bounds for clustersstatusWITHOUT_NOT_IN_RELEASE (still 200) when the second id is absent from the release
Example response
{
"with": 1,
"n_with": 282,
"unit": "protein (MGYP accession); for a multi-member cluster this is the representative's assembly list, a lower bound on the cluster's",
"universe_assemblies": 51274,
"discovery_readiness": {
"verdict": "testable",
"claim_allowed": true,
"n_assemblies": 282,
"n_independent_runs": 59,
"min_assemblies_required": 3,
"why": "282 assemblies across 59 distinct runs: the minimum for a null contrast is met. The enrichment test on this engine is saturated, so only the depletion direction is valid"
},
"without": 80,
"n_without": 1606,
"n_only_with": 48,
"n_both": 234,
"sample_only_with": [
"ERZ1026854",
"ERZ1026878",
"ERZ1030449"
],
"pair_readiness": {
"verdict": "testable",
"claim_allowed": true,
"n_shared": 234,
"n_independent_runs": 55,
"bounded_by": "testable",
"why": "234 assemblies across 55 distinct runs: the minimum for a null contrast is met. The enrichment test on this engine is saturated, so only the depletion direction is valid"
},
"weakest_side": {
"which": "with",
"verdict": "testable",
"claim_allowed": true,
"n_assemblies": 282,
"n_independent_runs": 59
},
"enrichment": null,
"enrichment_note": "pass &universe=<assembly count> for an observed/expected ratio; it is omitted rather than guessed because a wrong denominator makes the number meaningless",
"caveat": "a pair present in few assemblies is not evidence whatever the ratio",
"elapsed_ms": 3.6
}Agent
/api/v1/agent
Ask a research question in plain language; an agent queries the endpoints above and streams an answer in which every data claim cites a step (beta).
Beta. Send a JSON body; the response is a server-sent event stream. The agent plans, calls BioQ endpoints (in parallel where it can), and writes a markdown answer whose citations [n] refer to step n. Protein sequences in the question are replaced with handles such as [SEQ1] before the language model sees the text, and swapped back only when a step sends them to the BioQ engine. One request counts as one agent question; the queries the agent makes are not counted again. Up to 10 steps per question. Event payloads may change while in beta.
Parameters
| Name | Type | Description |
|---|---|---|
| questionrequired | string | JSON body field. The question, in plain language. May include pasted protein sequences or FASTA. |
| lang | string | JSON body field. Answer language: en, ko, ja, zh-CN, zh-TW, es, fr or de. default: en |
| history | array | JSON body field. Up to 3 previous turns as [{"q": question, "a": answer}] for follow-up questions. |
Example request
curl -N -X POST "https://bioq.bio/api/v1/agent" \
-H "Authorization: Bearer $BIOQ_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "Which lineages depend most on KRAS?", "lang": "en"}'Key fields
startrun_id, the question as the model sees it (masked_question), and the sequence handlesstepone engine call: n, tool, args, status (running, ok, error), and when finished http, ms, query and the full resultdeltaa chunk of text from the model for turn number turn; concatenate in orderturn_endkind is tools (the text so far was planning) or final (the text so far is the answer)donerun_id, steps, mserrormessage is agent_failed or timeout
Example response
{
"request": {
"question": "Which lineages depend most on KRAS?",
"lang": "en"
},
"content_type": "text/event-stream",
"stream": "event: start\ndata: {\"run_id\":\"a1b2c3d4e5\",\"masked_question\":\"Which lineages depend most on KRAS?\",\"sequences\":[]}\n\nevent: step\ndata: {\"n\":1,\"tool\":\"dependency\",\"args\":{\"gene\":\"KRAS\"},\"status\":\"running\"}\n\nevent: step\ndata: {\"n\":1,\"tool\":\"dependency\",\"args\":{\"gene\":\"KRAS\"},\"status\":\"ok\",\"http\":200,\"ms\":212,\"query\":\"/depmap?gene=KRAS\",\"result\":{\"…\":\"…\"}}\n\nevent: turn_end\ndata: {\"turn\":0,\"kind\":\"tools\",\"note\":\"\"}\n\nevent: delta\ndata: {\"turn\":1,\"text\":\"Pancreatic lines depend most on KRAS: 43 of 47 [1]…\"}\n\nevent: turn_end\ndata: {\"turn\":1,\"kind\":\"final\"}\n\nevent: done\ndata: {\"run_id\":\"a1b2c3d4e5\",\"steps\":1,\"ms\":9120}\n\n"
}Service
/api/v1/status
Check that the service is up and which data layers are loaded (no authentication).
Public health check. layers maps each data layer to true when it is loaded; an endpoint whose layer is false answers 503 with the reason rather than an empty result. counts gives headline sizes of the loaded datasets. Does not count against any quota.
Example request
curl "https://bioq.bio/api/v1/status"
Key fields
statusok when the gateway and engine are reachablelayersmap of layer name to loaded (true/false)countsheadline record counts for loaded datasets
Example response
{
"status": "ok",
"layers": {
"mgnify": true,
"trembl": true,
"structure": true,
"taxon": true,
"c2": true,
"dna": true,
"refseq": true,
"variants": true,
"unexplored": true,
"cooccur": true,
"pfam": true,
"pfam_reverse": true,
"homology": true,
"affinity": true,
"gdsc": false,
"string": true,
"biogrid": true,
"biome": true,
"seqhash": false,
"synteny": true,
"depmap": true,
"depmap_omics": true
},
"counts": {
"mgnify_clusters": 1661142385,
"pfam_annotated_proteins": 2985942516,
"trembl_accessions": 149430635,
"structures_experimental": 260621,
"structures_predicted": 542378,
"affinity_measurements": 5153526,
"string_edges": 162725618,
"biogrid_records": 2804534,
"synteny_placements": 177896636
}
}