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

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:

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:

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:

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:

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:

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:

  1. Records. A record is read when FILTER is PASS or . (many somatic and merged VCFs apply no filters). Contigs may be named chr7 or 7. Only alleles the chosen sample carries (its GT) are read.
  2. 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.
  3. 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.
  4. 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_synonymous is set.
  5. Thresholds. Calls under min_vaf or min_depth are dropped. VAF and depth are read from the sample's AF and DP values; a call without them is kept.
  6. 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) and common (max_pop_af or more) are listed with their frequency and status: not_queried, and are not sent to OncoKB. rare and absent go 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) reads not_covered and is asked unfiltered. This filter also runs in mode=somatic, where it removes germline polymorphisms that tumour-only calling lets through.
  7. OncoKB. An SNV is asked by its MANE protein change with reference_genome: GRCh38. If OncoKB answers ONCOKB_REJECTED_ALTERATION or ALTERATION_ON_OTHER_TRANSCRIPT, it is asked again by genomic coordinates and the row carries mane_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.
  8. Germline annotation. With mode=germline and germline_annotation=true, each constitutional call whose somatic answer was annotated or not_curated is also asked with germline: true. The row gains germline_status, germline_pathogenicity, germline_penetrance and germline_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

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:

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

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:

/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.

What the service does not do.

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.