OncoKB reader service: guide
Version 4.0.0. This page is served at /. Every field and error code: /reference. Try calls in a
browser: /docs. Machine-readable spec: /openapi.json.
In the examples $B is the base URL and $K is the API key. Ask the service owner for both.
Contents
- 1. What the service is and is not
- 2. Which call for which assay
- 3. Worked examples
- 4. How to send each kind of finding
- 5. What the answer means
- 6. Lab file pipelines
- 7. Accounts pacing and proxies
- 8. How little is transferred and why
- 9. Limits and caveats
- 10. Calling it from another server
1. What the service is and is not
- It reads oncokb.org with logged-in web accounts and returns OncoKB's evidence for a variant as JSON: oncogenicity, biological effect, therapeutic, diagnostic, prognostic and FDA levels, and the citations behind them.
- It makes the same JSON calls that oncokb.org's own pages make, with a plain HTTP client. The default backend has no browser.
- It takes single variants, whole samples, long lists, Ion Reporter TSV exports and GRCh38 VCFs, and it normalises the input before anything is sent to oncokb.org.
- It is not the licensed OncoKB API. It does not classify germline variants, and it does not call variants, copy number, fusions, TMB or MSI from raw data.
- It is for research use. Every answer carries
"research_only": true. Read section 9 before you build on it.
2. Which call for which assay
| You have | Call | Send | You get back |
|---|---|---|---|
| One variant: SNV or indel, fusion, copy-number call, TMB-High or MSI-High | POST /api/v1/annotate |
A gene with one of: alteration, fusion partner, copy-number type. Or genomic coordinates. Plus the tumour type | The answer, in the response |
| A targeted somatic panel: tens of genes, one sample | POST /api/v1/sample |
The sample's variants (up to 500 findings) and its tumour type | A summary for the report, one row per finding, and each finding's full answer |
| A comprehensive genomic profiling (CGP) panel: SNVs, indels, fusions, copy number, TMB, MSI | POST /api/v1/sample |
Variants, fusions, copy-number calls, biomarkers.tmb_high and biomarkers.msi_high, tumour type |
The same |
| A somatic exome (WES) or genome (WGS) VCF, GRCh38 | POST /api/v1/vcf with mode=somatic |
The VCF file, tumor_type, and your reporting thresholds min_vaf and min_depth |
A job, then a report (JSON or TSV): one row per coding call in an OncoKB gene |
| A germline exome or genome VCF, GRCh38 | POST /api/v1/vcf with mode=germline |
The VCF file | A job, then a report: the somatic annotation of each rare coding call and, for constitutional calls, OncoKB's germline annotation |
| Thermo Ion Reporter / Oncomine TSV exports | POST /api/v1/oncomine |
One or more .tsv exports and tumor_type |
A job, then a report per sample and a summary table |
| A long list of variants | POST /api/v1/jobs (up to 20,000), or POST /api/v1/annotate/batch (up to 100) |
{"items": [...]}; each item is an annotate body |
Job: progress, then results in pages of up to 500. Batch: every result in the response |
| A reference question | GET /api/v1/genes, GET /api/v1/genes/{symbol}?variants=true, GET /api/v1/info |
Nothing, or a gene symbol | The genes OncoKB annotates; one gene's curated alterations and levelled associations; the OncoKB data version in use |
Monitoring: GET /ready answers 200 when at least one account can carry a request and 503 with reasons otherwise.
GET /health always answers 200 while the process runs and shows accounts, traffic, the store and the data version.
Neither needs the key.
File uploads return a job. Poll GET /api/v1/jobs/{id} until status is done or failed, then fetch
GET /api/v1/jobs/{id}/report (JSON) or GET /api/v1/jobs/{id}/report.tsv. Delete the job when you have the report:
DELETE /api/v1/jobs/{id}. Jobs older than 30 days are removed in any case; the check runs when a job is submitted.
Pass tumor_type whenever the diagnosis is known. Without it the query is pan-cancer: see
tumour types.
3. Worked examples
The responses below were produced by the service from oncokb.org answers saved on 2026-11-10 (OncoKB data v7.7). Long
texts and lists are cut with ....
3.1 One variant
curl -s -X POST $B/api/v1/annotate -H "X-API-Key: $K" -H 'Content-Type: application/json' \
-d '{"gene":"IDH1","alteration":"p.Arg132Ser","tumor_type":"AML","client_ref":"S1-v1"}'
{"status": "annotated", "research_only": true, "client_ref": "S1-v1", "warnings": [], "errors": [],
"query": {"gene_input": "IDH1", "alteration_input": "p.Arg132Ser", "tumor_type_input": "AML",
"gene": "IDH1", "alteration": "R132S", "variant_type": "snv_indel", "fusion_genes": null,
"oncotree_code": "AML", "oncotree_name": "Acute Myeloid Leukemia", "reference_genome": "GRCh37"},
"gene": {"hugo_symbol": "IDH1", "gene_type": "ONCOGENE", "oncogene": true, "tumor_suppressor": false,
"entrez_gene_id": 3417, "gene_summary": "IDH1, a cell metabolism enzyme, is recurrently mutated in ...",
"background": "...",
"transcripts": {"grch37": {"isoform": "ENST00000345146", "refseq": "NM_005896.2"}, "grch38": "..."}},
"variant": {"canonical_alteration": "R132S", "oncokb_alteration": "R132S", "hotspot": true,
"curated_in_oncokb": true, "vus": false, "allele_exist": true, "vue": false,
"consequence": "missense_variant",
"protein_start": 132, "protein_end": 132, "last_evidence_update": "07/24/2026",
"variant_summary": "The IDH1 R132S mutation is likely oncogenic.",
"match": {"exact_row_present": true, "oncokb_alteration_curated": true, "basis": "exact"}},
"oncogenicity": {"status": "Likely Oncogenic"},
"mutation_effect": {"known_effect": "Switch-of-function", "description": "...",
"pmids": ["21760534", "29670690", "..."], "abstracts": []},
"evidence": {
"highest_sensitive_level": "Level 1", "highest_resistance_level": null,
"highest_diagnostic_level": "Level Dx2", "highest_prognostic_level": null, "highest_fda_level": "Level Fda2",
"oncokb_highest_levels": {"sensitive": "Level 1", "resistance": null, "diagnostic": "Level Dx2",
"prognostic": null, "fda": "Level Fda2"},
"other_tumor_type_levels": [{"level": "Level 3B", "cancer_type": "Myelodysplastic Syndromes"},
{"level": "Level Fda3", "cancer_type": "Myelodysplastic Syndromes"}],
"treatments": [
{"level": "Level 1", "tier": "standard_care", "regimen": "Ivosidenib", "drugs": ["Ivosidenib"],
"response": "sensitivity", "fda_level": "Level Fda2",
"matched_alterations": ["R132C", "R132H", "R132S", "R132G", "R132L"], "match_type": "exact",
"level_associated_cancer_types": ["Acute Myeloid Leukemia"], "excluded_cancer_types": [],
"in_query_tumor_type": true, "citation_count": 6, "pmids": ["29860938", "23393090", "23558169"],
"abstracts": [{"abstract": "Dohner et al. Abstract# 7042, ASCO 2022.", "link": "https://..."}, "..."],
"description": "Ivosidenib is a small molecule inhibitor of mutant IDH1 that is FDA-approved for ...",
"evidence_id": 26867, "approved_indications": [],
"drugs_detail": [{"name": "Ivosidenib", "ncit_code": "C114383"}]},
"..."],
"diagnostic": [{"level": "Level Dx2", "matched_alterations": ["Oncogenic Mutations"], "match_type": "generic",
"level_associated_cancer_types": ["Acute Myeloid Leukemia"], "in_query_tumor_type": true,
"citation_count": 7, "pmids": ["22397365", "..."], "...": "..."}],
"prognostic": [],
"fda": [{"level": "Level Fda2", "matched_alterations": ["R132S"],
"level_associated_cancer_types": ["Acute Myeloid Leukemia"], "excluded_cancer_types": [],
"in_query_tumor_type": true}, "..."],
"summaries": {"therapeutic": "The IDH1-targeted inhibitors ivosidenib and olutasidenib are FDA-approved for the treatment of patients with IDH1 R132-mutant acute myeloid leukemia.",
"diagnostic": "The presence of an IDH1 R132S mutation supports a diagnosis of acute myeloid leukemia.",
"prognostic": null, "fda": "..."}},
"provenance": {"source": "oncokb.org web API (variantAnnotation)",
"page_url": "https://www.oncokb.org/gene/IDH1/somatic/R132S/AML", "page_title": null,
"oncokb_data_version": "v7.7", "oncokb_last_data_update": "10/29/2026",
"parser_version": "4.0.0", "service_version": "4.0.0", "backend": "wire",
"cached": false, "cache_age_s": 0, "retrieved_at": "2026-11-10T09:39:32+00:00",
"wire_bytes": 6623, "account": "default", "cache_stored": true, "...": "..."},
"artifacts": {"pdf_available": false, "pdf_url": null}}
The same request again is answered from the store: provenance.cached is true, cache_age_s says how old the
stored answer is, and nothing is sent to oncokb.org.
3.2 A targeted somatic panel
One call for the whole sample. vaf is carried into the answer unchanged; it is not sent to oncokb.org.
curl -s -X POST $B/api/v1/sample -H "X-API-Key: $K" -H 'Content-Type: application/json' -d '{
"sample_id": "MY-17", "assay": "myeloid panel", "tumor_type": "AML", "options": {"detail": "summary"},
"variants": [{"gene": "NPM1", "alteration": "p.Trp288CysfsTer12", "vaf": 0.38},
{"gene": "DNMT3A", "alteration": "R882H", "vaf": 0.44},
{"gene": "NRAS", "alteration": "G12D", "vaf": 0.08}]}'
{"sample_id": "MY-17", "assay": "myeloid panel",
"tumor_type": {"input": "AML", "oncotree_code": "AML", "oncotree_name": "Acute Myeloid Leukemia"},
"research_only": true, "counts": {"annotated": 3},
"summary": {"actionable_findings": ["... the NPM1 row of findings ..."], "...": "..."},
"findings": [
{"index": 0, "client_ref": "0", "finding": "NPM1 W288Cfs*12", "status": "annotated", "oncogenicity": "Oncogenic",
"mutation_effect": "Switch-of-function", "hotspot": false, "pathogenicity": null,
"highest_sensitive_level": "Level 1", "highest_resistance_level": null, "highest_diagnostic_level": "Level Dx1",
"highest_prognostic_level": "Level Px1", "highest_fda_level": "Level Fda2", "vaf": 0.38, "errors": []},
{"index": 1, "client_ref": "1", "finding": "DNMT3A R882H", "status": "annotated", "oncogenicity": "Oncogenic",
"mutation_effect": "Loss-of-function", "hotspot": true, "highest_sensitive_level": null, "vaf": 0.44, "...": "..."},
{"index": 2, "client_ref": "2", "finding": "NRAS G12D", "status": "annotated", "oncogenicity": "Oncogenic",
"mutation_effect": "Gain-of-function", "hotspot": true, "highest_sensitive_level": null, "vaf": 0.08, "...": "..."}],
"results": ["... the full answer of each finding, in the order sent ..."],
"provenance": {"service_version": "4.0.0", "items": 3, "answered_from_store": 0, "wire_bytes": 11624,
"oncokb_data_version": "v7.7"}}
NRAS G12D is oncogenic but has no level written for AML, so its highest_sensitive_level is null. The two drugs
OncoKB lists for it in Erdheim-Chester Disease (Level 3B) appear under summary.therapies_in_other_tumor_types.
3.3 A CGP panel
Variants, a fusion, a copy-number call and TMB-High in one request. The findings are picked to show every kind of input; they are not a likely patient. The last variant is a DNA-only change, which the service does not accept, to show where an unread finding ends up.
curl -s -X POST $B/api/v1/sample -H "X-API-Key: $K" -H 'Content-Type: application/json' -d '{
"sample_id": "S-001", "assay": "CGP", "tumor_type": "AML", "options": {"detail": "summary"},
"variants": [
{"gene": "IDH1", "alteration": "p.Arg132Ser", "vaf": 0.41, "client_ref": "snv-1"},
{"gene": "FLT3", "alteration": "ITD", "client_ref": "itd"},
{"gene": "DNMT3A", "alteration": "R882H", "vaf": 0.44, "client_ref": "snv-2"},
{"gene": "CBFB", "fusion_partner": "MYH11", "client_ref": "fusion-1"},
{"gene": "CDKN2A", "copy_number_type": "Deletion", "client_ref": "cnv-1"},
{"gene": "TP53", "alteration": "c.743G>A", "client_ref": "dna-only"}],
"biomarkers": {"tmb_high": true, "msi_high": false}}'
The response has the same top-level keys as in 3.2. Below is its summary block, cut down to the rows that come from
IDH1, FLT3 and DNMT3A and to the first rows of each list. Rows from the fusion, the copy-number call and TMB-High are
left out.
"summary": {
"actionable_findings": [
{"index": 1, "client_ref": "itd", "finding": "FLT3 ITD", "status": "annotated", "oncogenicity": "Oncogenic",
"mutation_effect": "Gain-of-function", "hotspot": false, "pathogenicity": null,
"highest_sensitive_level": "Level 1", "highest_resistance_level": null, "highest_diagnostic_level": "Level Dx2",
"highest_prognostic_level": "Level Px1", "highest_fda_level": "Level Fda2", "vaf": null, "errors": []},
{"index": 0, "client_ref": "snv-1", "finding": "IDH1 R132S", "status": "annotated",
"oncogenicity": "Likely Oncogenic", "highest_sensitive_level": "Level 1", "vaf": 0.41, "...": "..."},
"..."],
"oncogenic_findings": ["IDH1 R132S", "FLT3 ITD", "DNMT3A R882H", "..."],
"therapies_in_tumor_type": [
{"regimen": "Gilteritinib", "level": "Level 1", "response": "sensitivity", "fda_level": "Level Fda2",
"finding": "FLT3 ITD",
"matched_alterations": ["D835", "Internal Tandem Duplication", "I836"],
"cancer_types": ["Acute Myeloid Leukemia"], "pmids": ["31665578", "28645776", "28516360", "38277619"]},
{"regimen": "Ivosidenib", "level": "Level 1", "response": "sensitivity", "fda_level": "Level Fda2",
"finding": "IDH1 R132S",
"matched_alterations": ["R132C", "R132H", "R132S", "R132G", "R132L"],
"cancer_types": ["Acute Myeloid Leukemia"], "pmids": ["29860938", "23393090", "23558169"]},
"..."],
"resistance_in_tumor_type": [],
"therapies_in_other_tumor_types": [
{"regimen": "Olutasidenib + Azacitidine Regimen", "level": "Level 3B", "response": "sensitivity",
"fda_level": "Level Fda3", "finding": "IDH1 R132S", "matched_alterations": ["Oncogenic Mutations"],
"cancer_types": ["Myelodysplastic Syndromes"], "pmids": ["40457853"]}],
"resistance_in_other_tumor_types": [],
"diagnostic": [{"level": "Level Dx2", "finding": "IDH1 R132S", "cancer_types": ["Acute Myeloid Leukemia"],
"pmids": ["22397365", "..."]}, "..."],
"prognostic": [{"level": "Level Px1", "finding": "FLT3 ITD", "cancer_types": ["Acute Myeloid Leukemia"],
"pmids": ["15746041", "22417203", "11585760"]}],
"not_read": [
{"index": 5, "client_ref": "dna-only", "finding": "dna-only", "status": "invalid", "oncogenicity": null,
"highest_sensitive_level": null, "vaf": null, "errors": ["NEEDS_PROTEIN_CHANGE"], "...": "..."}]}
How to read the summary:
| Key | What it holds | Use it for |
|---|---|---|
actionable_findings |
The findings rows that have a sensitivity or resistance level in the sample's tumour type. Best level first (1, 2, 3A, 3B, 4, R1, R2), then Oncogenic before Likely Oncogenic, then by name |
The lead table of a report |
oncogenic_findings |
Names of the findings OncoKB calls Oncogenic, Likely Oncogenic or Resistance, with or without a level | The list of driver findings |
therapies_in_tumor_type |
One row per drug regimen, level and finding, from treatment rows written for the sample's tumour type. Sensitivity only. Sorted by level, then regimen | Therapies with evidence in this tumour type |
resistance_in_tumor_type |
The same for resistance evidence (Level R1, R2) in the sample's tumour type | Drugs the findings argue against |
therapies_in_other_tumor_types |
Sensitivity rows that OncoKB wrote for a different tumour type; cancer_types says which |
Off-indication options. Never present these as evidence for this tumour type |
resistance_in_other_tumor_types |
Resistance rows written for a different tumour type | Context only |
diagnostic, prognostic |
Diagnostic and prognostic rows written for the sample's tumour type | Dx and Px statements |
not_read |
The findings rows whose status is invalid or error, with their error codes |
Findings your report must not treat as negative. Fix and resend, or retry |
findings has one compact row per finding in the order sent: variants first, then TMB-High, then MSI-High. results
holds the complete answer of each finding in the same order (the structure of 3.1). A finding without client_ref
gets its position as client_ref; the biomarkers get TMB-High and MSI-High. A fusion is named by its two partners, here
CBFB::MYH11 Fusion. A finding whose input the service rejected has no resolved name
and is named by its client_ref.
One bad finding never fails the sample: the call answers 200 and the finding is listed in not_read. This also holds
for a tumour type the service does not know: then every finding is invalid with UNKNOWN_TUMOR_TYPE, so check
counts and not_read before you use the summary.
3.4 A somatic exome or genome VCF
J=$(curl -s -X POST $B/api/v1/vcf -H "X-API-Key: $K" -F "file=@tumour.hard-filtered.vcf.gz" \
-F mode=somatic -F tumor_type=MEL -F min_vaf=0.05 -F min_depth=50 \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["job_id"])')
until curl -s $B/api/v1/jobs/$J -H "X-API-Key: $K" | grep -qE '"status": ?"(done|failed)"'; do sleep 10; done
curl -s $B/api/v1/jobs/$J/report -H "X-API-Key: $K" -o report.json
curl -s $B/api/v1/jobs/$J/report.tsv -H "X-API-Key: $K" -o report.tsv
curl -s -X DELETE $B/api/v1/jobs/$J -H "X-API-Key: $K"
The upload answers at once:
{"job_id": "8f2c1a0d5b7e4c31", "status": "running", "poll": "/api/v1/jobs/8f2c1a0d5b7e4c31",
"report": "/api/v1/jobs/8f2c1a0d5b7e4c31/report", "tsv": "/api/v1/jobs/8f2c1a0d5b7e4c31/report.tsv"}
One row of calls[] in the report, for a BRAF V600E call (vaf, depth and genotype come from the VCF):
{"origin": "somatic", "gene": "BRAF", "chrom": "7", "pos": 140753336, "ref": "A", "alt": "T",
"mane": "NM_004333.6", "consequence": "missense", "protein_mane": "V600E",
"genotype": "0/1", "vaf": 0.31, "depth": 240,
"gnomad_class": "rare", "gnomad_drop": false, "gnomad_filtering_af": 1.09798e-05, "gnomad_af": 1.23999e-06,
"gnomad_grpmax": "sas",
"gnomad_nhomalt": 0, "gnomad_filter": "PASS",
"status": "annotated", "oncokb_gene": "BRAF", "oncokb_alteration": "V600E", "oncogenicity": "Oncogenic",
"mutation_effect": "Gain-of-function", "hotspot": true,
"highest_sensitive": "Level 1", "highest_resistance": null, "highest_diagnostic": null, "highest_prognostic": null,
"levels_any_tumor": ["Level 1", "Level 3B"], "errors": [], "warnings": [],
"page": "https://www.oncokb.org/gene/BRAF/somatic/V600E/MEL?refGenome=GRCh38"}
Add ?full=true to the report URL to get the complete OncoKB answer of each call as oncokb_full. What the pipeline
keeps, drops and does not read is in section 6.
3.5 A germline exome or genome VCF
curl -s -X POST $B/api/v1/vcf -H "X-API-Key: $K" -F "file=@sample.hard-filtered.vcf.gz" -F mode=germline
Poll and fetch the report as in 3.4. Each row has the same columns, with origin set to germline or mosaic. For
constitutional calls the row also carries OncoKB's germline annotation: germline_status, germline_pathogenicity,
germline_penetrance and germline_inheritance. Read the germline caveat before using either.
3.6 Ion Reporter and Oncomine TSV exports
curl -s -X POST $B/api/v1/oncomine -H "X-API-Key: $K" \
-F "files=@011026_1_selected_variants.tsv" -F "files=@011026_2_selected_variants.tsv" -F "tumor_type=AML"
The answer is a job, as in 3.4. GET /api/v1/jobs/{id}/report returns samples[] (one report per file, with
variants[] and biomarkers[]) and summary[] (one row per kept variant). report.tsv holds the summary rows with
the columns sample, assay, label, vaf, status, oncokb_gene, oncokb_alteration, oncogenicity,
mutation_effect, hotspot, highest_sensitive, highest_resistance, highest_diagnostic, highest_prognostic,
levels_any_tumor, errors, flags.
3.7 A long list
curl -s -X POST $B/api/v1/jobs -H "X-API-Key: $K" -H 'Content-Type: application/json' -d '{"items": [
{"gene": "IDH1", "alteration": "R132S", "tumor_type": "AML", "client_ref": "row-1"},
{"gene": "KRAS", "alteration": "G12S", "tumor_type": "AML", "client_ref": "row-2"},
{"gene": "TTN", "alteration": "A100T", "client_ref": "row-3"}]}'
{"job_id": "2abc82b6c57144ef", "status": "running", "total": 3, "poll": "/api/v1/jobs/2abc82b6c57144ef"}
curl -s "$B/api/v1/jobs/2abc82b6c57144ef" -H "X-API-Key: $K" # progress
curl -s "$B/api/v1/jobs/2abc82b6c57144ef?offset=0&limit=500" -H "X-API-Key: $K" # results, 500 per page
{"id": "2abc82b6c57144ef", "kind": "variants", "status": "done", "total": 3, "done": 3,
"counts": {"annotated": 2, "not_found": 1},
"submitted_at": "2026-11-10T09:48:14+00:00", "finished_at": "2026-11-10T09:48:14+00:00",
"offset": 0, "results": ["... one answer per item, in the order sent; an item not done yet is null ..."]}
TTN is not a gene OncoKB annotates, so row-3 is not_found with GENE_NOT_ANNOTATED_BY_ONCOKB and no request went
to oncokb.org for it. For up to 100 items, POST /api/v1/annotate/batch takes the same body and answers
{"counts": {...}, "results": [...]} in the response. In both, each item has its own status and one bad item never
fails the rest.
3.8 Reference lookups
curl -s $B/api/v1/info -H "X-API-Key: $K"
curl -s $B/api/v1/genes -H "X-API-Key: $K"
curl -s "$B/api/v1/genes/BRAF?variants=true" -H "X-API-Key: $K"
/api/v1/info:
{"service_version": "4.0.0", "parser_version": "4.0.0", "backend": "wire",
"oncokb": {"data_version": "v7.7", "data_date": "10/29/2026", "...": "..."},
"cancer_gene_list": {"date": "10/29/2026 (v7.7)", "source": "oncokb.org cancerGeneList (live)",
"annotated_genes": 1040, "...": "..."}}
/api/v1/genes answers {"source", "list_date", "count", "genes": [...]}. genes lists the genes OncoKB annotates
(1,040 in data v7.7), sorted by symbol, each with hugo_symbol, entrez_gene_id, gene_type, oncokb_annotated,
grch37_isoform, grch38_isoform, msk_impact and msk_heme. Add ?annotated_only=false for the whole Cancer Gene
List (1,247 genes). No request goes to oncokb.org for this call. Until the service has read the list from oncokb.org,
the answer comes from a bundled snapshot and holds hugo_symbol and oncokb_annotated only.
/api/v1/genes/BRAF?variants=true:
{"hugo_symbol": "BRAF", "entrez_gene_id": 673, "gene_type": "ONCOGENE", "oncokb_annotated": true,
"aliases": ["BRAF1"],
"transcripts": {"grch37": {"isoform": "ENST00000288602", "refseq": "NM_004333.4"},
"grch38": {"isoform": "ENST00000646891", "refseq": "NM_004333.4"}},
"in_panels": {"msk_impact": true, "msk_heme": true, "foundation_one": true, "foundation_heme": true,
"vogelstein": true, "sanger_cgc": true},
"annotated_alterations": [
{"alteration": "K601N", "oncogenicity": "Oncogenic", "mutation_effect": "Gain-of-function",
"pmids": ["26732095", "..."], "description": "The class II BRAF K601N mutation is located in ..."},
"... 205 alterations in all ..."],
"clinical_alterations": [
{"alteration": "V600", "level": "Level 3A", "fda_level": "Level Fda3", "drugs": ["Vemurafenib", "Dabrafenib"],
"cancer_types": ["Histiocytosis"], "excluded_cancer_types": [], "pmids": ["31213430", "..."],
"description": "Vemurafenib is an orally available, small molecule RAF-targeted inhibitor ..."},
"... 52 associations in all ..."],
"provenance": {"source": "oncokb.org cancerGeneList (live)", "oncokb_data_version": "v7.7",
"page_url": "https://www.oncokb.org/gene/BRAF", "cached": false, "wire_bytes": 52307}}
Without variants=true the two tables are left out and nothing is downloaded. With it, the tables cost two requests
per gene and OncoKB data version, and are then served from the store. A gene that is in the list but that OncoKB does
not annotate has no tables. A symbol that is not in the Cancer Gene List gets 404 GENE_NOT_IN_CANCER_GENE_LIST; so
does every symbol until the service has read the list from oncokb.org.
4. How to send each kind of finding
Every finding is an annotate body. POST /api/v1/annotate takes one; /sample, /annotate/batch and /jobs take
many. Unknown fields are rejected, and no field has a clinical default.
| Field | Meaning |
|---|---|
gene |
HGNC symbol. Other Biomarkers for TMB and MSI. A::B for a fusion |
alteration |
Protein change or named alteration |
fusion_partner |
The 3' partner, when gene is the 5' partner |
copy_number_type |
Amplification or Deletion |
genomic_coordinates |
A VCF-style position, instead of the three fields above |
reference_genome |
GRCh37 (default) or GRCh38: the transcript set a protein change is read on |
germline |
true asks OncoKB's germline annotation instead of the somatic one |
tumor_type |
OncoTree code or name. In /sample it is set once for the whole sample |
variant_type |
Optional check: snv_indel, fusion, cnv, biomarker, special. A contradiction is rejected |
client_ref |
Up to 128 characters, returned unchanged. Use it to match answers to your rows |
options.detail |
summary, standard (default) or full |
options.force_refresh |
true skips the store and asks oncokb.org again |
options.generate_pdf |
Browser backend only. The default backend answers with a warning instead |
detail, force_refresh and generate_pdf may also be sent at the top level of an annotate body. In /sample,
options is set once and applies to every finding. GET /api/v1/annotate takes the same fields as query parameters,
except genomic_coordinates and client_ref.
Protein changes
| You send | It is asked as |
|---|---|
R132S, p.Arg132Ser, c.394C>A:p.Arg132Ser, IDH1 R132S |
R132S |
p.Trp288CysfsTer12 |
W288Cfs*12 |
R196Ter, R196X |
R196* |
p.Glu746_Ala750del |
E746_A750del |
X541_splice |
X541_splice |
EGFR exon 19 deletion, ex19del, del19 |
Exon 19 in-frame deletions |
EGFR exon 20 insertion, ex20ins |
Exon 20 in-frame insertions |
EGFR vIII |
vIII |
MET exon 14 skipping, METex14 |
Exon 14 splice mutations |
TERT promoter, C228T, C250T, c.-124C>T, c.-146C>T |
Promoter Mutation |
FLT3 ITD, KMT2A PTD, AR V7 |
ITD, PTD, V7 |
Oncogenic Mutations, Truncating Mutations |
The same group entry |
Gene aliases are mapped: HER2 and NEU to ERBB2, MLL to KMT2A, C-KIT to KIT, C-MET to MET.
Rejected before anything is sent (HTTP 422):
| You send | Code | Send instead |
|---|---|---|
A DNA-only change, c.394C>A |
NEEDS_PROTEIN_CHANGE |
The protein change, or genomic coordinates |
p.? (splice site, start loss) |
NEEDS_GENOMIC_CHANGE |
Genomic coordinates |
A synonymous change, p.=, R132= |
NOT_PROTEIN_ALTERING |
Nothing: filter these out |
FLT3 TKD, KIT exon 11, NPM1 mutation |
AMBIGUOUS_ALTERATION |
The exact protein change |
| Something that is not a protein change | INVALID_PROTEIN_CHANGE |
A corrected value |
reference_genome matters when your protein change was called on a GRCh38 annotation such as MANE Select. OncoKB
reads a protein change on its own transcript of the genome you name; the default is GRCh37, as on oncokb.org. hg19,
b37 and hg38 are accepted spellings. Anything else is 422 INVALID_REFERENCE_GENOME. Answers for the two genomes
are stored separately, and query.reference_genome in every answer says which one was used.
When your transcript differs from OncoKB's, OncoKB says so in one of two ways. ONCOKB_REJECTED_ALTERATION (HTTP 422)
means the reference amino acid does not match OncoKB's transcript. ALTERATION_ON_OTHER_TRANSCRIPT (HTTP 200,
not_found) means OncoKB knows the change under another name, which it puts in errors[].suggestions. In both cases
send the genomic coordinates and let OncoKB do the mapping. The lab file pipelines do this retry for you; single calls
do not.
Genomic coordinates
{"gene": "BRAF", "tumor_type": "MEL",
"genomic_coordinates": {"chromosome": "7", "position": 140753336, "reference": "A", "alternate": "T",
"genome_build": "GRCh38"}}
| Field | Meaning |
|---|---|
chromosome |
1 to 22, X, Y, MT. A chr prefix is accepted |
position |
VCF POS, 1-based |
reference, alternate |
VCF REF and one ALT allele. A, C, G, T only; no symbolic alleles such as <DEL> |
genome_build |
Required: GRCh37 or GRCh38 (hg19, b37, hg38 accepted) |
Use coordinates for VCF lines, indels, splice sites and start loss. OncoKB maps the change onto its own transcript and
annotates it in the same request. The answer's query.gene and query.alteration are what OncoKB resolved (here
BRAF and V600E). query.genomic_change is the change as it was sent to OncoKB
({"change": "7,140753336,140753336,A,T", "build": "GRCh38"}), query.reference_genome is the build, and
provenance.resolved_via is the oncokb.org page for the genomic change.
gene is optional here and only a hint. With it, a call in a gene OncoKB does not annotate is answered locally, and a
gene that differs from the one OncoKB resolved adds the warning gene_hint_differs:A->B. A change that OncoKB cannot
place on a protein (intronic, intergenic) is not_found with GENOMIC_CHANGE_NOT_ANNOTATED. Coordinates cannot be
combined with alteration, fusion_partner or copy_number_type (422 AMBIGUOUS_INPUT).
Fusions
Three equivalent forms, always 5' partner first:
{"gene": "BCR", "fusion_partner": "ABL1"}
{"gene": "BCR::ABL1"}
{"gene": "ABL1", "alteration": "BCR-ABL1 Fusion"}
The service asks OncoKB for BCR::ABL1 Fusion under the 3' partner first, then under the 5' partner, and returns the
entry that curates this exact fusion. An immunoglobulin partner (IGH, IGK, IGL) is not asked. gene.hugo_symbol in
the answer says whose entry was used. If neither partner is a gene OncoKB annotates, the answer is not_found without
a request.
When only one partner is known, ask for that gene's group entry: {"gene": "ALK", "alteration": "Fusions"}
(rearrangement and translocation mean the same). A fusion and another alteration of the same gene are two
findings: fusion_partner together with an unrelated alteration is 422 VARIANT_TYPE_CONFLICT.
Copy number
{"gene": "ERBB2", "copy_number_type": "Amplification"}
OncoKB has two copy-number alterations: Amplification and Deletion. Deletion means a homozygous (deep) loss.
amp, amplified, homozygous deletion, deep deletion and homdel are accepted. gain, loss, del and
hemizygous deletion are rejected with 422 NEEDS_CNV_CALL, because the service does not guess which of the two you
mean. Apply your own copy-number thresholds first.
TMB-High and MSI-High
{"gene": "Other Biomarkers", "alteration": "TMB-High"}
{"gene": "Other Biomarkers", "alteration": "MSI-High"}
In /sample, send "biomarkers": {"tmb_high": true, "msi_high": true}; a value of false or a missing key sends
nothing. TMB-H and MSI-H are accepted. The service does not compute or threshold TMB or MSI: send the finding only
when your assay calls it High. HRD, LOH and mutational signatures are not OncoKB biomarkers (422
UNSUPPORTED_BIOMARKER).
Germline findings
Add "germline": true to a finding to ask OncoKB's germline annotation for it. OncoKB names germline variants by
their cDNA change and matches the name exactly, so there are three ways to ask, best first:
| You send | What happens |
|---|---|
genomic_coordinates |
OncoKB maps the change itself. query.alteration in the answer is OncoKB's cDNA name, for example c.181T>G |
A cDNA name, "alteration": "c.181T>G" |
Passed on as it is. This form is accepted only with germline: true; without it a c. change is 422 NEEDS_PROTEIN_CHANGE |
A protein change, "alteration": "C61G" |
Normalised and passed on, with the warning germline_lookup_by_protein_change:oncokb_names_germline_variants_by_cdna. It matches only if OncoKB lists the variant under that name |
What comes back is described in section 5.
Tumour types
tumor_type takes an OncoTree code (AML, NSCLC, COADREAD) or name (Acute Myeloid Leukemia, Melanoma). Case
does not matter, British spellings (leukaemia, tumour) are understood, and common shorthand is mapped: CRC,
CLL, MDS/MPN, multiple myeloma, breast cancer, GBM. The list is closed (OncoTree 2025-10-03 plus legacy
codes): free text is never passed through. An unknown value is 422 UNKNOWN_TUMOR_TYPE with close matches in
errors[].suggestions.
Leaving tumor_type out is an explicit pan-cancer query. The answer carries the warning
tumor_type_missing:pan_cancer_query. Compared with a query for a tumour type:
- Oncogenicity and biological effect are the same.
- Every evidence row OncoKB has for the alteration is listed, for all tumour types, and
in_query_tumor_typeistrueonly for rows written for all tumours (All Tumors, All Solid Tumors, All Liquid Tumors, All Pediatric Tumors). highest_*_leveltherefore staysnullfor most alterations, andsummariesarenull. Useother_tumor_type_levelsandoncokb_highest_levelsto see what exists.
With a tumour type, pass the actual diagnosis. OncoKB decides which rows apply to it, and the service follows OncoKB
(see in_query_tumor_type in section 5). A level written for another tumour type does not count: NRAS G12D
with AML has no sensitivity level, although OncoKB has a Level 3B row for it in Erdheim-Chester Disease. When the
diagnosis is not settled, leave tumor_type out.
The detail option
detail |
What is left out |
|---|---|
full |
Nothing. evidence.articles lists every cited article with title, journal, date and authors |
standard (default) |
evidence.articles |
summary |
Also the long texts: description, abstracts, drugs_detail, approved_indications and evidence_id of each row, the mutation effect description and abstracts, and gene.background. Levels, drugs, cancer types and PMIDs stay |
summary is the right choice for report tables and for /sample. The store keeps oncokb.org's own JSON, so asking for
another detail level later costs no new download.
5. What the answer means
Status
A 200 does not mean OncoKB curated the variant. Read status.
status |
HTTP | Meaning |
|---|---|---|
annotated |
200 | OncoKB has an oncogenicity call for the alteration (somatic), or a pathogenicity entry (germline) |
not_curated |
200 | The gene is in OncoKB, this alteration is not: "Unknown Oncogenic Effect". The gene summary is still returned |
not_found |
200 | OncoKB has nothing to look up: the gene is not annotated, the change is on another transcript, or a genomic change maps to no protein change. errors[].code says which |
invalid |
422 | The input was rejected, by this service or by OncoKB. Do not resend it unchanged |
error |
503 | oncokb.org could not be read. errors[].retryable says whether trying again can help |
In /sample, /annotate/batch and /jobs the call answers 200 and each item carries its own status.
Only annotated and not_curated answers are sure to carry the gene, variant, oncogenicity, mutation_effect
and evidence blocks. A not_found answered from the gene list, a not_found for a genomic change OncoKB could not
map, an invalid input and an error from a failed download have none of them. Read those blocks with a default.
Oncogenicity and biological effect
oncogenicity.status is OncoKB's call: Oncogenic, Likely Oncogenic, Likely Neutral, Inconclusive or
Resistance. For not_curated it is Unknown Oncogenic Effect. It is null for every other status and for germline
answers.
mutation_effect.known_effect is Gain-of-function, Loss-of-function, Switch-of-function, Neutral, each also
with a Likely prefix, or Inconclusive. It is null when OncoKB says Unknown.
variant.hotspot, variant.vus and variant.curated_in_oncokb come from the same OncoKB answer.
curated_in_oncokb: false with status: annotated means OncoKB has no entry of its own for this exact alteration
and derived the call from a broader one, for example Truncating Mutations.
Levels
Each row of evidence.treatments, diagnostic, prognostic and fda is one statement by OncoKB: a level, the
alterations it is written for and the cancer types it is written for.
| Level | OncoKB's definition |
|---|---|
Level 1 |
FDA-recognized biomarker predictive of response to an FDA-approved drug in this indication |
Level 2 |
Standard care biomarker recommended by the NCCN or other expert panels predictive of response to an FDA-approved drug in this indication |
Level 3A |
Compelling clinical evidence supports the biomarker as being predictive of response to a drug in this indication |
Level 3B |
Standard care or investigational biomarker predictive of response to an FDA-approved or investigational drug in another indication |
Level 4 |
Compelling biological evidence supports the biomarker as being predictive of response to a drug |
Level R1 |
Standard care biomarker predictive of resistance to an FDA-approved drug in this indication |
Level R2 |
Compelling clinical evidence supports the biomarker as being predictive of resistance to a drug |
Level Dx1 to Dx3 |
Diagnostic: required for diagnosis, supports diagnosis, may assist diagnosis |
Level Px1 to Px3 |
Prognostic, by strength of the studies |
Level Fda1 to Fda3 |
FDA: companion diagnostics, evidence of clinical significance, potential clinical significance |
Fields that tell you which tumour type a level belongs to:
| Field | Meaning |
|---|---|
level_associated_cancer_types |
The cancer types OncoKB wrote this row for |
excluded_cancer_types |
Cancer types OncoKB excludes from the row |
in_query_tumor_type |
true when the row applies to the tumour type you asked about. With a tumour type, OncoKB's own verdict decides: the service does not overrule it with its OncoTree copy. Without one, only rows written for all tumours are true |
evidence.highest_sensitive_level, highest_resistance_level, highest_diagnostic_level, highest_prognostic_level, highest_fda_level |
The best level among the rows with in_query_tumor_type: true. Computed by the service from the rows it returns |
evidence.other_tumor_type_levels |
{level, cancer_type} for every row that does not apply to your tumour type |
evidence.oncokb_highest_levels |
OncoKB's own highest levels for the query (sensitive, resistance, diagnostic, prognostic, fda), copied from its answer |
highest_* and oncokb_highest_levels can differ, and the difference is the point. highest_* only counts evidence
written for your tumour type. OncoKB's own value also counts evidence from other indications. Two examples from data
v7.7:
- NRAS G12D with
AML:highest_sensitive_levelisnull;oncokb_highest_levels.sensitiveisLevel 3B, from a row written for Erdheim-Chester Disease. - BCR::ABL1 without a tumour type: every
highest_*isnull;oncokb_highest_levels.sensitiveisLevel 1.
Use highest_* to answer "is there evidence in this tumour type". Use oncokb_highest_levels to show what OncoKB
itself reports.
OncoKB applies a diagnostic or prognostic entry that is filed under a subtype to the parent tumour type you asked
about, and the service follows it. NPM1 W288Cfs*12 with AML has highest_diagnostic_level: Level Dx1 from a row
whose cancer type reads AML with Mutated NPM1, with in_query_tumor_type: true.
Treatment rows also carry tier (standard_care for Levels 1, 2 and R1; investigational for 3A, 3B, 4 and R2),
response (sensitivity or resistance), regimen with its drugs, and the fda_level of the same evidence.
Row order follows oncokb.org. With a tumour type, each drug regimen is listed once at its highest level, standard care first, then investigational, each by level and then drug name. Without one, every row is listed, by level, alteration, cancer type and drug.
match_type
A row can apply to your alteration in three ways. match_type says which.
match_type |
The row lists | Example |
|---|---|---|
exact |
Your alteration, by its name or by OncoKB's curated name for it | IDH1 R132S in a row for R132C, R132H, R132S, ...; FLT3 ITD in a row for Internal Tandem Duplication; BCR-ABL1 in a row for BCR::ABL1 Fusion |
positional |
The residue of your alteration | BRAF V600E in a row for V600 |
generic |
A group your alteration belongs to | Oncogenic Mutations, Truncating Mutations, Fusions |
A Level 1 that is reached through a generic row is weaker evidence for your exact variant than the level alone
suggests. Show matched_alterations next to the level. FDA rows have no match_type.
variant.match sums this up for the whole answer: exact_row_present (some row is exact),
oncokb_alteration_curated (OncoKB has an entry for this alteration) and basis (exact when either is true,
otherwise generic; null unless the status is annotated).
Warnings
Warnings do not change status. Some carry a detail after a colon.
| Warning | Meaning |
|---|---|
tumor_type_missing:pan_cancer_query |
No tumor_type was sent; levels are not tumour-specific |
alteration_normalised_by_oncokb:X |
OncoKB rewrote the protein change to X before annotating it |
alteration_not_validated:X |
OncoKB did not validate the protein change; X is the reason it gives |
tumor_type_not_echoed_by_oncokb |
OncoKB's answer does not name the tumour type that was sent |
unrecognised_biological_effect:X |
OncoKB returned an effect outside the known list; known_effect is null |
fusion_partner_not_in_oncokb:GENE |
No entry curates the fusion, and OncoKB has nothing for this partner |
gene_hint_differs:A->B |
OncoKB mapped the coordinates to gene B, not to the gene you sent |
germline_lookup_by_protein_change:... |
A germline lookup was made by protein change. OncoKB names germline variants by cDNA change, so not_curated may only mean that the name did not match |
pdf_not_available:no_browser_in_this_backend |
generate_pdf was asked of the default backend |
oncomine_transcript_differs:A->B |
Lab file reports: an Oncomine protein change was asked again by position |
mane_transcript_differs_from_oncokb |
Lab file reports: a MANE protein change was asked again by position |
Germline
Germline caveat. OncoKB's somatic annotation describes what a variant does in a tumour. For a variant found in a germline genome it means "would be oncogenic if somatic", not "inherited cancer risk". Germline classification belongs to ClinVar and ACMG review.
"germline": true asks OncoKB's separate germline annotation instead. The answer has the same structure, with these
differences:
- A
germlineblock:pathogenicity(OncoKB's value, for exampleLikely Pathogenic),penetrance, andgenomic_indicators[], each withname,descriptionandinheritance_mechanism(for exampleAUTOSOMAL_DOMINANT). oncogenicity.statusisnull.statusisannotatedwhen OncoKB has a pathogenicity or an entry for the variant, otherwisenot_curated.provenance.sourcenames the germline annotation andpage_urlis the germline page.query.alterationis OncoKB's cDNA name when you asked by coordinates or by cDNA name.- In
/sample, the finding's row haspathogenicity.
OncoKB's germline content is narrow. Its own text for a variant it does not hold reads: "OncoKB germline annotation is
limited to pathogenic and likely pathogenic germline variants identified in patients sequenced at MSK." So
not_curated with an empty germline block is a common answer, and it says nothing about the variant. Ask by
coordinates or by cDNA name where you can: a lookup by protein change can miss a variant that OncoKB lists under its
cDNA name. The service adds no classification of its own.
6. Lab file pipelines
| Oncomine Myeloid | Oncomine Comprehensive Plus (CGP 500), Precision | Illumina WGS / WES (DRAGEN) | |
|---|---|---|---|
| Sample | Blood or marrow, tumour | Solid tumour (FFPE) | Usually blood (constitutional), or tumour |
| What the assay calls | Hotspots and full genes from DNA; fusions, FLT3-ITD and KMT2A-PTD from RNA | About 500 genes: SNV, indel, CNV, fusions, TMB, MSI | Every variant in the exome or genome |
| File | Ion Reporter TSV, hg19, protein change given | The same, with TMB and MSI in the header | VCF, GRCh38, no annotation |
| Main problem | Full exports list every hotspot position, including absent ones | The same, and CNV classes looser than OncoKB's | Millions of calls, no protein change, mostly common polymorphisms |
| What the pipeline does | Thermo-like filter, transcript retry | The same, plus CNV thresholds and TMB / MSI | MANE consequence, gnomAD filter, OncoKB genes only |
6.1 Ion Reporter TSV: POST /api/v1/oncomine
Upload one or more .tsv exports (multipart field files, repeatable; .txt is accepted) and optionally
tumor_type, which applies to every file. Two kinds of export are recognised:
- A selected export, which holds what the lab reviewed and kept. Its variants are kept even when they fall outside
the filter below (flag
lab_selected_outside_filter). Rows that are not variant calls, or are not calledPRESENT, are still dropped. - A full export (more than 300 rows, or any
ABSENTcall). It is filtered the way Ion Reporter's default Oncomine filter chain does (thermo_like_v1).
The filter for full exports:
| Row type | Kept when |
|---|---|
| SNV, indel | Call PRESENT; exonic or splice site; protein-changing; not a UCSC common SNP and population frequency at most 1 %; VAF at least 2 %; depth at least 15 |
| SNV, indel with an Oncomine Variant Class (Hotspot, Truncating) | Call PRESENT; exonic or splice site; protein-changing; VAF at least 1 %; depth at least 15. The population filter is waived: ASXL1 G646Wfs*12, for example, sits in population databases as a homopolymer artefact |
| Fusion (Oncomine class Fusion or NonTargetedFusion), MET exon 14 skipping, EGFR vIII, KMT2A-PTD (RNA), FLT3-ITD | Call PRESENT |
| CNV, class Amplification | Copy number at least 4. Below 6 the flag low_level_amplification is set |
| CNV, class Deletion | Sent to OncoKB only at copy number 0.5 or less, because OncoKB's Deletion is a deep loss and Ion Reporter's class also covers one-copy loss. One-copy losses are listed with single_copy_loss_not_queried and not asked |
| TMB, MSI (CGP header) | Asked as TMB-High or MSI-High only when Ion Reporter calls them High or MSI-H, with QC PASS for MSI |
| Expression controls, gene expression, RNA exon tiles, LOH | Never |
Other things the pipeline does:
- Transcript retry. Oncomine sometimes numbers a protein change on another transcript than OncoKB (NF1 on
NM_001042492; MYD88 L252P is OncoKB's L265P; WT1; CSF3R). When OncoKB answers
ONCOKB_REJECTED_ALTERATIONorALTERATION_ON_OTHER_TRANSCRIPT, the variant is asked again by its hg19 position and OncoKB maps it. The row then carries the warningoncomine_transcript_differs:A->B. - Flags per variant:
possible_germline(VAF 40 to 60 % or at least 90 %),in_population_databases,low_level_amplification,splice_site_via_coordinates,lab_selected_outside_filter:.... - Each distinct query is asked once, however many samples carry it.
- Patient names in the file name or in the
##Sample Nameheader are never stored. Samples are named by run date and position (011026_3) orsample_N.
Checked against the lab in October 2026 with version 3.2.0 (the filter code is unchanged): on 57 lab-selected exports the filter keeps 320 of the lab's 320 variants.
6.2 VCF: POST /api/v1/vcf
Upload one VCF (.vcf or .vcf.gz, multipart field file).
| Form field | Default | Meaning |
|---|---|---|
mode |
germline |
germline for a constitutional exome or genome, somatic for a tumour VCF |
tumor_type |
none | OncoTree code or name; none means pan-cancer |
sample |
none | The sample column to read. Required when the VCF has several, for example tumour and normal |
max_pop_af |
0.01 |
gnomAD filtering allele frequency at or above which a call is listed but not asked |
include_synonymous |
false |
Also keep synonymous calls |
min_vaf |
0 |
Drop calls below this allele fraction before any lookup, for example 0.05 |
min_depth |
0 |
Drop calls below this read depth |
germline_annotation |
true |
With mode=germline: also ask OncoKB's germline annotation for constitutional calls |
What happens to the file:
- Records. A record is read when FILTER is
PASSor.(many somatic and merged VCFs apply no filters). Contigs may be namedchr7or7. Only alleles the chosen sample carries (its GT) are read. - Genes. A call is kept when it overlaps the coding sequence, or the two splice bases on either side of an exon, of a gene OncoKB annotates. HLA and KIR genes are skipped: their normal allele differences look like truncations. In a 30x genome this keeps about 1,200 of 5.2 million calls.
- TERT promoter. The two hotspots C228T (chr5:1295113 G>A) and C250T (chr5:1295135 G>A) lie outside the coding
sequence. They are recognised by position and asked as TERT
Promoter Mutation. - Consequence. Computed on the server from the NCBI MANE Select v1.5 transcript: missense, nonsense, start or stop
loss, frameshift, in-frame indel, multi-base change, splice site, synonymous. Synonymous calls are dropped unless
include_synonymousis set. - Thresholds. Calls under
min_vaformin_depthare dropped. VAF and depth are read from the sample'sAFandDPvalues; a call without them is kept. - Population filter. gnomAD v4.1 (exomes and genomes, about 807,000 people), using the filtering allele
frequency (95 % confidence, highest ancestry group, as ACMG BA1 and BS1 recommend).
common_ba1(5 % or more) andcommon(max_pop_afor more) are listed with their frequency andstatus: not_queried, and are not sent to OncoKB.rareandabsentgo on. Sites that failed gnomAD's own QC are flagged and never used to drop a call. A call the gnomAD subset does not cover (the TERT promoter, or a gene OncoKB added after the subset was built) readsnot_coveredand is asked unfiltered. This filter also runs inmode=somatic, where it removes germline polymorphisms that tumour-only calling lets through. - OncoKB. An SNV is asked by its MANE protein change with
reference_genome: GRCh38. If OncoKB answersONCOKB_REJECTED_ALTERATIONorALTERATION_ON_OTHER_TRANSCRIPT, it is asked again by genomic coordinates and the row carriesmane_transcript_differs_from_oncokb. Indels, multi-base changes and splice sites are asked by genomic coordinates from the start. A variant that appears twice is asked once. - Germline annotation. With
mode=germlineandgermline_annotation=true, each constitutional call whose somatic answer wasannotatedornot_curatedis also asked withgermline: true. The row gainsgermline_status,germline_pathogenicity,germline_penetranceandgermline_inheritance.
origin in the report: with mode=germline, a call with the DRAGEN MOSAIC flag or a VAF under 30 % is mosaic
(possible somatic change or clonal haematopoiesis) and is listed first; the rest are germline. With mode=somatic
every call is somatic.
The report has mode, vcf_records, pass, scan_counts, tumor_type, population_filter, counts and calls[].
report.tsv has one row per coding call with these columns: origin, gene, chrom, pos, ref, alt,
consequence, protein_mane, genotype, vaf, depth, gnomad_class, gnomad_filtering_af, gnomad_af,
gnomad_grpmax, gnomad_nhomalt, status, oncokb_alteration, oncogenicity, mutation_effect, hotspot,
highest_sensitive, highest_resistance, levels_any_tumor, germline_pathogenicity, germline_penetrance,
germline_inheritance, errors. The columns highest_sensitive, highest_resistance and the three germline_*
columns are new in 4.0.
The gnomAD subset (about 1.5 million sites, about 24 MB) and the MANE files live on the server. Only public gene intervals were downloaded to build them. The consequence and the population filter run locally, and patient positions are looked up nowhere. What goes to oncokb.org is the gene, the change and the tumour type code.
On the test genome used in October 2026 the gnomAD filter removed 365 of 390 coding calls, and all six calls that had looked "Likely Oncogenic" before the filter existed were common polymorphisms.
6.3 What the VCF pipeline does not do
- No copy number, structural variants or fusions from a VCF. Symbolic alleles and the output of CNV and SV callers
are not read. Send those calls yourself through
/sampleor/annotate. - No TMB and no MSI from a VCF. Send them as biomarkers when your pipeline calls them High.
- GRCh38 only. A file whose header names GRCh37 or hg19 is refused with 422
BAD_VCF, and a job stops when the reference bases of the file do not fit GRCh38. Single calls withgenomic_coordinatesaccept both builds. - MANE Select only. One transcript per gene decides whether a call is coding and what the protein change is.
- No non-coding variants other than the two TERT promoter hotspots.
- No other vendors' TSV formats (Illumina TSO500 and the like). Send a VCF or a list of findings.
7. Accounts pacing and proxies
The service reads oncokb.org through a pool of web accounts. Each account is one registered oncokb.org login with its own pacing and, if you want, its own proxy.
How lookups are spread
For each request to oncokb.org the pool picks the usable account whose next allowed start comes first. An account is usable when it is enabled, not resting after a failure, under its daily limit, and has a token or a password. Three settings hold each account back:
max_concurrency: at most this many requests in flight on the account (default 3).min_interval_s: at least this long between two request starts on the account (default 0.4 s).daily_limit: at most this many requests per UTC day (default 0, no limit).
When every account is busy, a lookup waits for its turn, for up to five minutes; it does not fail at once. When every
account is resting after a failure, a direct call waits only if the shortest rest ends within 10 seconds, and a
lookup inside a job (/jobs, /vcf, /oncomine) waits only if it ends within 4 minutes. Otherwise the lookup fails
at once with NO_ACCOUNT_AVAILABLE. Two identical lookups that arrive together share one download. With the default
pacing one account carries about 2.5 fresh lookups per second, and each further account adds about the same. /health
shows the current ceiling as accounts.requests_per_s_ceiling.
Adding accounts
There are three ways.
The accounts file. Edit it, then call POST /admin/accounts/reload. The file is accounts.json in the data
directory (/var/lib/oncokb/accounts.json on the server), or the path in ONCOKB_ACCOUNTS_FILE. A template is in
deploy/accounts.example.json:
{"defaults": {"max_concurrency": 3, "min_interval_s": 0.4, "daily_limit": 0},
"accounts": [
{"id": "lab1", "username": "first@example.org", "password": "...", "proxy": "http://user:pass@proxy.example.net:8000"},
{"id": "lab2", "username": "second@example.org", "password": "...", "daily_limit": 3000, "note": "no proxy"}],
"proxies": []}
A reload applies the file's settings to the accounts it already knows. They keep their token, their counters and a
rest or back-off in progress. Accounts that are no longer listed are dropped. New ones are logged in within about half
a minute, or on first use. A file that does not parse changes nothing and answers 422 INVALID_ACCOUNTS_FILE.
The API. POST /admin/accounts adds an account, or changes the one with the same id (without an id: the one
with the same login). Fields you leave out keep their stored value. The service writes the accounts file itself. A new
account, or one whose password you changed, is logged in at once, so a wrong password shows in the answer as
"logged_in": false.
curl -s -X POST $B/admin/accounts -H "X-API-Key: $K" -H 'Content-Type: application/json' \
-d '{"id": "lab3", "username": "third@example.org", "password": "...", "min_interval_s": 0.5}'
curl -s $B/admin/accounts -H "X-API-Key: $K" # state of every account
curl -s -X POST $B/admin/accounts/lab3/login -H "X-API-Key: $K" # log in now, end a rest
curl -s -X DELETE $B/admin/accounts/lab3 -H "X-API-Key: $K"
The environment. ONCOKB_USERNAME and ONCOKB_PASSWORD define one account with the id default. Its login and
password are read from the environment only. It cannot be removed or given another login through the API; its pacing
and proxy can be set there.
No endpoint returns a password, a token or proxy credentials. GET /admin/accounts shows the login masked
(f***@example.org) and the proxy as scheme, host and port only. The service writes the accounts file and the state
file with owner-only permissions (0600).
Account settings
| Setting | Default | Meaning |
|---|---|---|
id |
acct- and eight characters derived from the login |
The name in logs, in /admin/accounts and in provenance.account. Letters, digits, dot, dash, underscore; up to 48 |
username |
Required | The oncokb.org login e-mail (email is accepted as the key) |
password |
None | Needed to log in, and to log in again when the token is rejected or runs out |
token |
None | Optional: a token copied from the oncokb.org account page, to skip the first login |
proxy |
None | http://user:pass@host:port, https://, socks5:// or socks5h://. The port is required, and special characters in the password must be percent-encoded. This is the account's only way out |
enabled |
true |
false takes the account out of use |
max_concurrency |
3 | 1 to 32 |
min_interval_s |
0.4 | 0 to 60 seconds |
daily_limit |
0 | Requests per UTC day, logins and token checks included; 0 means no limit. The count survives a restart |
note |
None | Free text, up to 200 characters |
The defaults come from the defaults block of the accounts file, or from ONCOKB_ACCOUNT_CONCURRENCY,
ONCOKB_MIN_INTERVAL_S and ONCOKB_ACCOUNT_DAILY_LIMIT.
Tokens
An oncokb.org web account has one token, valid for 180 days. Logging in returns it. The service logs an account in when
it has no token, when oncokb.org rejects the token (HTTP 401), and when the token is past its expiry date. For
academic accounts, the kind used here, a login after the expiry date extends the token by 180 days, so nothing has to
be done by hand. The service reads each token's expiry date twice a day; /admin/accounts shows it as
token_expires_at, and token_renewal carries a note in the last 14 days.
"Generate new token" on the oncokb.org account page does not extend anything: the new token inherits the old expiry
date. If oncokb.org refuses the login itself, for example with "account has expired", the account rests and
/admin/accounts shows the reason in last_error. The fix then is the verification link that OncoKB e-mails to the
account's address.
Proxies
- Each account can have its own proxy. An account always leaves through the same proxy, so oncokb.org sees one account per address.
- A shared list can be given as
proxiesin the accounts file or asONCOKB_PROXIES(comma-separated). When both are set, the file's list is used. Each account without a proxy of its own takes the entry that the fewest accounts use, which on a first start means entry 1, 2, 3 and so on, wrapping round. It then keeps that entry across reloads and restarts, until the entry is taken off the list. ONCOKB_REQUIRE_PROXY=1sends nothing from an account that has no proxy./readythen reportsno_proxy_configuredwhen no account has one.- Use sticky (session) endpoints of your proxy provider, so that the kept-alive connection stays on one address.
- www.oncokb.org is reachable over IPv4 only. A proxy that offers only IPv6 exits cannot reach it.
- Proxy settings in the service's environment (
HTTP_PROXY,HTTPS_PROXY) are ignored on purpose. - With per-GB billing, 10,000 fresh lookups at about 16 KB are about 0.16 GB. Set
ONCOKB_PROXY_USD_PER_GBand/healthand/admin/usageshow an estimated cost.
GET /admin/usage?days=30 lists requests, failures and bytes per UTC day and account. The byte counts include an
estimate for headers and TLS handshakes, which a proxy bills as well.
What happens on failures
| oncokb.org or the network | The service | If no account can finish the lookup |
|---|---|---|
| Rejects the token (401) | Drops the token, logs the account in again once (every lookup waiting on that account shares the one login) and repeats the request | SESSION_INVALID or LOGIN_REQUIRED, retryable |
| Refuses the login | The account rests 15 minutes, doubling up to 6 hours. /admin/accounts shows why |
LOGIN_REQUIRED, then NO_ACCOUNT_AVAILABLE |
| Answers 429 or 5xx | Doubles the account's interval (at least 1 s, at most 30 s) and moves the lookup to another account. The interval halves again with each success | THROTTLED, retryable |
| Answers 403 | The account rests 1 hour, doubling up to 6 hours; another account takes the lookup | ACCOUNT_FORBIDDEN, retryable |
| Connection or proxy fails, or times out | The same account tries once more. On a second failure it rests 1 minute, doubling, and another account takes the lookup | PAGE_LOAD_FAILED, retryable |
Sends an answer over ONCOKB_MAX_ANSWER_KB |
Drops the download; does not try again | RESPONSE_TOO_LARGE, not retryable |
| Answers 400 | Does not try again | ONCOKB_BAD_REQUEST, not retryable |
| (no account is usable) | Sends nothing | NO_ACCOUNT_AVAILABLE; the message says when to retry |
A lookup makes at most four attempts (ONCOKB_MAX_ATTEMPTS). The doubling of a rest starts over once the account
works again, and POST /admin/accounts/{id}/login ends a rest at once. Failed lookups are never stored, so a retry
asks oncokb.org again. last_error in /health names the kind of failure and the time, without account names; the
detail per account is in /admin/accounts.
8. How little is transferred and why
One lookup is one GET of oncokb.org's variantAnnotation JSON, the document its alteration page is drawn from. The
answer comes gzip-compressed. Measured on 2026-11-10 with OncoKB data v7.7:
| What | Bytes on the wire |
|---|---|
| 81 myeloid-panel lookups | 1.3 to 49 KB each, mean 4.6 KB, 369 KB in total |
| A 40-finding solid-tumour CGP sample with tumour types | 668 KB in total, 16.5 KB per lookup on average |
| The same 40 findings again | 0 bytes: answered from the store in 0.07 s |
| A login | One POST of 64 bytes |
| Reading a token's expiry date | 273 bytes |
The OncoKB data version (/api/v1/info) |
1 KB |
| The Cancer Gene List | 51 KB, once per OncoKB data version |
| A new TLS connection | About 5 to 6 KB |
The 40-finding sample took 17.3 s on one account at the default pacing, with 54 MB of service memory. Version 3.2.0 loaded the page in a browser: about 1.9 MB per lookup and about 350 MB of memory per browser tab.
Why the numbers are small:
- No page. The service fetches the one JSON document and none of the scripts, styles and other calls of the page.
- Connections are kept open. Each account holds an HTTP/2 connection to oncokb.org, so the TLS handshake is paid once, not per lookup. An idle connection is closed after four minutes.
- The store. Every answer is kept in one SQLite file (
oncokb.sqlite). A stored answer is reused for as long as OncoKB's data version is unchanged, and at mostONCOKB_CACHE_MAX_AGE_DAYS(45 days). OncoKB releases roughly monthly. The service reads/api/v1/infoevery 3 hours and also notices a new version in any answer; answers of the old version are then dropped.POST /admin/cache/purgeempties the store by hand, andforce_refreshskips it for one call. - The store holds OncoKB's JSON, not the service's response. Every response is rebuilt from it on each request. A
fix to the service, or a different
detaillevel, needs no new download. - Genes OncoKB does not annotate cost nothing. They are answered from the Cancer Gene List without a request. This matters for exomes and genomes, where most variants are in such genes. The list is used for this only when it belongs to the current OncoKB data version.
- Oversized answers are cut off. oncokb.org answers an unknown gene, or a genomic change outside any gene, with
its whole evidence table: 1.48 MB compressed, 13 MB of JSON. The service reads every answer as a stream and drops
the download as soon as the first bytes say
geneExist=false; that field is within the first 4 KB of the JSON. Any answer aboveONCOKB_MAX_ANSWER_KB(default 400) is dropped as well, withRESPONSE_TOO_LARGE. Dropping means closing that connection, so lookups that may end this way go out on a second connection of the account and leave its main connection untouched.
/health shows the running totals under traffic (requests, bytes, connections, downloads cut short, megabytes the
store saved) and store. provenance.wire_bytes in each fresh answer is what that lookup cost.
9. Limits and caveats
Research use and OncoKB's terms. The accounts are free oncokb.org accounts. OncoKB's terms allow those for research. Commercial use, and clinical reporting, need a licence from OncoKB. The output of this service is not for clinical reporting without review.
Usage is visible to OncoKB. oncokb.org records usage per token, and its staff can review it. Its site code shows no automatic rate limit on the calls this service makes, but its staff can limit or close an account. That is why every account is paced and why the service backs off on 429 and 5xx.
PDFs. Only the browser backend (ONCOKB_BACKEND=browser) can print the OncoKB page to a PDF. It reads with one
account and one Chromium, and it is not installed on the server. With the default backend generate_pdf adds the
warning pdf_not_available:no_browser_in_this_backend, and provenance.page_url is the page a person can open.
What is not annotated.
- Genes outside OncoKB's annotated list:
not_found. - Synonymous changes, DNA-only changes and
p.?: rejected with 422; send coordinates where that helps. - Copy-number gain or loss short of Amplification or homozygous Deletion.
- HRD, LOH and mutational signatures.
- Non-coding variants, other than the TERT promoter.
- Germline pathogenicity beyond what OncoKB's germline annotation holds.
What the service does not do.
- It does not compute TMB or MSI and does not call copy number, fusions or structural variants from files.
- It does not correct a transcript mismatch in single calls; it reports it. The lab file pipelines retry by coordinates.
- It reads oncokb.org's internal web calls. A change on oncokb.org's side can break it.
EXTRACTION_FAILEDand unexpected warnings are the signs.
Operational limits.
| What | Limit |
|---|---|
/annotate/batch |
100 items |
/sample |
500 findings |
/jobs |
20,000 items; results in pages of 500 |
| Upload | 1,024 MB per file (ONCOKB_MAX_UPLOAD_MB); nginx accepts 1,100 MB per request |
| Finished jobs | Removed after 30 days (ONCOKB_JOB_RETENTION_DAYS) |
| Stored answers | Until the OncoKB data version changes; at most 45 days |
| VCF upload | GRCh38 only |
| Fresh lookups | About 2.5 per second per account at the default pacing |
There is one process on one server. A restart marks running jobs failed with "service restarted during the job;
submit it again"; nothing resumes by itself. There is one shared API key and no per-caller quota: one large job slows
everyone's fresh lookups, while stored answers stay immediate.
Patient data. Only the gene, the alteration or genomic change, the reference genome and the tumour type code go to
oncokb.org. sample_id, client_ref, VAF, depth and file names do not. Uploads are deleted when their job ends.
Job reports hold patient-derived variants: delete them when you have the result. The request log leaves out query
strings.
10. Calling it from another server
Base URL. $B is https://3-105-9-242.sslip.io (TLS on since 2026-11-10; plain HTTP redirects to it). The address
is a Lightsail static IP and does not change when the instance is stopped and started.
Key. Send X-API-Key: $K on every /api/v1/* and /admin/* call. A missing or wrong key is 401 UNAUTHORIZED.
/health, /ready, /, /reference, /docs and /openapi.json are open. There is one key for all callers.
Request ID. Every answer has an X-Request-ID header. Send your own (1 to 64 characters from A-Z a-z 0-9 . _ -)
and it is echoed and written to the service's log line for the call. Anything else is replaced by a generated one.
Quote it when you report a problem.
Prefer POST. GET /api/v1/annotate puts the variant in the URL, where proxies and logs on your side may keep it.
Request rate. nginx allows each calling address 30 requests per second with a burst of 200. Above that it answers 503 with an HTML page, not with the JSON error body.
Timeouts. A stored answer returns in milliseconds. A fresh lookup needs one request to oncokb.org, with a 30 s
network timeout per attempt (ONCOKB_TIMEOUT_S) and up to four attempts, and it may first wait for a free slot on an
account.
| Call | Suggested client timeout |
|---|---|
/annotate |
120 s |
/annotate/batch, /sample |
600 s, which is also nginx's limit. 500 fresh findings on one account take about 200 s at 2.5 lookups per second |
/jobs, /vcf, /oncomine |
60 s for the submit and each poll; uploads as long as your link needs |
Errors. Every 4xx and 5xx body has status and errors:
{"status": "error",
"errors": [{"code": "THROTTLED", "retryable": true, "message": "oncokb.org answered HTTP 429"}]}
Program against errors[].code, not against message. field, suggestions, hint and retryable are present when
they apply.
Retrying. Retry only when errors[].retryable is true.
| HTTP | Codes | What to do |
|---|---|---|
| 401 | UNAUTHORIZED |
Fix the key. Do not retry |
| 404, 405, 409, 413 | NOT_FOUND, NOT_FOUND_OR_RUNNING, GENE_NOT_IN_CANCER_GENE_LIST, METHOD_NOT_ALLOWED, NOT_READY, FILE_TOO_LARGE |
Fix the call. 409 NOT_READY on a report means the job is still running or has failed; errors[0].status says which. Keep polling GET /api/v1/jobs/{id} while it is running |
| 422 | Input codes, ONCOKB_REJECTED_ALTERATION, EMPTY_SAMPLE, SAMPLE_TOO_LARGE |
Do not resend unchanged. See field and suggestions |
| 503, retryable | THROTTLED |
Wait 30 to 60 s, then retry. The service has already slowed itself down |
| 503, retryable | PAGE_LOAD_FAILED, SESSION_INVALID |
Retry after 5 s, 15 s, 45 s |
| 503, retryable | NO_ACCOUNT_AVAILABLE, LOGIN_REQUIRED, ACCOUNT_FORBIDDEN |
Accounts are resting. The message of NO_ACCOUNT_AVAILABLE says how long. Retry after that; if it persists, tell the owner |
| 503, not retryable | NO_ACCOUNT_AVAILABLE (no account configured), RESPONSE_TOO_LARGE, ONCOKB_BAD_REQUEST, EXTRACTION_FAILED, RESOLVED_NAME_UNUSABLE |
Do not retry. Report it with the request ID |
| 500 | INTERNAL |
A bug in the service. Report it with the request ID |
Before a large run, call GET /ready. A 503 there lists the reasons: engine_not_started, login_not_configured,
no_proxy_configured, no_usable_account.
A client in Python. Standard library only.
import json, time, urllib.error, urllib.request
BASE, KEY = "https://<host>", "<key>" # $B and $K
def call(method, path, body=None, timeout=120):
req = urllib.request.Request(BASE + path, method=method, data=None if body is None else json.dumps(body).encode(),
headers={"X-API-Key": KEY, "Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.status, json.loads(r.read()), r.headers.get("X-Request-ID")
except urllib.error.HTTPError as e: # 4xx and 5xx carry the JSON error body
raw = e.read()
try:
out = json.loads(raw)
except ValueError: # a 502 / 504 page from nginx is not JSON
out = {"errors": [{"code": f"HTTP_{e.code}", "retryable": e.code >= 500}]}
return e.code, out, e.headers.get("X-Request-ID")
def annotate(body, attempts=4):
"""One variant. Retries only what the service marks retryable; raises on every other failure."""
for attempt in range(attempts):
status, out, request_id = call("POST", "/api/v1/annotate", body)
if status == 200:
return out # then read out["status"]
retryable = any(e.get("retryable") for e in out.get("errors", []))
if status >= 500 and retryable and attempt + 1 < attempts:
time.sleep(5 * 3 ** attempt) # 5 s, 15 s, 45 s
continue
raise RuntimeError(f"HTTP {status}, request {request_id}: {out.get('errors')}")
a = annotate({"gene": "IDH1", "alteration": "R132S", "tumor_type": "AML"})
print(a["status"], a["oncogenicity"]["status"], a["evidence"]["highest_sensitive_level"])
For a whole sample, call call("POST", "/api/v1/sample", {...}, timeout=600) and read summary and not_read. For a
long list, post to /api/v1/jobs, poll call("GET", job["poll"]) until status is no longer running, page through
?offset=0&limit=500, and delete the job.