Clinical intelligence
For treating practices: the pre-visit brief, the ranked panel worklist with the evidence and guideline behind each item, care gaps, and between-visit signals the patient chose to share. Write-back: triage a worklist item (assign it to a clinician from GET /v1/panel/clinicians, snooze, dismiss or mark done) and close the loop on results and referrals (acknowledge, act, tell the patient, close). Validated questionnaires (PHQ-9, GAD-7 and the rest of Norvya's instrument registry, English items unless an official translation exists) requested from your system and scored server-side; a response that trips a crisis item shows the member crisis resources first and alerts staff, and is never routed to coaching or an AI path. Caseload questionnaire batches (one instrument to a member list or a cohort filter, asynchronous, with a suppressed aggregate) and clinician-rated measures a named clinician assigned. Specialty hub results and the stateless spirometry, lung-cancer screening and GI alarm checks are their own family: Specialty care.
- Who calls it
- Core for Concierge doctors, Independent practices, EMR vendors, Hospitals and health systems, Specialty care; useful for Federal (via primes), Practice management & billing, Medical travel desks, Behavioral health practices, Rehab & nutrition practices, Older adults & caregiving programmes
- Before production
- No gate of its own; your market's “Before production” applies.
- Sandbox
- Build against synthetic members first. What each sandbox holds
Markets that use this family
- Concierge doctors (core)
- Independent practices (core)
- EMR vendors (core)
- Federal (via primes) (useful)
- Hospitals and health systems (core)
- Practice management & billing (useful)
- Medical travel desks (useful)
- Behavioral health practices (useful)
- Rehab & nutrition practices (useful)
- Older adults & caregiving programmes (useful)
- Specialty care (core)
Scopes
| Scope | Allows |
|---|---|
clinical.brief.read | Read the clinician pre-visit brief for a patient (medical practices only; per-section consent). |
clinical.loops.write | Close the loop on results and referrals from your system: record that a named clinician acknowledged a result, that the patient was told, the action taken, and move a tracked referral through scheduled, seen, report received and closed (medical practices only). |
clinical.panel.read | Read the practice's ranked panel worklist and start worklist runs (medical practices only). |
clinical.panel.write | Triage your practice's worklist findings from your system (addressed, snoozed until a date, dismissed with a reason, or reopened) with the same rules as Norvya's own worklist screen (medical practices only). |
clinical.patient.read | Read a patient's care gaps and between-visit home-data signals (medical practices only; consent and data basis respected). |
clinical.ratings.write | Record a clinician-rated or clinician-timed measure (for example FOIS, Nine-Hole Peg Test, AIMS) that a named clinician of your practice assigned; scored by Norvya (treating practices only). |
instruments.read | Read questionnaire scores (total, severity band, subscales) with the instrument version and completion time, never item-level answers; mental-health instruments only when the patient shares mental health with you (treating practices only). |
instruments.request | Ask a patient to complete a validated questionnaire from Norvya's instrument library; Norvya sends its own generic notification (treating practices only). |
Endpoints
Clinician pre-visit brief for one patient
The pre-visit brief (longevity, labs, body composition, GLP-1, progress, life-stage care) for a patient of YOUR medical practice. Each section follows the patient's sharing switches for your practice; withheld sections are never computed and are listed in withheld_by_consent. 409 not_a_medical_practice for a gym or other non-practice organisation.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Validation Error429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/patients/$MEMBER_REF/brief" \
-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}/brief",
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)}/brief`);
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",
"external_id": "string",
"available": false,
"sections": {},
"withheld_by_consent": [
"string"
],
"consent_note": "string",
"disclaimer": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Your practice's ranked worklist: who needs attention and why
The latest COMPLETED current-data worklist run for your practice, ranked by the transparent priority score (no model, no hidden weights). Each finding carries its rule code, severity, the evidence it used and its published guideline. Only members still actively linked; read through the clinic data basis (what the practice may use; member home data only for the types the member shares with you). Triage states ARE applied (since Wave A3, the staff worklist's rules: worklist_triage.apply): addressed, dismissed and snoozed findings are hidden unless include_triaged=true, escalated / reappeared / ended-snooze findings reopen, and `priority` / `top_severity` / ranking count OPEN findings only (run_priority keeps the run's own score). Each finding carries item_ref and a triage object. 409 not_a_medical_practice for a non-practice.
Parameters
One member (mr_... or ext:...).
Also show findings staff (or your system) addressed, dismissed or snoozed.
Language of title / detail (an app language).
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Errors specific to this call
409not_a_medical_practiceAll responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/panel/worklist" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/panel/worklist",
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/panel/worklist`);
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());{
"run": {
"id": 0,
"status": "string",
"as_of": "string",
"started_at": "string",
"finished_at": "string",
"patient_count": 0,
"processed_count": 0,
"error_count": 0,
"summary": {}
},
"pending_run": {
"id": 0,
"status": "string",
"as_of": "string",
"started_at": "string",
"finished_at": "string",
"patient_count": 0,
"processed_count": 0,
"error_count": 0,
"summary": {}
},
"total": 0,
"offset": 0,
"limit": 0,
"wording_status": "string",
"items": [
{
"member_ref": "string",
"external_id": "string",
"rank": 0,
"priority": 0,
"top_severity": "string",
"findings": [
{}
],
"limitations": [
"string"
],
"triage": {}
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Start a worklist run for your practice
Queues a run over every active patient of your practice (background). Poll GET /v1/panel/worklist/runs/{id}, or subscribe to the worklist.run_completed webhook. One run at a time per practice (409 panel_worklist_run_in_progress); 409 panel_worklist_no_patients for an empty panel. The run is recorded as started by your client (chained audit row) — runs started by a partner do not opt the practice into the nightly refresh, which follows staff use.
Parameters
YYYY-MM-DD; default today's data. A past date runs a point-in-time evaluation.
Errors specific to this call
409panel_worklist_no_patientsAll responses
202Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/panel/worklist/runs" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.post(
BASE + "/v1/panel/worklist/runs",
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/panel/worklist/runs`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"id": 0,
"status": "string",
"as_of": "string",
"started_at": "string",
"finished_at": "string",
"patient_count": 0,
"processed_count": 0,
"error_count": 0,
"summary": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Status of one worklist run
Counts and progress only (no patient rows). 404 panel_worklist_run_not_found for a run that is not your practice's.
Parameters
Errors specific to this call
404panel_worklist_run_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/panel/worklist/runs/$RUN_ID" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
run_id = os.environ["RUN_ID"]
resp = requests.get(
f"{BASE}/v1/panel/worklist/runs/{run_id}",
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 run_id = process.env.RUN_ID;
const url = new URL(`${BASE}/v1/panel/worklist/runs/${encodeURIComponent(run_id)}`);
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());{
"id": 0,
"status": "string",
"as_of": "string",
"started_at": "string",
"finished_at": "string",
"patient_count": 0,
"processed_count": 0,
"error_count": 0,
"summary": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Care gaps for one patient
The practice care-gap rules (colorectal and breast screening, kidney health and HbA1c in diabetes, statin in diabetes), each with its status, dates, evidence and guideline citation, evaluated on what YOUR practice may use for this patient (clinic data basis: the patient's sharing switches, or your own records under practice-of-record with a BAA). Nothing is assumed: a missing input is insufficient_data, never a guess.
Parameters
YYYY-MM-DD: evaluate as of a past date.
YYYY-MM-DD (needs as_of): only what was on file at the end of that day.
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
All 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/care-gaps" \
-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}/care-gaps",
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)}/care-gaps`);
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",
"external_id": "string",
"as_of": "string",
"known_as_of": "string",
"gaps": [
{}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Between-visit signals from the patient's own home data
Signals (home BP vs office, resting heart rate, sleep, activity, CGM time in range, at-home labs) computed ONLY from the data types the patient has switched on for your practice (home_bp, wearables, home_labs, cgm; default off; read live, so turning one off removes it from the next call). Every signal is member-reported, never practice-confirmed and cites its source.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
All 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/between-visit" \
-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}/between-visit",
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)}/between-visit`);
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",
"external_id": "string",
"engine": "string",
"as_of": "string",
"label": "string",
"consent": {},
"consent_version": "string",
"sharing": [
"string"
],
"last_reading": {},
"signals": [
{}
],
"top_priority": 0,
"limitations": [
"string"
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Your practice's clinicians, as staff_ref values
The active clinical staff of your practice (admin, provider, pharmacist, therapist) as staff_ref values with name and role, to map your own users to: result writes name the clinician who did it. Staff names are not patient data. Needs clinical.loops.write or clinical.panel.write.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/panel/clinicians" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/panel/clinicians",
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/panel/clinicians`);
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());{
"clinicians": [
{
"staff_ref": "string",
"name": "string",
"role": "string",
"title": "string"
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Triage one worklist finding (addressed, snoozed, dismissed or reopened)
The same states and rules as Norvya's worklist screen (worklist_triage): addressed and dismissed need a reason_code; snoozed needs snooze_until (or snooze_days) and comes back on that date; open reopens. A later run at a HIGHER severity, or the finding coming back after it cleared, reopens it automatically. The change is recorded with your client as the actor and is visible to staff at once. 404 worklist_item_not_found for a ref that is not in your latest worklist run; 422 worklist_reason_required, worklist_invalid_reason, worklist_note_required_for_other, worklist_snooze_until_required, worklist_invalid_snooze, worklist_note_too_long, worklist_invalid_state.
Parameters
Request body
Content type: application/json (required)
open | addressed | snoozed | dismissed
Required with reason_code other.
YYYY-MM-DD, 1..365 days ahead (snoozed).
Errors specific to this call
404worklist_item_not_found422worklist_reason_requiredAll 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/panel/worklist/items/$ITEM_REF/triage" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"state": "addressed",
"reason_code": "addressed_in_visit",
"note": "string",
"snooze_until": "2026-11-15",
"snooze_days": 0
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
item_ref = os.environ["ITEM_REF"]
resp = requests.post(
f"{BASE}/v1/panel/worklist/items/{item_ref}/triage",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"state": "addressed",
"reason_code": "addressed_in_visit",
"note": "string",
"snooze_until": "2026-11-15",
"snooze_days": 0,
},
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 item_ref = process.env.ITEM_REF;
const url = new URL(`${BASE}/v1/panel/worklist/items/${encodeURIComponent(item_ref)}/triage`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"state": "addressed",
"reason_code": "addressed_in_visit",
"note": "string",
"snooze_until": "2026-11-15",
"snooze_days": 0
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"item_ref": "string",
"member_ref": "string",
"code": "string",
"domain": "string",
"severity": "string",
"triage": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
A patient's results awaiting follow-up (acknowledge, inform, act, close)
The patient's items in your practice's result follow-up inbox, oldest first: what arrived, whether a clinician acknowledged it, whether the patient was told, the action recorded, and the overdue clocks. A pointer, never the value: fetch the value from its source. A flag is carried only when a source supplied it (flag_origin says which kind). Only results the patient still shares with you.
Parameters
open | closed | all
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
All 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/results" \
-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}/results",
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)}/results`);
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",
"status": "string",
"items": [
{
"result_ref": "string",
"member_ref": "string",
"label": "string",
"source_kind": "string",
"resulted_on": "string",
"arrived_at": "string",
"status": "string",
"acknowledged": false,
"acknowledged_at": "string",
"patient_informed": false,
"informed_at": "string",
"informed_method": "string",
"action_taken": "string",
"action_at": "string",
"action_link_kind": "string",
"escalated": false,
"closed_at": "string",
"flag": "string",
"flag_origin": "string",
"statement_code": "string",
"clocks": {}
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Record that a named clinician acknowledged a result
The clinician named by `clinician` saw it. NOT notification of the patient (that is /informed). Re-acknowledging keeps the first record. 409 already_closed; 422 clinician_not_found.
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 who did it (GET /v1/panel/clinicians).
Errors specific to this call
409already_closed422clinician_not_foundAll 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/patients/$MEMBER_REF/results/$RESULT_REF/acknowledge" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clinician": "stf_1a2b3c4d5e6f7a8b9c0d1e2f",
"note": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
result_ref = os.environ["RESULT_REF"]
resp = requests.post(
f"{BASE}/v1/patients/{member_ref}/results/{result_ref}/acknowledge",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"clinician": "stf_1a2b3c4d5e6f7a8b9c0d1e2f",
"note": "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 member_ref = process.env.MEMBER_REF;
const result_ref = process.env.RESULT_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/results/${encodeURIComponent(result_ref)}/acknowledge`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clinician": "stf_1a2b3c4d5e6f7a8b9c0d1e2f",
"note": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"result_ref": "string",
"member_ref": "string",
"label": "string",
"source_kind": "string",
"resulted_on": "string",
"arrived_at": "string",
"status": "string",
"acknowledged": false,
"acknowledged_at": "string",
"patient_informed": false,
"informed_at": "string",
"informed_method": "string",
"action_taken": "string",
"action_at": "string",
"action_link_kind": "string",
"escalated": false,
"closed_at": "string",
"flag": "string",
"flag_origin": "string",
"statement_code": "string",
"clocks": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Record that the patient was told
By the named clinician, by `method`. 422 invalid_inform_method.
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 who did it (GET /v1/panel/clinicians).
phone | portal_message | in_person | letter | other
Errors specific to this call
422invalid_inform_methodAll 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/patients/$MEMBER_REF/results/$RESULT_REF/informed" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clinician": "stf_1a2b3c4d5e6f7a8b9c0d1e2f",
"note": "string",
"method": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
result_ref = os.environ["RESULT_REF"]
resp = requests.post(
f"{BASE}/v1/patients/{member_ref}/results/{result_ref}/informed",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"clinician": "stf_1a2b3c4d5e6f7a8b9c0d1e2f",
"note": "string",
"method": "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 member_ref = process.env.MEMBER_REF;
const result_ref = process.env.RESULT_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/results/${encodeURIComponent(result_ref)}/informed`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clinician": "stf_1a2b3c4d5e6f7a8b9c0d1e2f",
"note": "string",
"method": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"result_ref": "string",
"member_ref": "string",
"label": "string",
"source_kind": "string",
"resulted_on": "string",
"arrived_at": "string",
"status": "string",
"acknowledged": false,
"acknowledged_at": "string",
"patient_informed": false,
"informed_at": "string",
"informed_method": "string",
"action_taken": "string",
"action_at": "string",
"action_link_kind": "string",
"escalated": false,
"closed_at": "string",
"flag": "string",
"flag_origin": "string",
"statement_code": "string",
"clocks": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Record the action taken on a result
What was done, in the clinician's words, and what kind of thing it was. 422 action_text_required, invalid_action_link_kind.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
referral | order | message | note | appointment | none
Errors specific to this call
422action_text_requiredAll 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/patients/$MEMBER_REF/results/$RESULT_REF/action" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clinician": "string",
"action_taken": "string",
"link_kind": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
result_ref = os.environ["RESULT_REF"]
resp = requests.post(
f"{BASE}/v1/patients/{member_ref}/results/{result_ref}/action",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"clinician": "string",
"action_taken": "string",
"link_kind": "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 member_ref = process.env.MEMBER_REF;
const result_ref = process.env.RESULT_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/results/${encodeURIComponent(result_ref)}/action`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clinician": "string",
"action_taken": "string",
"link_kind": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"result_ref": "string",
"member_ref": "string",
"label": "string",
"source_kind": "string",
"resulted_on": "string",
"arrived_at": "string",
"status": "string",
"acknowledged": false,
"acknowledged_at": "string",
"patient_informed": false,
"informed_at": "string",
"informed_method": "string",
"action_taken": "string",
"action_at": "string",
"action_link_kind": "string",
"escalated": false,
"closed_at": "string",
"flag": "string",
"flag_origin": "string",
"statement_code": "string",
"clocks": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Close the loop on a result
Refused until a clinician acknowledged it (409 acknowledge_first) and, for a flagged result, the patient was told (409 patient_not_informed) and an action recorded (409 action_not_recorded).
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
Errors specific to this call
409acknowledge_first409patient_not_informed409action_not_recordedAll 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/patients/$MEMBER_REF/results/$RESULT_REF/close" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clinician": "string",
"reason": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
result_ref = os.environ["RESULT_REF"]
resp = requests.post(
f"{BASE}/v1/patients/{member_ref}/results/{result_ref}/close",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"clinician": "string",
"reason": "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 member_ref = process.env.MEMBER_REF;
const result_ref = process.env.RESULT_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/results/${encodeURIComponent(result_ref)}/close`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"clinician": "string",
"reason": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"result_ref": "string",
"member_ref": "string",
"label": "string",
"source_kind": "string",
"resulted_on": "string",
"arrived_at": "string",
"status": "string",
"acknowledged": false,
"acknowledged_at": "string",
"patient_informed": false,
"informed_at": "string",
"informed_method": "string",
"action_taken": "string",
"action_at": "string",
"action_link_kind": "string",
"escalated": false,
"closed_at": "string",
"flag": "string",
"flag_origin": "string",
"statement_code": "string",
"clocks": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Referrals your practice tracks for a patient, with their loop status
Each tracked referral's status, stage, the dates recorded, how each fact was established, and the overdue clocks your practice set. No record of an appointment is not a record that none was made.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
All 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/referrals" \
-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}/referrals",
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)}/referrals`);
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",
"items": [
{
"referral_ref": "string",
"member_ref": "string",
"status": "string",
"stage": "string",
"open": false,
"allowed_statuses": [
"string"
],
"urgency_level": "string",
"referral_reason": "string",
"specialist_name": "string",
"specialty": "string",
"origin": "string",
"sent_at": "string",
"appointment_scheduled_date": "string",
"appointment_completed_date": "string",
"completion_recorded": false,
"completion_source": "string",
"report_received": false,
"report_received_at": "string",
"report_source": "string",
"referring_provider_notified": false,
"closed_at": "string",
"flags": [
{}
],
"possible_completion": "string",
"disclaimer": "string"
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Move a tracked referral (scheduled, seen, report received, completed, ...)
Through referral_loop's own functions and transition table (422 invalid_transition:<from>-><to>). scheduled / seen need occurred_on; report_received needs report_source and the report text or your document_ref; completed (the referring provider has the outcome) needs a report on file (409 no_report_to_send). Recorded with source practice_system.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
accepted | scheduled | appointment_scheduled | seen | no_show | report_received | completed | declined | cancelled | unreachable | closed_unmet | pending
YYYY-MM-DD: the appointment / visit / report date (required for scheduled and seen).
report_received: direct | fax | portal | mail | phone | in_person | patient_reported | upload
report_received: the report text, or ...
... your document id instead.
Errors specific to this call
409no_report_to_sendAll 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/patients/$MEMBER_REF/referrals/$REFERRAL_REF/status" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "scheduled",
"occurred_on": "2026-11-02",
"note": "string",
"report_source": "string",
"report": "string",
"document_ref": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
referral_ref = os.environ["REFERRAL_REF"]
resp = requests.post(
f"{BASE}/v1/patients/{member_ref}/referrals/{referral_ref}/status",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"status": "scheduled",
"occurred_on": "2026-11-02",
"note": "string",
"report_source": "string",
"report": "string",
"document_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 member_ref = process.env.MEMBER_REF;
const referral_ref = process.env.REFERRAL_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/referrals/${encodeURIComponent(referral_ref)}/status`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"status": "scheduled",
"occurred_on": "2026-11-02",
"note": "string",
"report_source": "string",
"report": "string",
"document_ref": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"referral_ref": "string",
"member_ref": "string",
"status": "string",
"stage": "string",
"open": false,
"allowed_statuses": [
"string"
],
"urgency_level": "string",
"referral_reason": "string",
"specialist_name": "string",
"specialty": "string",
"origin": "string",
"sent_at": "string",
"appointment_scheduled_date": "string",
"appointment_completed_date": "string",
"completion_recorded": false,
"completion_source": "string",
"report_received": false,
"report_received_at": "string",
"report_source": "string",
"referring_provider_notified": false,
"closed_at": "string",
"flags": [
{}
],
"possible_completion": "string",
"disclaimer": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Questionnaires you may request
Norvya's validated instrument library (scored server-side), with the version, the licence record and whether the wording is English-only. `clinician_rated` lists the clinician-rated measures (recorded with POST /v1/patients/{member_ref}/clinician-ratings where recordable); `not_requestable` lists every other questionnaire Norvya supports with the reason (parent-completed child screens, feature flows with their own red-flag checks, licences this deployment does not hold, specialty-hub checks, crisis triage checks). Treating practices only; instruments.request or instruments.read.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/instruments" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/instruments",
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/instruments`);
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": [
{
"id": "phq9",
"name": "string",
"short_name": "string",
"version": "PHQ-9",
"category": "string",
"item_count": 0,
"estimated_minutes": 0,
"language": "en",
"english_validated_only": false,
"mental_health": false,
"crisis_capable": false,
"licensed": false,
"licence": {
"holder": "string",
"verdict": "string",
"on_licensed_path": false
}
}
],
"clinician_rated": [
{
"id": "fois",
"name": "string",
"version": "string",
"item_count": 0,
"items": [
{}
],
"accepts_decimal": false,
"recordable": false,
"reason_code": "string"
}
],
"not_requestable": [
{
"id": "swyc_bpsc",
"registry": "string",
"reason_code": "string",
"reason": "string",
"served_by": "string"
}
],
"note": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Ask a patient to complete a questionnaire
Norvya sends its own generic notification ("Questionnaire from your care team"; no instrument name in the push) and shows the questionnaire in the app. The patient answers in Norvya, never through this API, so a crisis answer reaches the patient's crisis resources and your staff first. Idempotent with external_id. 409 member_not_accepting_requests when the patient muted your organisation.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
Your id: a retry with it returns the same request.
Errors specific to this call
409member_not_accepting_requestsAll responses
201Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/members/$MEMBER_REF/instrument-requests" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"instrument_id": "phq9",
"external_id": "ORD-55120"
}'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/members/{member_ref}/instrument-requests",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"instrument_id": "phq9",
"external_id": "ORD-55120",
},
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/members/${encodeURIComponent(member_ref)}/instrument-requests`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"instrument_id": "phq9",
"external_id": "ORD-55120"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"member_ref": "string",
"response_ref": "ir_4c1d2e3f4a5b6c7d8e9f0a1b",
"instrument_id": "string",
"status": "requested",
"created": false,
"notified": false,
"results_visible": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
A patient's questionnaire scores
Total, severity band, positive-screen flag and subscales with the instrument version and completion time. Never item-level answers. Mental-health questionnaires only while the patient shares mental health with you, others while they share medical history; pending requests are yours only. crisis_flag true means Norvya's crisis gate fired in the app: follow your practice's crisis protocol (crisis_guidance).
Parameters
submitted | requested | all
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/members/$MEMBER_REF/instrument-responses" \
-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/members/{member_ref}/instrument-responses",
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/members/${encodeURIComponent(member_ref)}/instrument-responses`);
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",
"sharing": {},
"items": [
{
"response_ref": "string",
"instrument": {
"id": "string",
"name": "string",
"version": "string"
},
"status": "string",
"requested_by_you": false,
"requested_at": "string",
"completed_at": "string",
"language": "string",
"score": {
"total": 0.0,
"max_score": 0.0,
"band": "string",
"positive_screen": false,
"subscales": {}
},
"crisis_flag": false,
"crisis_guidance": "string"
}
],
"answers_included": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
Sandbox: make a synthetic patient complete a requested questionnaire
Sandbox organisations only (404 elsewhere). Scored server-side like a real submission; crisis_example (PHQ-9) shows what a crisis-flagged response looks like. No notification or push is sent; your instrument.completed webhook fires as it would live.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json
Errors specific to this call
404elsewhereAll 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/members/$MEMBER_REF/instrument-requests/$RESPONSE_REF/simulate-completion" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"answers": [
0
],
"crisis_example": false
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
response_ref = os.environ["RESPONSE_REF"]
resp = requests.post(
f"{BASE}/v1/members/{member_ref}/instrument-requests/{response_ref}/simulate-completion",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"answers": [
0,
],
"crisis_example": False,
},
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 response_ref = process.env.RESPONSE_REF;
const url = new URL(`${BASE}/v1/members/${encodeURIComponent(member_ref)}/instrument-requests/${encodeURIComponent(response_ref)}/simulate-completion`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"answers": [
0
],
"crisis_example": false
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"response_ref": "string",
"instrument": {
"id": "string",
"name": "string",
"version": "string"
},
"status": "string",
"requested_by_you": false,
"requested_at": "string",
"completed_at": "string",
"language": "string",
"score": {
"total": 0.0,
"max_score": 0.0,
"band": "string",
"positive_screen": false,
"subscales": {}
},
"crisis_flag": false,
"crisis_guidance": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Record a clinician-rated measure a named clinician assigned
The measures Norvya lets a practice's staff enter (GET /v1/instruments clinician_rated[] with recordable true: today the Nine-Hole Peg Test and FOIS), scored by Norvya and stored exactly as the staff screen stores them, attributed to the clinician you name (an active clinical staff member of your practice; 422 clinician_not_found). The patient must share their medical history with you (409 member_not_sharing).
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
One value per item, in item order (GET /v1/instruments clinician_rated[].items).
staff_ref of the clinician who assigned it (GET /v1/panel/clinicians).
Errors specific to this call
422clinician_not_found409member_not_sharingAll responses
201Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/patients/$MEMBER_REF/clinician-ratings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"instrument_id": "fois",
"values": [
5
],
"clinician": "string"
}'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}/clinician-ratings",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"instrument_id": "fois",
"values": [
5,
],
"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 member_ref = process.env.MEMBER_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/clinician-ratings`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"instrument_id": "fois",
"values": [
5
],
"clinician": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"member_ref": "string",
"response_ref": "string",
"instrument_id": "string",
"version": "string",
"score": {},
"recorded_at": "string",
"clinician": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Ask a caseload to complete a questionnaire
One requestable instrument to up to 500 of your active members (member_refs) or a cohort: all_active, care_program (active enrolments in one programme) or worklist_finding (patients with that finding in your latest completed worklist run). Sent asynchronously with Norvya's own generic notification, one request per member (a member who already has your request for it pending gets no second one; members who muted your organisation are refused). Each member answers in the app, where the crisis gate runs first. Poll the status_url (batch_ref) or subscribe to instrument.batch_dispatched; completions arrive as instrument.completed. Idempotent with external_id.
Request body
Content type: application/json (required)
Up to 500 of your members.
Your id: a retry returns the same batch.
All responses
202Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/instrument-requests/batch" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"instrument_id": "phq9",
"member_refs": [
"string"
],
"cohort": {
"kind": "all_active",
"program": "string",
"code": "string"
},
"external_id": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.post(
BASE + "/v1/instrument-requests/batch",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"instrument_id": "phq9",
"member_refs": [
"string",
],
"cohort": {
"kind": "all_active",
"program": "string",
"code": "string",
},
"external_id": "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 url = new URL(`${BASE}/v1/instrument-requests/batch`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"instrument_id": "phq9",
"member_refs": [
"string"
],
"cohort": {
"kind": "all_active",
"program": "string",
"code": "string"
},
"external_id": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"batch_ref": "string",
"status": "string",
"error": "string",
"instrument": {},
"cohort": {},
"created_at": "string",
"started_at": "string",
"finished_at": "string",
"progress": {},
"aggregate": {
"min_cell": 0,
"completed": {
"count": 0,
"suppressed": false
},
"positive_screens": {
"count": 0,
"suppressed": false
},
"crisis_flagged": {
"count": 0,
"suppressed": false
}
},
"members_included": false,
"members": [
{
"member_ref": "string",
"outcome": "string",
"code": "string",
"status": "string",
"response_ref": "string",
"completed_at": "string",
"results_visible": false,
"score_band": "string",
"positive_screen": false,
"crisis_flag": false,
"crisis_guidance": "string"
}
],
"created": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
Your caseload batches, newest first
The batches THIS client started for your organisation (no member data).
Parameters
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/instrument-requests/batch" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/instrument-requests/batch",
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/instrument-requests/batch`);
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": [
{
"batch_ref": "string",
"status": "string",
"instrument_id": "string",
"members": 0,
"created_at": "string",
"finished_at": "string"
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
A caseload batch: progress and results
Progress (requested, already pending, refused, completed) and an aggregate (completed, positive screens, crisis flags) with small cells withheld. With instruments.read as well: the per-member list (member_ref, outcome, status, and the score band, positive screen and crisis_flag only where the member shares that instrument's results with you). crisis_flag true means the patient already saw crisis resources and your staff were notified in Norvya: follow your crisis protocol (crisis_guidance).
Parameters
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/instrument-requests/batch/$BATCH_REF" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
batch_ref = os.environ["BATCH_REF"]
resp = requests.get(
f"{BASE}/v1/instrument-requests/batch/{batch_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 batch_ref = process.env.BATCH_REF;
const url = new URL(`${BASE}/v1/instrument-requests/batch/${encodeURIComponent(batch_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());{
"batch_ref": "string",
"status": "string",
"error": "string",
"instrument": {},
"cohort": {},
"created_at": "string",
"started_at": "string",
"finished_at": "string",
"progress": {},
"aggregate": {
"min_cell": 0,
"completed": {
"count": 0,
"suppressed": false
},
"positive_screens": {
"count": 0,
"suppressed": false
},
"crisis_flagged": {
"count": 0,
"suppressed": false
}
},
"members_included": false,
"members": [
{
"member_ref": "string",
"outcome": "string",
"code": "string",
"status": "string",
"response_ref": "string",
"completed_at": "string",
"results_visible": false,
"score_band": "string",
"positive_screen": false,
"crisis_flag": false,
"crisis_guidance": "string"
}
],
"created": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
Webhook events
Payloads carry no health data, only a reference to fetch with your token. Each event needs its scope as well as webhooks.manage. Signing and retries
| Event | Scope | When |
|---|---|---|
instrument.batch_dispatched | instruments.request | A caseload questionnaire batch you started finished sending its requests. |
instrument.completed | instruments.read | A patient completed a questionnaire this practice requested (after the crisis gate has run). |
patient.attention_needed | clinical.panel.read | A patient newly entered the top priority band in a completed current-data run. |
worklist.item_triaged | clinical.panel.read | A worklist finding was triaged by staff in the app or by another of the practice's clients (never echoed to the client that made the change). |
worklist.run_completed | clinical.panel.read | A worklist run of the practice completed. |
Generated from the Partner API router and developers_catalog.py. The router docstring is the source of each description.