Norvya Developers
Get a sandbox
API reference · Clinical

Clinical intelligence

Built — gated23 endpoints · 5 webhook events · OpenAPI tag clinical

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
Note. Behaviour change (16 October 2026): GET /v1/panel/worklist now applies staff triage. Items your staff (or your system) dismissed, snoozed or marked done are hidden by default; pass include_triaged=true to get them back with their triage state.

Markets that use this family

Scopes

ScopeAllows
clinical.brief.readRead the clinician pre-visit brief for a patient (medical practices only; per-section consent).
clinical.loops.writeClose 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.readRead the practice's ranked panel worklist and start worklist runs (medical practices only).
clinical.panel.writeTriage 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.readRead a patient's care gaps and between-visit home-data signals (medical practices only; consent and data basis respected).
clinical.ratings.writeRecord 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.readRead 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.requestAsk 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

GET/v1/patients/{member_ref}/briefclinical.brief.read

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

member_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Validation Error429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/patients/$MEMBER_REF/brief" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Your practice's ranked worklist: who needs attention and why

GET/v1/panel/worklistclinical.panel.read

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

severityquery · string | null
domainquery · string | null
member_refquery · string | null

One member (mr_... or ext:...).

include_clearquery · boolean
include_triagedquery · boolean

Also show findings staff (or your system) addressed, dismissed or snoozed.

limitquery · integer
offsetquery · integer
langquery · string

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_practice

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/panel/worklist" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Start a worklist run for your practice

POST/v1/panel/worklist/runsclinical.panel.read

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

as_ofquery · string | null

YYYY-MM-DD; default today's data. A past date runs a point-in-time evaluation.

Errors specific to this call

409panel_worklist_no_patients

All responses

202Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s -X POST "https://norvya.fly.dev/v1/panel/worklist/runs" \
  -H "Authorization: Bearer $TOKEN"
Response 202
{
  "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.

Status of one worklist run

GET/v1/panel/worklist/runs/{run_id}clinical.panel.read

Counts and progress only (no patient rows). 404 panel_worklist_run_not_found for a run that is not your practice's.

Parameters

run_idpath · integerrequired

Errors specific to this call

404panel_worklist_run_not_found

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/panel/worklist/runs/$RUN_ID" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Care gaps for one patient

GET/v1/patients/{member_ref}/care-gapsclinical.patient.read

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

member_refpath · stringrequired
as_ofquery · string | null

YYYY-MM-DD: evaluate as of a past date.

known_as_ofquery · string | null

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 Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/patients/$MEMBER_REF/care-gaps" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Between-visit signals from the patient's own home data

GET/v1/patients/{member_ref}/between-visitclinical.patient.read

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

member_refpath · stringrequired
as_ofquery · string | null

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/patients/$MEMBER_REF/between-visit" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Your practice's clinicians, as staff_ref values

GET/v1/panel/cliniciansclinical.loops.write

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 Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/panel/clinicians" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "clinicians": [
    {
      "staff_ref": "string",
      "name": "string",
      "role": "string",
      "title": "string"
    }
  ]
}

The response is the shape from the schema, not live data.

Triage one worklist finding (addressed, snoozed, dismissed or reopened)

POST/v1/panel/worklist/items/{item_ref}/triageclinical.panel.write

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

item_refpath · stringrequired

Request body

Content type: application/json (required)

statestringrequired

open | addressed | snoozed | dismissed

reason_codestring | null
notestring | null

Required with reason_code other.

snooze_untilstring | null

YYYY-MM-DD, 1..365 days ahead (snoozed).

snooze_daysinteger | null

Errors specific to this call

404worklist_item_not_found422worklist_reason_required

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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
  }'
Response 200
{
  "item_ref": "string",
  "member_ref": "string",
  "code": "string",
  "domain": "string",
  "severity": "string",
  "triage": {}
}

The response is the shape from the schema, not live data.

A patient's results awaiting follow-up (acknowledge, inform, act, close)

GET/v1/patients/{member_ref}/resultsclinical.patient.read

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

member_refpath · stringrequired
statusquery · string

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 Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/patients/$MEMBER_REF/results" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Record that a named clinician acknowledged a result

POST/v1/patients/{member_ref}/results/{result_ref}/acknowledgeclinical.loops.write

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

member_refpath · stringrequired
result_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

Request body

Content type: application/json (required)

clinicianstringrequired

staff_ref of the clinician who did it (GET /v1/panel/clinicians).

notestring | null

Errors specific to this call

409already_closed422clinician_not_found

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 200
{
  "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.

Record that the patient was told

POST/v1/patients/{member_ref}/results/{result_ref}/informedclinical.loops.write

By the named clinician, by `method`. 422 invalid_inform_method.

Parameters

member_refpath · stringrequired
result_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

Request body

Content type: application/json (required)

clinicianstringrequired

staff_ref of the clinician who did it (GET /v1/panel/clinicians).

notestring | null
methodstringrequired

phone | portal_message | in_person | letter | other

Errors specific to this call

422invalid_inform_method

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 200
{
  "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.

Record the action taken on a result

POST/v1/patients/{member_ref}/results/{result_ref}/actionclinical.loops.write

What was done, in the clinician's words, and what kind of thing it was. 422 action_text_required, invalid_action_link_kind.

Parameters

member_refpath · stringrequired
result_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

Request body

Content type: application/json (required)

clinicianstringrequired
action_takenstringrequired
link_kindstring | null

referral | order | message | note | appointment | none

Errors specific to this call

422action_text_required

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 200
{
  "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.

Close the loop on a result

POST/v1/patients/{member_ref}/results/{result_ref}/closeclinical.loops.write

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

member_refpath · stringrequired
result_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

Request body

Content type: application/json (required)

clinicianstringrequired
reasonstring | null

Errors specific to this call

409acknowledge_first409patient_not_informed409action_not_recorded

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 200
{
  "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.

Referrals your practice tracks for a patient, with their loop status

GET/v1/patients/{member_ref}/referralsclinical.patient.read

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

member_refpath · stringrequired
openquery · boolean

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/patients/$MEMBER_REF/referrals" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Move a tracked referral (scheduled, seen, report received, completed, ...)

POST/v1/patients/{member_ref}/referrals/{referral_ref}/statusclinical.loops.write

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

member_refpath · stringrequired
referral_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

Request body

Content type: application/json (required)

statusstringrequired

accepted | scheduled | appointment_scheduled | seen | no_show | report_received | completed | declined | cancelled | unreachable | closed_unmet | pending

occurred_onstring | null

YYYY-MM-DD: the appointment / visit / report date (required for scheduled and seen).

notestring | null
report_sourcestring | null

report_received: direct | fax | portal | mail | phone | in_person | patient_reported | upload

reportstring | null

report_received: the report text, or ...

document_refstring | null

... your document id instead.

Errors specific to this call

409no_report_to_send

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 200
{
  "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.

Questionnaires you may request

GET/v1/instrumentsinstruments.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 Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/instruments" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Ask a patient to complete a questionnaire

POST/v1/members/{member_ref}/instrument-requestsinstruments.request

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

member_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

Request body

Content type: application/json (required)

instrument_idstringrequired
external_idstring | null

Your id: a retry with it returns the same request.

Errors specific to this call

409member_not_accepting_requests

All responses

201Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 201
{
  "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.

A patient's questionnaire scores

GET/v1/members/{member_ref}/instrument-responsesinstruments.read

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

member_refpath · stringrequired
statusquery · string

submitted | requested | all

response_refquery · string | null

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/members/$MEMBER_REF/instrument-responses" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

Sandbox: make a synthetic patient complete a requested questionnaire

POST/v1/members/{member_ref}/instrument-requests/{response_ref}/simulate-completioninstruments.request

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

member_refpath · stringrequired
response_refpath · stringrequired

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

404elsewhere

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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
  }'
Response 200
{
  "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.

Record a clinician-rated measure a named clinician assigned

POST/v1/patients/{member_ref}/clinician-ratingsclinical.ratings.write

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

member_refpath · stringrequired

EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens

Request body

Content type: application/json (required)

instrument_idstringrequired
valuesarray of numberrequired

One value per item, in item order (GET /v1/instruments clinician_rated[].items).

clinicianstringrequired

staff_ref of the clinician who assigned it (GET /v1/panel/clinicians).

Errors specific to this call

422clinician_not_found409member_not_sharing

All responses

201Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 201
{
  "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.

Ask a caseload to complete a questionnaire

POST/v1/instrument-requests/batchinstruments.request

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)

instrument_idstringrequired
member_refsarray of string | null

Up to 500 of your members.

cohortobject | null
external_idstring | null

Your id: a retry returns the same batch.

All responses

202Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes 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"
  }'
Response 202
{
  "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.

Your caseload batches, newest first

GET/v1/instrument-requests/batchinstruments.request

The batches THIS client started for your organisation (no member data).

Parameters

limitquery · integer

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/instrument-requests/batch" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

A caseload batch: progress and results

GET/v1/instrument-requests/batch/{batch_ref}instruments.request

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

batch_refpath · stringrequired

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/instrument-requests/batch/$BATCH_REF" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "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.

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

EventScopeWhen
instrument.batch_dispatchedinstruments.requestA caseload questionnaire batch you started finished sending its requests.
instrument.completedinstruments.readA patient completed a questionnaire this practice requested (after the crisis gate has run).
patient.attention_neededclinical.panel.readA patient newly entered the top priority band in a completed current-data run.
worklist.item_triagedclinical.panel.readA 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_completedclinical.panel.readA 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.

↑ ↓ to moveEnter to open