Social needs & programmes
Health-related social needs as a closed loop: request a validated screen (AHC-HRSN and others) from your system, read the coded findings (SNOMED CT / ICD-10-CM Z codes), make a referral to a community organisation and track it to accepted, completed or declined, with webhooks for completed screens and referral updates. Community care: the VA Community Care, IHS Purchased/Referred Care and TRICARE referrals and authorisations your staff keyed in, with visits used, documentation due dates and what needs attention. Norvya does not authorise care or determine eligibility; the agency's portal remains the system of record. Treating practices only.
- Who calls it
- Core for Federal (via primes), State & local government, Insurers (incl. Medicaid managed care); useful for Concierge doctors, Independent practices, Hospitals and health systems
- Before production
- No gate of its own; your market's “Before production” applies.
- Sandbox
- Build against synthetic members first. What each sandbox holds
- Standards
- AHC-HRSN screen; SNOMED CT / ICD-10-CM Z codes
Markets that use this family
- Concierge doctors (useful)
- Independent practices (useful)
- Federal (via primes) (core)
- State & local government (core)
- Insurers (incl. Medicaid managed care) (core)
- Hospitals and health systems (useful)
Scopes
| Scope | Allows |
|---|---|
programs.community_care.read | Read the VA Community Care, IHS Purchased/Referred Care and TRICARE referrals and authorisations your practice recorded in Norvya, with authorised dates, visits used and documentation deadlines; Norvya never authorises care or determines eligibility (treating practices only). |
social_needs.read | Read a patient's health-related social needs screen (AHC-HRSN core) with the positive domains coded to ICD-10-CM Z-codes and SNOMED CT as Norvya asserts them, and the closed-loop community referrals your practice made, with Gravity-aligned Task status (treating practices only; the patient must share their medical history with you; a positive interpersonal-safety screen never leaves Norvya). |
social_needs.write | Ask a patient to complete a social needs screen (Norvya's generic push), and create and update closed-loop referrals to community organisations (treating practices only). |
Endpoints
Social needs vocabulary: screeners, findings and their codes, referral statuses
The findings a screen can produce with the ICD-10-CM Z-codes and SNOMED CT concepts Norvya asserts (Z59.10 inadequate housing, not the retired Z59.1), the US Core `sdoh` category, the referral statuses and their FHIR Task.status. No patient data.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/social-needs/reference" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/social-needs/reference",
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/social-needs/reference`);
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());{
"screeners": [
{}
],
"not_available": [
{}
],
"findings": [
{
"finding": "food_insecurity",
"display": "Food insecurity",
"domain": "food",
"gravity_domain": "food-insecurity",
"icd10": "Z59.41",
"snomed": "733423003",
"code": {
"coding": [],
"text": "string"
},
"category": [
{}
]
}
],
"withheld_domains": [
"string"
],
"safety_note": "string",
"statuses": [
"string"
],
"open_statuses": [
"string"
],
"closed_statuses": [
"string"
],
"status_aliases": {},
"task_status": {},
"coding_note": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Ask a patient to complete a social needs screen
Creates a request for the AHC-HRSN core screen and sends Norvya's generic "Questionnaire from your care team" push (the instrument name stays in the app). 409 patient_muted_clinic when the patient muted your practice; 422 screener_not_available for PRAPARE (not built), 422 screener_not_supported for anything else. The completed screen raises social_needs.screening_completed.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
ahc_hrsn (the CMS AHC-HRSN core tool). prapare answers 422 screener_not_available: it is not built in Norvya.
Shown to staff in Norvya, never in the push.
Errors specific to this call
409patient_muted_clinic422screener_not_availableAll 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/patients/$MEMBER_REF/social-needs/screenings" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"instrument": "ahc_hrsn",
"note": "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}/social-needs/screenings",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"instrument": "ahc_hrsn",
"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 url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/social-needs/screenings`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"instrument": "ahc_hrsn",
"note": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"screening_ref": "hrs_3f9c1a0b7d2e4c6a8b1d0e2f",
"member_ref": "string",
"instrument": "string",
"status": "requested",
"notification": "generic_push"
}The response is the shape from the schema, not live data.
Try it in your sandbox
A patient's social needs screen, coded findings and your referrals
The latest submitted AHC-HRSN screen: each domain positive or not, the positive findings coded (ICD-10-CM Z-code; SNOMED CT where certain; US Core category sdoh), any screen you requested that is still pending, and the closed-loop referrals YOUR practice made. The patient must share their medical history with you (403 medical_history_not_shared). The interpersonal-safety domain never leaves Norvya.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Errors specific to this call
403medical_history_not_sharedAll 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/social-needs" \
-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}/social-needs",
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)}/social-needs`);
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",
"screened": false,
"latest_screening": {
"screening_ref": "string",
"instrument": "string",
"instrument_version": "string",
"completed_at": "string",
"requested_by_you": false,
"domains": [
{
"domain": "string",
"positive": false
}
],
"unmet_needs_reported": 0,
"findings": [
{
"finding": "food_insecurity",
"display": "Food insecurity",
"domain": "food",
"gravity_domain": "food-insecurity",
"icd10": "Z59.41",
"snomed": "733423003",
"code": {},
"category": []
}
],
"withheld_domains": [
"string"
]
},
"pending_requests": [
{
"screening_ref": "string",
"instrument": "string",
"requested_at": "string"
}
],
"referrals": [
{
"referral_ref": "sdr_5e7a9c1b3d5f7a9c1e3a5b7d",
"member_ref": "string",
"finding": "string",
"domain": "string",
"gravity_domain": "string",
"code": {
"coding": [],
"text": "string"
},
"icd10": "string",
"snomed": "string",
"category": [
{}
],
"resource": {
"name": "Mid-Ohio Food Collective",
"contact": "(614) 555-0100"
},
"status": "sent",
"open": false,
"needs_met": false,
"task_status": {
"system": "http://hl7.org/fhir/task-status",
"code": "requested"
},
"outcome_note": "string",
"created_at": "string",
"sent_at": "string",
"closed_at": "string",
"updated_at": "string"
}
],
"safety_note": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Create a closed-loop referral to a community organisation
One referral for one finding (codes stamped from the finding at write time, so a later terminology change cannot rewrite history). The patient must share their medical history with you. 422 unknown_finding / finding_not_available (interpersonal safety) / invalid_referral_status.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
A finding from GET /v1/social-needs/reference.
An hrsn status, or the alias service_delivered / declined.
Errors specific to this call
422unknown_findingAll 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/social-needs/referrals" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"finding": "food_insecurity",
"resource_name": "Mid-Ohio Food Collective",
"resource_contact": "(614) 555-0100",
"status": "sent",
"note": "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}/social-needs/referrals",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"finding": "food_insecurity",
"resource_name": "Mid-Ohio Food Collective",
"resource_contact": "(614) 555-0100",
"status": "sent",
"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 url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/social-needs/referrals`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"finding": "food_insecurity",
"resource_name": "Mid-Ohio Food Collective",
"resource_contact": "(614) 555-0100",
"status": "sent",
"note": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"referral_ref": "sdr_5e7a9c1b3d5f7a9c1e3a5b7d",
"member_ref": "string",
"finding": "string",
"domain": "string",
"gravity_domain": "string",
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z59.41",
"display": "string"
}
],
"text": "string"
},
"icd10": "string",
"snomed": "string",
"category": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z59.41",
"display": "string"
}
],
"resource": {
"name": "Mid-Ohio Food Collective",
"contact": "(614) 555-0100"
},
"status": "sent",
"open": false,
"needs_met": false,
"task_status": {
"system": "http://hl7.org/fhir/task-status",
"code": "requested"
},
"outcome_note": "string",
"created_at": "string",
"sent_at": "string",
"closed_at": "string",
"updated_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Your practice's closed-loop community referrals
Newest first, at most 500, for patients still linked and sharing their medical history with you.
Parameters
One hrsn status (or alias).
Only referrals still open.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/social-needs/referrals" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/social-needs/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 url = new URL(`${BASE}/v1/social-needs/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());{
"items": [
{
"referral_ref": "sdr_5e7a9c1b3d5f7a9c1e3a5b7d",
"member_ref": "string",
"finding": "string",
"domain": "string",
"gravity_domain": "string",
"code": {
"coding": [],
"text": "string"
},
"icd10": "string",
"snomed": "string",
"category": [
{}
],
"resource": {
"name": "Mid-Ohio Food Collective",
"contact": "(614) 555-0100"
},
"status": "sent",
"open": false,
"needs_met": false,
"task_status": {
"system": "http://hl7.org/fhir/task-status",
"code": "requested"
},
"outcome_note": "string",
"created_at": "string",
"sent_at": "string",
"closed_at": "string",
"updated_at": "string"
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
One closed-loop referral
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/social-needs/referrals/$REFERRAL_REF" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
referral_ref = os.environ["REFERRAL_REF"]
resp = requests.get(
f"{BASE}/v1/social-needs/referrals/{referral_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 referral_ref = process.env.REFERRAL_REF;
const url = new URL(`${BASE}/v1/social-needs/referrals/${encodeURIComponent(referral_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());{
"referral_ref": "sdr_5e7a9c1b3d5f7a9c1e3a5b7d",
"member_ref": "string",
"finding": "string",
"domain": "string",
"gravity_domain": "string",
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z59.41",
"display": "string"
}
],
"text": "string"
},
"icd10": "string",
"snomed": "string",
"category": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z59.41",
"display": "string"
}
],
"resource": {
"name": "Mid-Ohio Food Collective",
"contact": "(614) 555-0100"
},
"status": "sent",
"open": false,
"needs_met": false,
"task_status": {
"system": "http://hl7.org/fhir/task-status",
"code": "requested"
},
"outcome_note": "string",
"created_at": "string",
"sent_at": "string",
"closed_at": "string",
"updated_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Move a referral along its loop (sent, accepted, completed, declined...)
Any hrsn status (aliases service_delivered -> completed, declined -> declined_by_patient). A closed status stamps closed_at; reopening clears it. Other clients of your practice receive social_needs.referral_updated; your own client does not.
Parameters
Request body
Content type: application/json (required)
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s -X POST "https://norvya.fly.dev/v1/social-needs/referrals/$REFERRAL_REF/status" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "accepted",
"outcome_note": "Enrolled in weekly food box."
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
referral_ref = os.environ["REFERRAL_REF"]
resp = requests.post(
f"{BASE}/v1/social-needs/referrals/{referral_ref}/status",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"status": "accepted",
"outcome_note": "Enrolled in weekly food box.",
},
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 referral_ref = process.env.REFERRAL_REF;
const url = new URL(`${BASE}/v1/social-needs/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": "accepted",
"outcome_note": "Enrolled in weekly food box."
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"referral_ref": "sdr_5e7a9c1b3d5f7a9c1e3a5b7d",
"member_ref": "string",
"finding": "string",
"domain": "string",
"gravity_domain": "string",
"code": {
"coding": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z59.41",
"display": "string"
}
],
"text": "string"
},
"icd10": "string",
"snomed": "string",
"category": [
{
"system": "http://hl7.org/fhir/sid/icd-10-cm",
"code": "Z59.41",
"display": "string"
}
],
"resource": {
"name": "Mid-Ohio Food Collective",
"contact": "(614) 555-0100"
},
"status": "sent",
"open": false,
"needs_met": false,
"task_status": {
"system": "http://hl7.org/fhir/task-status",
"code": "requested"
},
"outcome_note": "string",
"created_at": "string",
"sent_at": "string",
"closed_at": "string",
"updated_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
A patient's VA / IHS / TRICARE referrals and authorisations at your practice
The referrals and authorisations your practice recorded from the agency (agency_care): numbers, authorised services, dates and visits, visits counted, documents sent, the advisory flags. Norvya does not authorise care, determine eligibility or submit to any agency system.
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/community-care/authorizations" \
-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}/community-care/authorizations",
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)}/community-care/authorizations`);
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": [
{
"authorization_ref": "cca_1a2b3c4d5e6f7a8b9c0d1e2f",
"member_ref": "string",
"agency": "va",
"administrator": "string",
"referral_number": "string",
"authorization_number": "string",
"episode_of_care": "string",
"referring_facility": "string",
"authorized_services": [
"string"
],
"authorized_from": "string",
"authorized_to": "string",
"authorized_visits": 0,
"documentation_due_on": "string",
"status": "authorized",
"flags": {},
"created_at": "string",
"updated_at": "string",
"disclaimer": "string",
"visits": [
{}
],
"documents": [
{}
]
}
],
"disclaimer": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Your practice's community-care referrals and authorisations
Parameters
va | ihs | tricare | other
Only rows with needs_attention.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/community-care/authorizations" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/community-care/authorizations",
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/community-care/authorizations`);
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": [
{
"authorization_ref": "cca_1a2b3c4d5e6f7a8b9c0d1e2f",
"member_ref": "string",
"agency": "va",
"administrator": "string",
"referral_number": "string",
"authorization_number": "string",
"episode_of_care": "string",
"referring_facility": "string",
"authorized_services": [
"string"
],
"authorized_from": "string",
"authorized_to": "string",
"authorized_visits": 0,
"documentation_due_on": "string",
"status": "authorized",
"flags": {},
"created_at": "string",
"updated_at": "string",
"disclaimer": "string",
"visits": [
{}
],
"documents": [
{}
]
}
],
"disclaimer": "string"
}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 |
|---|---|---|
social_needs.referral_updated | social_needs.read | A closed-loop community referral of the practice was created or changed status. |
social_needs.screening_completed | social_needs.read | A patient submitted an AHC-HRSN screen. |
Generated from the Partner API router and developers_catalog.py. The router docstring is the source of each description.