Public-health reporting
Report from your own system to the state, one explicit call at a time. Immunization registry: readiness of your connection, the patient's record with a reference per dose, a preview of what the HL7 v2 VXU would contain, the submission itself with the registry's acknowledgement and retry state, and history queries (Z34 / Z44) whose returned doses land on the record. Electronic case reporting: match a patient against the CSTE / CDC / APHL trigger codes (a match is never a statement that a case is reportable), confirm or dismiss, build the FHIR eICR, transmit it, and read or file the Reportability Response. Syndromic surveillance (NSSP): record an emergency or urgent-care visit with its chief complaint and send its HL7 v2 ADT message. Webhooks for registry acknowledgements and Reportability Responses. Treating practices and hospitals only; every transmission is audited before it leaves.
- Who calls it
- Core for State & local government; useful for Independent practices, Hospitals and health systems
- Before production
- A signed data-exchange agreement with each jurisdiction (immunization registry, eCR on the AIMS platform, NSSP onboarding), and the feature switched on for your organisation. Until then each call answers a not-active code. A sandbox simulates every transmission and never reaches a state.
- Sandbox
- Build against synthetic members first. What each sandbox holds
- Standards
- HL7 v2 VXU and ADT; FHIR eICR; CSTE / CDC / APHL trigger codes
Markets that use this family
- Independent practices (useful)
- State & local government (core)
- Hospitals and health systems (useful)
Scopes
| Scope | Allows |
|---|---|
public_health.case_reports.read | Read electronic case reporting readiness, candidates, reports and the Reportability Responses public health sent back (treating practices and hospitals). |
public_health.case_reports.write | Electronic case reporting: match a patient against the loaded trigger codes, confirm or dismiss a candidate, build the eICR, transmit it, and file a Reportability Response (treating practices and hospitals; refused unless case reporting is active for you). |
public_health.immunizations.read | Read your registry connection's readiness, the patient's immunization record, your submissions and their acknowledgements, and query the registry for a patient's history (treating practices and hospitals; the patient must share their medical history with you). |
public_health.immunizations.write | Report a patient's immunization to your state immunization registry (HL7 v2 VXU) and preview what would be sent; an explicit act, audited, refused unless your registry connection is active under a signed state agreement (treating practices and hospitals). |
public_health.syndromic.read | Read your syndromic surveillance readiness and what was sent where (no patient data in the message list). |
public_health.syndromic.write | Syndromic surveillance (NSSP): record an emergency or urgent-care visit and send its HL7 v2 ADT message, one explicit send at a time (registered reporting facilities only). |
Endpoints
Your immunization registry connection: readiness, never credentials
Whether a dose report or history query would be transmitted right now, and if not, why: the connection's status, the state, whether an administrator attested to the state's data-exchange agreement, and the onboarding steps still missing. Never the endpoint, the user name, the password or the facility id. Activation itself is done by your organisation's administrator in Norvya, not through the API.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/public-health/iis" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/iis",
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/public-health/iis`);
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());{
"configured": false,
"active": false,
"status": "active",
"jurisdiction": "MN",
"registry": "Minnesota Immunization Information Connection (MIIC)",
"agreement_attested": false,
"agreement_attested_at": "string",
"onboarding": [
{
"step": "agreement_signed",
"status": "complete",
"mandatory": false
}
],
"missing": [
"string"
],
"attention": {},
"query_profiles": {},
"retry_policy": {},
"standard": "string",
"simulated": false,
"posture": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Your registry submissions and their acknowledgements
Newest first: dose reports (VXU) and history queries (QBP) your organisation sent — through the API or from Norvya's own screens — with the registry's ACK code, Norvya's retry state (5 min, 30 min, 2 h, 12 h, 24 h; then failed_permanent) and the review queue (needs_review: a data problem the registry reported, never retried automatically). Patients no longer linked to you are left out.
Parameters
Filter by status, e.g. needs_review.
VXU | QBP
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/public-health/iis/submissions" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/iis/submissions",
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/public-health/iis/submissions`);
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": [
{
"submission_ref": "iis_9a2b4c6d8e0f1a3b5c7d9e1f",
"message_type": "VXU",
"query_profile": null,
"member_ref": "string",
"immunization_ref": "string",
"status": "accepted",
"accepted": false,
"ack_code": "AA",
"error_code": null,
"registry_error_code": "string",
"attempt_count": 0,
"max_attempts": 0,
"next_attempt_at": "string",
"created_at": "string",
"sent_at": "string",
"simulated": false
}
],
"posture": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
One registry submission
Status, ACK code and retry state of one submission. Another organisation's submission and an unknown ref are the same 404 iis_submission_not_found.
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/public-health/iis/submissions/$SUBMISSION_REF" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
submission_ref = os.environ["SUBMISSION_REF"]
resp = requests.get(
f"{BASE}/v1/public-health/iis/submissions/{submission_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 submission_ref = process.env.SUBMISSION_REF;
const url = new URL(`${BASE}/v1/public-health/iis/submissions/${encodeURIComponent(submission_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());{
"submission_ref": "iis_9a2b4c6d8e0f1a3b5c7d9e1f",
"message_type": "VXU",
"query_profile": null,
"member_ref": "string",
"immunization_ref": "string",
"status": "accepted",
"accepted": false,
"ack_code": "AA",
"error_code": null,
"registry_error_code": "string",
"attempt_count": 0,
"max_attempts": 0,
"next_attempt_at": "string",
"created_at": "string",
"sent_at": "string",
"simulated": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
A patient's immunization record (with refs to report)
Completed doses on the patient's Norvya record, newest first, each with the immunization_ref you report it by, plus Norvya's adult forecast. The patient must share their medical history with your organisation (403 medical_history_not_shared).
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/immunizations" \
-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}/immunizations",
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)}/immunizations`);
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": [
{
"immunization_ref": "imz_3f9c1a0b7d2e4c6a8b1d0e2f",
"vaccine_key": "tdap",
"group": "tdap_td",
"cvx": "115",
"display": "Tdap (tetanus, diphtheria, whooping cough)",
"occurrence_date": "2026-09-30",
"dose_number": 0,
"lot_number": "string",
"source": "clinic",
"verification": "staff_recorded"
}
],
"forecast": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
What a registry report of one dose would contain
Builds the HL7 v2.5.1 VXU^V04 with the same builder a submission uses (your state's published local rules applied) and returns a summary: the segments, the vaccine, whether it reports a new administration (00) or a historical dose (01), and whether the VFC eligibility observation is included. Not the HL7 text. A dose the builder cannot report truthfully is refused with its reason (422 iis_uncoded_dose, iis_patient_incomplete, iis_vfc_eligibility_unknown ...). Needs a configured connection (409 iis_not_configured); nothing is sent.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Errors specific to this call
422iis_uncoded_doseAll 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/immunizations/$IMMUNIZATION_REF/iis-preview" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
immunization_ref = os.environ["IMMUNIZATION_REF"]
resp = requests.get(
f"{BASE}/v1/patients/{member_ref}/immunizations/{immunization_ref}/iis-preview",
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 immunization_ref = process.env.IMMUNIZATION_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/immunizations/${encodeURIComponent(immunization_ref)}/iis-preview`);
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",
"immunization_ref": "string",
"message_type": "VXU^V04",
"profile": "Z22",
"segments": [
"MSH",
"PID",
"ORC",
"RXA",
"OBX"
],
"vaccine": {},
"information_source": "00",
"vfc_eligibility_included": false,
"jurisdiction": "string",
"would_transmit": false,
"simulated": false,
"standard": "string",
"note": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Report one dose to your state immunization registry
An explicit act: builds the VXU and sends it over the CDC IIS web service to your registry, reads the ACK and answers with the submission. A transport failure or an application reject (AR / CR) is retried on the backoff ladder; an application error (AE / CE) or a dose Norvya cannot report truthfully waits in needs_review for your staff. Refused unless the connection is active under an attested state agreement (409 iis_not_active, nothing recorded). The disclosure is audited before it leaves and refused when the audit log is down. Subscribe to public_health.iis_ack_received for ACKs that arrive on a retry.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
All 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/immunizations/$IMMUNIZATION_REF/iis-submissions" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
member_ref = os.environ["MEMBER_REF"]
immunization_ref = os.environ["IMMUNIZATION_REF"]
resp = requests.post(
f"{BASE}/v1/patients/{member_ref}/immunizations/{immunization_ref}/iis-submissions",
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 immunization_ref = process.env.IMMUNIZATION_REF;
const url = new URL(`${BASE}/v1/patients/${encodeURIComponent(member_ref)}/immunizations/${encodeURIComponent(immunization_ref)}/iis-submissions`);
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());{
"submission_ref": "iis_9a2b4c6d8e0f1a3b5c7d9e1f",
"message_type": "VXU",
"query_profile": null,
"member_ref": "string",
"immunization_ref": "string",
"status": "accepted",
"accepted": false,
"ack_code": "AA",
"error_code": null,
"registry_error_code": "string",
"attempt_count": 0,
"max_attempts": 0,
"next_attempt_at": "string",
"created_at": "string",
"sent_at": "string",
"simulated": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
Ask your registry for a patient's immunization history
QBP^Q11 (Z34 history, or Z44 evaluated history and forecast) for one patient. Doses the registry returns are added to the patient's record (source iis, document reviewed; duplicates skipped) and the record comes back with Norvya's adult forecast. A candidate list, no match or a protected record is reported in query_status and nothing is imported. The query sends the patient's identity to the state, so it is audited as a disclosure first (fail closed), and the answer as a read. Needs an active connection (409 iis_not_active). A registry outage is retried in the background and reported here as 502 iis_unreachable.
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
409iis_not_active502iis_unreachableAll 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/iis-queries" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profile": "Z44"
}'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}/iis-queries",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"profile": "Z44",
},
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)}/iis-queries`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"profile": "Z44"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"member_ref": "string",
"submission": {
"submission_ref": "iis_9a2b4c6d8e0f1a3b5c7d9e1f",
"message_type": "VXU",
"query_profile": null,
"member_ref": "string",
"immunization_ref": "string",
"status": "accepted",
"accepted": false,
"ack_code": "AA",
"error_code": null,
"registry_error_code": "string",
"attempt_count": 0,
"max_attempts": 0,
"next_attempt_at": "string",
"created_at": "string",
"sent_at": "string",
"simulated": false
},
"query_status": "OK",
"patients_returned": 0,
"imported": {},
"history": [
{
"immunization_ref": "imz_3f9c1a0b7d2e4c6a8b1d0e2f",
"vaccine_key": "tdap",
"group": "tdap_td",
"cvx": "115",
"display": "Tdap (tetanus, diphtheria, whooping cough)",
"occurrence_date": "2026-09-30",
"dose_number": 0,
"lot_number": "string",
"source": "clinic",
"verification": "staff_recorded"
}
],
"forecast": {},
"registry_forecast": {},
"simulated": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
Electronic case reporting readiness
Whether case reporting is active for your organisation and, if not, what is missing; the trigger-code release Norvya matches against (the CSTE / CDC / APHL eRSD; Norvya ships no condition list of its own); what trigger matching can and cannot see; and what is not claimed.
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/public-health/case-reporting" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/case-reporting",
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/public-health/case-reporting`);
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());{
"active": false,
"missing": [
"case_reporting_not_enabled"
],
"jurisdiction": "string",
"adapter": "direct",
"ersd": {},
"respect_sharing_flags": false,
"coverage": {},
"reportability_determined_by": "string",
"not_claimed": [
"string"
],
"ig_version": "string",
"simulated": false,
"posture": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Match a patient's record against the reportable-condition trigger codes
Matches the patient's coded conditions, labs and immunizations against the active eRSD release and raises a candidate per matched condition (already-known ones come back too). A match is NOT a statement that the case is reportable: only the Reportability Response decides. An empty list is not a statement that there is nothing to report (see coverage). 409 case_reporting_not_active; 409 no_ersd_release.
Parameters
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Errors specific to this call
409no_ersd_releaseAll 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/case-reports/evaluate" \
-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.post(
f"{BASE}/v1/patients/{member_ref}/case-reports/evaluate",
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)}/case-reports/evaluate`);
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());{
"member_ref": "string",
"candidates": [
{
"candidate_ref": "ecc_1b3d5f7a9c0e2a4c6e8a0b2d",
"member_ref": "string",
"status": "candidate",
"trigger": {},
"condition": {},
"source": {},
"sharing_blocked": false,
"override_recorded": false,
"detected_at": "string",
"reviewed_at": "string",
"reportability": "string"
}
],
"coverage": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Case-report candidates waiting for review, and their history
Parameters
candidate | confirmed | dismissed | sent | failed
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/public-health/case-reports/candidates" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/case-reports/candidates",
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/public-health/case-reports/candidates`);
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": [
{
"candidate_ref": "ecc_1b3d5f7a9c0e2a4c6e8a0b2d",
"member_ref": "string",
"status": "candidate",
"trigger": {},
"condition": {},
"source": {},
"sharing_blocked": false,
"override_recorded": false,
"detected_at": "string",
"reviewed_at": "string",
"reportability": "string"
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Confirm or dismiss a case-report candidate
Your clinician's decision, recorded from your system. A candidate whose trigger data the patient keeps closed (sharing_blocked) cannot be confirmed here: overriding a patient's sharing choice needs a clinician's recorded reason inside Norvya (409 override_requires_clinician). A sent candidate cannot be changed (409 already_sent).
Parameters
Request body
Content type: application/json (required)
confirm | dismiss
Errors specific to this call
409override_requires_clinician409already_sentAll 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/public-health/case-reports/candidates/$CANDIDATE_REF/review" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "confirm",
"note": "string"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
candidate_ref = os.environ["CANDIDATE_REF"]
resp = requests.post(
f"{BASE}/v1/public-health/case-reports/candidates/{candidate_ref}/review",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"action": "confirm",
"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 candidate_ref = process.env.CANDIDATE_REF;
const url = new URL(`${BASE}/v1/public-health/case-reports/candidates/${encodeURIComponent(candidate_ref)}/review`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"action": "confirm",
"note": "string"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"candidate_ref": "ecc_1b3d5f7a9c0e2a4c6e8a0b2d",
"member_ref": "string",
"status": "candidate",
"trigger": {},
"condition": {},
"source": {},
"sharing_blocked": false,
"override_recorded": false,
"detected_at": "string",
"reviewed_at": "string",
"reportability": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Build the eICR for a confirmed candidate
Builds the FHIR eICR (hl7.fhir.us.ecr 2.1.2) from the patient's record through Norvya's one FHIR builder and stores it encrypted, ready to transmit. 42 CFR Part 2 records are always withheld; clinical detail behind a closed sharing flag is withheld unless a clinician recorded an override. The document itself is not returned. Audited as a disclosure (fail closed). 409 candidate_not_confirmed.
Parameters
Errors specific to this call
409candidate_not_confirmedAll 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/public-health/case-reports/candidates/$CANDIDATE_REF/build" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
candidate_ref = os.environ["CANDIDATE_REF"]
resp = requests.post(
f"{BASE}/v1/public-health/case-reports/candidates/{candidate_ref}/build",
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 candidate_ref = process.env.CANDIDATE_REF;
const url = new URL(`${BASE}/v1/public-health/case-reports/candidates/${encodeURIComponent(candidate_ref)}/build`);
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());{
"report": {
"report_ref": "ecr_7c9e1a3b5d7f9a1c3e5a7b9d",
"candidate_ref": "string",
"member_ref": "string",
"format": "fhir_eicr",
"set_id": "string",
"version": 0,
"status": "sent",
"transmitted_at": "string",
"part2_withheld": 0,
"error_code": "string",
"created_at": "string",
"reportability_response": {},
"rr_received_at": "string",
"simulated": false
},
"build": {}
}The response is the shape from the schema, not live data.
Try it in your sandbox
Your eICRs and the Reportability Responses that came back
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/public-health/case-reports" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/case-reports",
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/public-health/case-reports`);
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": [
{
"report_ref": "ecr_7c9e1a3b5d7f9a1c3e5a7b9d",
"candidate_ref": "string",
"member_ref": "string",
"format": "fhir_eicr",
"set_id": "string",
"version": 0,
"status": "sent",
"transmitted_at": "string",
"part2_withheld": 0,
"error_code": "string",
"created_at": "string",
"reportability_response": {},
"rr_received_at": "string",
"simulated": false
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
One eICR and its Reportability Response
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/public-health/case-reports/$REPORT_REF" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
report_ref = os.environ["REPORT_REF"]
resp = requests.get(
f"{BASE}/v1/public-health/case-reports/{report_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 report_ref = process.env.REPORT_REF;
const url = new URL(`${BASE}/v1/public-health/case-reports/${encodeURIComponent(report_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());{
"report_ref": "ecr_7c9e1a3b5d7f9a1c3e5a7b9d",
"candidate_ref": "string",
"member_ref": "string",
"format": "fhir_eicr",
"set_id": "string",
"version": 0,
"status": "sent",
"transmitted_at": "string",
"part2_withheld": 0,
"error_code": "string",
"created_at": "string",
"reportability_response": {},
"rr_received_at": "string",
"simulated": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
Transmit a built eICR to public health
Hands the stored eICR to your organisation's configured adapter (Direct to the AIMS platform, or a drop directory your bridge collects). An explicit act, audited before it leaves and refused when the audit log is down. 409 already_sent; 409 document_not_stored; a failed hand-off marks the report failed and answers 503 with the adapter's code.
Parameters
Errors specific to this call
409already_sent409document_not_stored503withAll 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/public-health/case-reports/$REPORT_REF/transmit" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
report_ref = os.environ["REPORT_REF"]
resp = requests.post(
f"{BASE}/v1/public-health/case-reports/{report_ref}/transmit",
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 report_ref = process.env.REPORT_REF;
const url = new URL(`${BASE}/v1/public-health/case-reports/${encodeURIComponent(report_ref)}/transmit`);
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());{
"report_ref": "ecr_7c9e1a3b5d7f9a1c3e5a7b9d",
"candidate_ref": "string",
"member_ref": "string",
"format": "fhir_eicr",
"set_id": "string",
"version": 0,
"status": "sent",
"transmitted_at": "string",
"part2_withheld": 0,
"error_code": "string",
"created_at": "string",
"reportability_response": {},
"rr_received_at": "string",
"simulated": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
File the Reportability Response public health sent
For organisations whose own system receives the RR. A FHIR RR is read for its determinations; a CDA RR is stored and marked not parsed. 413 file_too_large; 415 unsupported_media_type; 422 unreadable_response.
Parameters
Request body
The Reportability Response document as public health sent it: a FHIR Bundle (JSON) or a CDA document (XML, stored but not parsed). At most 20 MB.
Content type: application/fhir+json, application/json, application/xml, text/xml, application/hl7-cda+xml (required)
Errors specific to this call
415unsupported_media_type422unreadable_responseAll 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/public-health/case-reports/$REPORT_REF/response" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/fhir+json" \
--data-binary @your_body_fileimport os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
report_ref = os.environ["REPORT_REF"]
with open("your_body_file", "rb") as fh:
resp = requests.post(
f"{BASE}/v1/public-health/case-reports/{report_ref}/response",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/fhir+json"},
data=fh,
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
import { readFile } from "node:fs/promises";
const BASE = "https://norvya.fly.dev";
const report_ref = process.env.REPORT_REF;
const url = new URL(`${BASE}/v1/public-health/case-reports/${encodeURIComponent(report_ref)}/response`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/fhir+json",
},
body: await readFile("your_body_file"),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"report_ref": "ecr_7c9e1a3b5d7f9a1c3e5a7b9d",
"candidate_ref": "string",
"member_ref": "string",
"format": "fhir_eicr",
"set_id": "string",
"version": 0,
"status": "sent",
"transmitted_at": "string",
"part2_withheld": 0,
"error_code": "string",
"created_at": "string",
"reportability_response": {},
"rr_received_at": "string",
"simulated": false
}The response is the shape from the schema, not live data.
Syndromic surveillance readiness
What is configured and what is missing before your organisation can send: the facility registration and its NSSP onboarding attestation, the jurisdiction, the syndromic endpoint. Built against the PHIN Messaging Guide R1.1 + 2012 addendum and not NIST-validated (see not_claimed).
All responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/public-health/syndromic/status" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/syndromic/status",
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/public-health/syndromic/status`);
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());{
"ready": false,
"missing": [
"facility_not_registered"
],
"facility": {},
"jurisdiction": "string",
"messages_sent": 0,
"triggers": [
"string"
],
"settings": [
"string"
],
"profile_built_against": "string",
"source": {},
"not_claimed": [
"string"
],
"simulated": false,
"posture": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Record an emergency or urgent-care visit
The encounter as your facility records it, keyed on your visit number (recording the same number again updates it). Chief complaint in the patient's own words: Norvya never derives one, and a visit without it is never sent. Nothing is sent by recording. 409 syndromic_not_onboarded; 409 visit_number_in_use (that number belongs to another patient).
EMR vendors also pass vendor (the practice) with a vendor-issued token. EMR vendor tokens
Request body
Content type: application/json (required)
ISO 8601 date-time, UTC.
E emergency | I inpatient | O outpatient
In the patient's own words; a visit without one is never sent.
Errors specific to this call
409syndromic_not_onboarded409visit_number_in_useAll 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/public-health/syndromic/visits" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"member_ref": "string",
"visit_number": "ED-2026-000123",
"arrived_at": "2026-10-15T14:05:00Z",
"patient_class": "E",
"chief_complaint": "cough and fever 3 days",
"disposition": "string",
"discharged_at": "string",
"age_years": 0,
"diagnoses": [
{
"code": "J06.9",
"system": "I10",
"text": "string"
}
]
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.post(
BASE + "/v1/public-health/syndromic/visits",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"member_ref": "string",
"visit_number": "ED-2026-000123",
"arrived_at": "2026-10-15T14:05:00Z",
"patient_class": "E",
"chief_complaint": "cough and fever 3 days",
"disposition": "string",
"discharged_at": "string",
"age_years": 0,
"diagnoses": [
{
"code": "J06.9",
"system": "I10",
"text": "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/public-health/syndromic/visits`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"member_ref": "string",
"visit_number": "ED-2026-000123",
"arrived_at": "2026-10-15T14:05:00Z",
"patient_class": "E",
"chief_complaint": "cough and fever 3 days",
"disposition": "string",
"discharged_at": "string",
"age_years": 0,
"diagnoses": [
{
"code": "J06.9",
"system": "I10",
"text": "string"
}
]
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"visit_ref": "ssv_2c4e6a8b0d2f4a6c8e0a2b4d",
"member_ref": "string",
"visit_number": "string",
"arrived_at": "string",
"discharged_at": "string",
"patient_class": "string",
"chief_complaint_recorded": false,
"disposition": "string",
"age_years": 0,
"diagnoses": [
{}
],
"reportable": false,
"not_reportable_reason": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Visits on file
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/public-health/syndromic/visits" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/syndromic/visits",
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/public-health/syndromic/visits`);
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": [
{
"visit_ref": "ssv_2c4e6a8b0d2f4a6c8e0a2b4d",
"member_ref": "string",
"visit_number": "string",
"arrived_at": "string",
"discharged_at": "string",
"patient_class": "string",
"chief_complaint_recorded": false,
"disposition": "string",
"age_years": 0,
"diagnoses": [
{}
],
"reportable": false,
"not_reportable_reason": "string"
}
]
}The response is the shape from the schema, not live data.
Try it in your sandbox
Send one visit's syndromic ADT message
Builds the HL7 v2.5.1 ADT (A01 / A03 / A04 / A08) and hands it to your endpoint's adapter — one explicit send; Norvya never transmits on its own. The same content for the same visit and trigger is refused (409 duplicate_message: double counting inflates a surveillance signal); 409 chief_complaint_missing. Audited before it leaves, refused when the audit log is down.
Parameters
Request body
Content type: application/json (required)
A01 | A03 | A04 | A08
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/public-health/syndromic/visits/$VISIT_REF/send" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"trigger": "A04"
}'import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
visit_ref = os.environ["VISIT_REF"]
resp = requests.post(
f"{BASE}/v1/public-health/syndromic/visits/{visit_ref}/send",
headers={"Authorization": f"Bearer {TOKEN}"},
json={
"trigger": "A04",
},
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 visit_ref = process.env.VISIT_REF;
const url = new URL(`${BASE}/v1/public-health/syndromic/visits/${encodeURIComponent(visit_ref)}/send`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"trigger": "A04"
}),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"message_ref": "ssm_5e7a9c1b3d5f7a9c1e3a5b7d",
"visit_ref": "string",
"trigger": "string",
"control_id": "string",
"jurisdiction": "string",
"status": "string",
"sent_at": "string",
"warnings": [
"string"
],
"simulated": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
What was sent where (no patient data)
Each message: trigger, control id, jurisdiction, status, time, any warnings (age_not_recorded, disposition_not_recorded). No patient reference beyond the visit_ref you already hold.
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/public-health/syndromic/messages" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
resp = requests.get(
BASE + "/v1/public-health/syndromic/messages",
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/public-health/syndromic/messages`);
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": [
{
"message_ref": "ssm_5e7a9c1b3d5f7a9c1e3a5b7d",
"visit_ref": "string",
"trigger": "string",
"control_id": "string",
"jurisdiction": "string",
"status": "string",
"sent_at": "string",
"warnings": [
"string"
],
"simulated": 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 |
|---|---|---|
public_health.case_report_response | public_health.case_reports.read | A Reportability Response was filed for one of the practice's eICRs. |
public_health.iis_ack_received | public_health.immunizations.read | A state immunization registry acknowledged a dose report of the practice. |
Generated from the Partner API router and developers_catalog.py. The router docstring is the source of each description.