Oncology
The survivorship caseload with open red flags (never the check-in answers), a patient's treatment episodes and care plan, and a staged FHIR treatment-summary import: dry run, then commit or discard by a named clinician.
- Who calls it
- useful for Behavioral health practices
- Before production
- Treating practices with a BAA; the patient's sharing choices apply.
- Sandbox
- Build against synthetic members first. What each sandbox holds
- Standards
- FHIR treatment-summary import (staged)
Markets that use this family
- Behavioral health practices (useful)
Scopes
| Scope | Allows |
|---|---|
oncology.import.write | Send a FHIR R4 oncology treatment-summary bundle for one patient: staged as a draft with what was mapped and what was not (nothing is written to the record), then commit the values a named clinician reviewed, or discard it. |
oncology.read | Read your survivorship caseload with the status flags Norvya computes (plan status, next dated follow-up, open red flags by lane) and a patient's cancer treatment summary; never the side-effect check-in answers (treating practices with a BAA on file; the patient must share their medical history with you). |
Endpoints
Your survivorship caseload with the status flags Norvya computes
Patients with a cancer episode who share their medical history with you, in the staff screen's order (open red flags, the soonest dated follow-up, then no shared plan): plan status (with the survivorship plan's document_ref when your practice authored it), the next dated follow-up item, the latest check-in's lane and open red flags. Never the check-in answers.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/oncology/caseload" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/oncology/caseload",
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
const BASE = "https://norvya.fly.dev";
const url = new URL(`${BASE}/v1/oncology/caseload`);
const res = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"items": [
{
"member_ref": "string",
"episodes": [
{}
],
"plan": {},
"next_due": {},
"latest_check": {
"kind": "string",
"lane": "string",
"open_red_flag": false,
"created_at": "string",
"reviewed_at": "string"
},
"open_red_flags": 0,
"worst_open_lane": "string",
"follow_up_reminders": false
}
],
"side_effect_check_in": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
One patient's cancer treatment summary
Each cancer episode (type, diagnosis date, stage and histology as written, status, treatments with modality, agents and dates, who entered it), the check-in lanes and your open import drafts. 409 member_not_sharing unless the patient shares their medical history with you.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Errors specific to this call
409member_not_sharingAll responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/patients/$MEMBER_REF/oncology" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
resp = requests.get(
f"{BASE}/v1/patients/{member_ref}/oncology",
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
const BASE = "https://norvya.fly.dev";
const member_ref = process.env.MEMBER_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/oncology`);
const res = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"member_ref": "string",
"episodes": [
{
"episode_ref": "one_4c1d2e3f4a5b6c7d8e9f0a1b",
"cancer_type": "string",
"diagnosis_date": "string",
"stage_text": "string",
"histology_text": "string",
"status": "string",
"treatments": [
{}
],
"treatment_completed_on": "string",
"in_chemo_window": false,
"on_chemotherapy_now": false,
"care_team_name": "string",
"oncology_phone": "string",
"entered_by": "string",
"entered_by_your_practice": false
}
],
"checks": [
{
"kind": "string",
"lane": "string",
"open_red_flag": false,
"created_at": "string",
"reviewed_at": "string"
}
],
"open_imports": [
"string"
],
"side_effect_check_in": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Stage a FHIR R4 oncology treatment summary as a draft (a dry run)
Mapped exactly as the care-team upload maps it (oncology_fhir_import.map_bundle: mCODE profiles, listed ICD-10-CM / SNOMED CT codes; nothing inferred, dosage never read) into a DRAFT: what was mapped (`proposal`) and every resource it did not use with the reason (`unmapped`). Nothing reaches the patient's record until POST .../commit with the values a clinician reviewed. One patient per bundle (422 bundle_multiple_patients); the bundle is not stored. 409 member_not_sharing without medical-history sharing.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
staff_ref of the clinician the draft is filed for.
A FHIR R4 Bundle for this ONE patient (mCODE profiles or ICD-10-CM / SNOMED CT cancer codes). Not stored; its SHA-256 is.
Errors specific to this call
422bundle_multiple_patients409member_not_sharingAll responses
201Successful Response401Unauthorized403Forbidden404Not Found409Conflict413Bundle too large.422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/patients/$MEMBER_REF/oncology/imports" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clinician": "string",
"bundle": {}
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
resp = requests.post(
f"{BASE}/v1/patients/{member_ref}/oncology/imports",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"clinician": "string",
"bundle": {},
},
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
const BASE = "https://norvya.fly.dev";
const member_ref = process.env.MEMBER_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/oncology/imports`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clinician": "string",
"bundle": {}
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"import_ref": "onx_4c1d2e3f4a5b6c7d8e9f0a1b",
"member_ref": "string",
"status": "string",
"source": "string",
"bundle_sha256": "string",
"proposal": {},
"unmapped": [
{}
],
"unmapped_total": 0,
"resource_counts": {},
"episode_ref": "string",
"created_by": "string",
"decided_by": "string",
"created_at": "string",
"decided_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
One oncology import: the draft, or what was committed
404 import_not_found for another practice's import or a patient no longer linked or sharing their medical history with you.
Parameters
Errors specific to this call
404import_not_foundAll responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/oncology/imports/$IMPORT_REF" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
import_ref = os.environ["IMPORT_REF"]
resp = requests.get(
f"{BASE}/v1/oncology/imports/{import_ref}",
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
const BASE = "https://norvya.fly.dev";
const import_ref = process.env.IMPORT_REF;
const url = new URL(`${BASE}/v1/oncology/imports/${encodeURIComponent(import_ref)}`);
const res = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"import_ref": "onx_4c1d2e3f4a5b6c7d8e9f0a1b",
"member_ref": "string",
"status": "string",
"source": "string",
"bundle_sha256": "string",
"proposal": {},
"unmapped": [
{}
],
"unmapped_total": 0,
"resource_counts": {},
"episode_ref": "string",
"created_by": "string",
"decided_by": "string",
"created_at": "string",
"decided_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Commit the values a clinician reviewed
The clinician's reviewed values — not the draft's — are written (oncology_fhir_import.confirm, entered by the care team): every treatment needs the modality the clinician chose (422 invalid_treatments). With episode_ref the treatments are added to that episode (identical rows not duplicated). 409 import_already_decided.
Parameters
Request body
Content type: application/json (required)
staff_ref of the clinician who reviewed the draft.
breast | colorectal | prostate | lung | melanoma | other
active_treatment | completed_treatment | surveillance | recurrence
Each with the modality the clinician chose (surgery | chemotherapy | radiation | immunotherapy | targeted | hormonal | ...), agents, start, end, detail, site.
Add to this existing episode instead of a new one.
Errors specific to this call
422invalid_treatments409import_already_decidedAll responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/oncology/imports/$IMPORT_REF/commit" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clinician": "string",
"cancer_type": "string",
"status": "string",
"treatments": [
{}
],
"diagnosis_date": "string",
"stage_text": "string",
"histology_text": "string",
"episode_ref": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
import_ref = os.environ["IMPORT_REF"]
resp = requests.post(
f"{BASE}/v1/oncology/imports/{import_ref}/commit",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"clinician": "string",
"cancer_type": "string",
"status": "string",
"treatments": [
{},
],
"diagnosis_date": "string",
"stage_text": "string",
"histology_text": "string",
"episode_ref": "string",
},
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
const BASE = "https://norvya.fly.dev";
const import_ref = process.env.IMPORT_REF;
const url = new URL(`${BASE}/v1/oncology/imports/${encodeURIComponent(import_ref)}/commit`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clinician": "string",
"cancer_type": "string",
"status": "string",
"treatments": [
{}
],
"diagnosis_date": "string",
"stage_text": "string",
"histology_text": "string",
"episode_ref": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"import_ref": "onx_4c1d2e3f4a5b6c7d8e9f0a1b",
"member_ref": "string",
"status": "string",
"source": "string",
"bundle_sha256": "string",
"proposal": {},
"unmapped": [
{}
],
"unmapped_total": 0,
"resource_counts": {},
"episode_ref": "string",
"created_by": "string",
"decided_by": "string",
"created_at": "string",
"decided_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Discard a staged oncology import
Parameters
Request body
Content type: application/json (required)
staff_ref of the clinician acting.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/oncology/imports/$IMPORT_REF/discard" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clinician": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
import_ref = os.environ["IMPORT_REF"]
resp = requests.post(
f"{BASE}/v1/oncology/imports/{import_ref}/discard",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"clinician": "string",
},
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
const BASE = "https://norvya.fly.dev";
const import_ref = process.env.IMPORT_REF;
const url = new URL(`${BASE}/v1/oncology/imports/${encodeURIComponent(import_ref)}/discard`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clinician": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"import_ref": "onx_4c1d2e3f4a5b6c7d8e9f0a1b",
"member_ref": "string",
"status": "string",
"source": "string",
"bundle_sha256": "string",
"proposal": {},
"unmapped": [
{}
],
"unmapped_total": 0,
"resource_counts": {},
"episode_ref": "string",
"created_by": "string",
"decided_by": "string",
"created_at": "string",
"decided_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Generated from the Partner API router and developers_catalog.py. The router docstring is the source of each description.