Claims in
Attach claims you already hold to your members: CMS CCLF files (Parts A, B and D) or a plan's claims in the documented generic shape. A dry run first (counts, match rate, rejections by code and row number, never a value), then an asynchronous commit and a claims.import_completed webhook. Claims match on your member reference, the member's MBI or plan member id (fingerprinted in memory, never stored), never on name or birth date; unmatched claims are dropped. Per member: a claims summary (settings, diagnoses, prescription fills) alongside what the member connected through Blue Button, and the member's record connections. Risk scores are refused unless an official CMS-HCC model is loaded. Claims never rewrite the member's own condition or medication list. Risk-adjustment audit evidence per member: the diagnosis codes in your imported claims with the dated claims documenting each, grouped into CMS-HCC categories only when an official model is loaded, never a risk score.
- Who calls it
- Core for Federal (via primes), State & local government, Insurers (incl. Medicaid managed care); useful for Independent practices
- Before production
- A BAA on file (claims_baa_required otherwise), and an organisation that may hold covered lives.
- Sandbox
- Build against synthetic members first. What each sandbox holds
- Standards
- CMS CCLF (Parts A, B, D); CMS-HCC only when an official model is loaded
Markets that use this family
- Independent practices (useful)
- Federal (via primes) (core)
- State & local government (core)
- Insurers (incl. Medicaid managed care) (core)
Scopes
| Scope | Allows |
|---|---|
claims.import.write | Send claims files (CMS CCLF Part A / B / D, or your plan's claims as CSV / NDJSON in the documented shape): staged with a dry run (match rate, row errors by code), then committed or discarded. MBIs and plan member ids are fingerprinted and never stored (treating practices and programme organisations with a BAA on file). |
claims.read | Read a member's claims summary (counts by part and setting, dates, top diagnosis categories, CMS-HCC categories only when an official model is loaded) from your imported claims and, when the member shares their medical history with you, their own Medicare Blue Button summary; and whether they connected Blue Button or VA records (read-only). |
claims.risk_adjustment.read | Read risk-adjustment audit evidence for a member: the diagnosis codes in the claims you imported with the dated claims documenting each, grouped into CMS-HCC categories only when an official model is loaded; never a risk score (treating practices and programme organisations with a BAA on file). |
Endpoints
Stage a claims import (CCLF or plan claims) and get its dry run
Parses the file(s) in the request, matches each claim to exactly one of your active members (member_ref, or the MBI / plan member id against the coverage recorded for the member, by fingerprint; never by name or birth date) and stages the matched claims. Nothing is attached until commit. MBIs, plan member ids and claim numbers are never stored. Treating practices and programme organisations with a BAA on file (409 not_a_medical_practice / claims_baa_required).
Request body
One claims file as the body (text/csv or text/plain for a pipe / comma / tab delimited CCLF or plan CSV, application/x-ndjson for plan NDJSON; ?filename= names it), or multipart/form-data with one or more `files` parts (e.g. CCLF1 + CCLF4 together). Generic plan shape: claim_id, member_ref | member_id | mbi, service_from, service_thru, dx_codes, procedure_codes, ndc_codes, place_of_service, part, setting, claim_type_code, paid_amount. At most 50 MB.
Content type: text/csv, text/plain, application/x-ndjson, multipart/form-data (required)
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/claims/imports" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: text/csv" \
--data-binary @your_body_fileimport os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
with open("your_body_file", "rb") as fh:
resp = requests.post(
BASE + "/v1/claims/imports",
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "text/csv"},
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 url = new URL(`${BASE}/v1/claims/imports`);
const res = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
"Content-Type": "text/csv",
},
body: await readFile("your_body_file"),
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"claims_import_ref": "cli_8a1f0c3e5b7d9a2c4e6f8b0d",
"status": "staged",
"file_kinds": [
"cclf1",
"cclf4"
],
"file_sha256": "string",
"file_bytes": 0,
"dry_run": {
"files": [
{
"filename": "CCLF1.psv",
"kind": "cclf1"
}
],
"rows_read": 0,
"claims_parsed": 0,
"claims_matched": 0,
"claims_unmatched": 0,
"claims_ambiguous": 0,
"members_matched": 0,
"match_rate": 0.92,
"match_basis": {
"mbi": 120
},
"diagnosis_rows_attached": 0,
"by_part": {
"A": 12,
"B": 98,
"D": 10
},
"by_setting": {},
"service_from_min": "string",
"service_thru_max": "string",
"rejected": 0,
"rejections_by_code": {},
"rejections": [
{
"file": "CCLF5.psv",
"row": 17,
"code": "malformed_mbi"
}
],
"unmatched_rows": [
{
"file": "string",
"row": 0
}
],
"can_commit": false,
"blocking": [
"claims_import_nothing_to_commit"
],
"raw_identifiers_stored": false
},
"result": {
"care_events_written": 9,
"claims_committed": 110,
"claims_replaced": 0,
"diagnosis_rows_merged": 12,
"dropped_link_ended_or_header_missing": 0,
"members": 41,
"profile_changed": false
},
"error_code": "string",
"created_at": "string",
"commit_started_at": "string",
"committed_at": "string",
"discarded_at": "string"
}The response is the shape from the schema, not live data.
One claims import: dry run, progress, result
Imports YOUR client sent only (404 claims_import_not_found otherwise). Counts and row numbers only; no identifier or claim content.
Parameters
Errors specific to this call
404claims_import_not_foundAll responses
200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service UnavailableCodes every call can return are in Errors & limits.
curl -s "https://norvya.fly.dev/v1/claims/imports/$CLAIMS_IMPORT_REF" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
claims_import_ref = os.environ["CLAIMS_IMPORT_REF"]
resp = requests.get(
f"{BASE}/v1/claims/imports/{claims_import_ref}",
headers={"Authorization": f"Bearer {TOKEN}"},
timeout=30,
)
resp.raise_for_status()
print(resp.json())// Node.js 18+ (ES module). Keep production credentials on your server.
const BASE = "https://norvya.fly.dev";
const claims_import_ref = process.env.CLAIMS_IMPORT_REF;
const url = new URL(`${BASE}/v1/claims/imports/${encodeURIComponent(claims_import_ref)}`);
const res = await fetch(url, {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.TOKEN}`,
},
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
console.log(await res.json());{
"claims_import_ref": "cli_8a1f0c3e5b7d9a2c4e6f8b0d",
"status": "staged",
"file_kinds": [
"cclf1",
"cclf4"
],
"file_sha256": "string",
"file_bytes": 0,
"dry_run": {
"files": [
{
"filename": "CCLF1.psv",
"kind": "cclf1"
}
],
"rows_read": 0,
"claims_parsed": 0,
"claims_matched": 0,
"claims_unmatched": 0,
"claims_ambiguous": 0,
"members_matched": 0,
"match_rate": 0.92,
"match_basis": {
"mbi": 120
},
"diagnosis_rows_attached": 0,
"by_part": {
"A": 12,
"B": 98,
"D": 10
},
"by_setting": {},
"service_from_min": "string",
"service_thru_max": "string",
"rejected": 0,
"rejections_by_code": {},
"rejections": [
{
"file": "CCLF5.psv",
"row": 17,
"code": "malformed_mbi"
}
],
"unmatched_rows": [
{
"file": "string",
"row": 0
}
],
"can_commit": false,
"blocking": [
"claims_import_nothing_to_commit"
],
"raw_identifiers_stored": false
},
"result": {
"care_events_written": 9,
"claims_committed": 110,
"claims_replaced": 0,
"diagnosis_rows_merged": 12,
"dropped_link_ended_or_header_missing": 0,
"members": 41,
"profile_changed": false
},
"error_code": "string",
"created_at": "string",
"commit_started_at": "string",
"committed_at": "string",
"discarded_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Commit a staged claims import
Attaches the staged claims in the background (poll GET, or subscribe to claims.import_completed). Members whose link ended since the dry run are dropped; a claim you sent before (same claim number) is replaced. Claims never change a member's self-reported conditions or medications. 409 claims_import_not_staged / claims_import_commit_in_progress / claims_import_nothing_to_commit / claims_baa_required (re-checked by the worker before the first write).
Parameters
Errors specific to this call
409claims_import_not_stagedAll 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/claims/imports/$CLAIMS_IMPORT_REF/commit" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
claims_import_ref = os.environ["CLAIMS_IMPORT_REF"]
resp = requests.post(
f"{BASE}/v1/claims/imports/{claims_import_ref}/commit",
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 claims_import_ref = process.env.CLAIMS_IMPORT_REF;
const url = new URL(`${BASE}/v1/claims/imports/${encodeURIComponent(claims_import_ref)}/commit`);
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());{
"claims_import_ref": "cli_8a1f0c3e5b7d9a2c4e6f8b0d",
"status": "staged",
"file_kinds": [
"cclf1",
"cclf4"
],
"file_sha256": "string",
"file_bytes": 0,
"dry_run": {
"files": [
{
"filename": "CCLF1.psv",
"kind": "cclf1"
}
],
"rows_read": 0,
"claims_parsed": 0,
"claims_matched": 0,
"claims_unmatched": 0,
"claims_ambiguous": 0,
"members_matched": 0,
"match_rate": 0.92,
"match_basis": {
"mbi": 120
},
"diagnosis_rows_attached": 0,
"by_part": {
"A": 12,
"B": 98,
"D": 10
},
"by_setting": {},
"service_from_min": "string",
"service_thru_max": "string",
"rejected": 0,
"rejections_by_code": {},
"rejections": [
{
"file": "CCLF5.psv",
"row": 17,
"code": "malformed_mbi"
}
],
"unmatched_rows": [
{
"file": "string",
"row": 0
}
],
"can_commit": false,
"blocking": [
"claims_import_nothing_to_commit"
],
"raw_identifiers_stored": false
},
"result": {
"care_events_written": 9,
"claims_committed": 110,
"claims_replaced": 0,
"diagnosis_rows_merged": 12,
"dropped_link_ended_or_header_missing": 0,
"members": 41,
"profile_changed": false
},
"error_code": "string",
"created_at": "string",
"commit_started_at": "string",
"committed_at": "string",
"discarded_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Discard a staged claims import
Deletes the staged claims; the manifest (counts, codes, hash) stays.
Parameters
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/claims/imports/$CLAIMS_IMPORT_REF/discard" \
-H "Authorization: Bearer $TOKEN"import os
import requests
BASE = "https://norvya.fly.dev"
TOKEN = os.environ["TOKEN"]
claims_import_ref = os.environ["CLAIMS_IMPORT_REF"]
resp = requests.post(
f"{BASE}/v1/claims/imports/{claims_import_ref}/discard",
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 claims_import_ref = process.env.CLAIMS_IMPORT_REF;
const url = new URL(`${BASE}/v1/claims/imports/${encodeURIComponent(claims_import_ref)}/discard`);
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());{
"claims_import_ref": "cli_8a1f0c3e5b7d9a2c4e6f8b0d",
"status": "staged",
"file_kinds": [
"cclf1",
"cclf4"
],
"file_sha256": "string",
"file_bytes": 0,
"dry_run": {
"files": [
{
"filename": "CCLF1.psv",
"kind": "cclf1"
}
],
"rows_read": 0,
"claims_parsed": 0,
"claims_matched": 0,
"claims_unmatched": 0,
"claims_ambiguous": 0,
"members_matched": 0,
"match_rate": 0.92,
"match_basis": {
"mbi": 120
},
"diagnosis_rows_attached": 0,
"by_part": {
"A": 12,
"B": 98,
"D": 10
},
"by_setting": {},
"service_from_min": "string",
"service_thru_max": "string",
"rejected": 0,
"rejections_by_code": {},
"rejections": [
{
"file": "CCLF5.psv",
"row": 17,
"code": "malformed_mbi"
}
],
"unmatched_rows": [
{
"file": "string",
"row": 0
}
],
"can_commit": false,
"blocking": [
"claims_import_nothing_to_commit"
],
"raw_identifiers_stored": false
},
"result": {
"care_events_written": 9,
"claims_committed": 110,
"claims_replaced": 0,
"diagnosis_rows_merged": 12,
"dropped_link_ended_or_header_missing": 0,
"members": 41,
"profile_changed": false
},
"error_code": "string",
"created_at": "string",
"commit_started_at": "string",
"committed_at": "string",
"discarded_at": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
A member's claims summary (your imports + their Blue Button)
Counts by part and setting, service-date span, the top ICD-10 categories and CMS-HCC condition categories (only when an official CMS model is loaded; never a guessed mapping and never a risk score) from the claims you imported (BAA gate), plus the member's own Medicare Blue Button summary when they share their medical history with you.
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/patients/$MEMBER_REF/claims-summary" \
-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}/claims-summary",
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)}/claims-summary`);
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": "mr_3f9c1a0b7d2e4c6a8b1d0e2f",
"claims_total": 0,
"first_service_date": "string",
"last_service_date": "string",
"sources": {},
"top_diagnosis_categories": [
{
"category": "E11",
"claims": 0
}
],
"hcc": {
"available": false,
"reason": "string",
"note": "string",
"condition_categories": [
"string"
],
"suppressed_by_hierarchy": [
"string"
],
"icd_codes_unmapped": 0,
"icd_mapped_rate": 0.0,
"model": {},
"supports_interactions": false,
"risk_score": 0.0,
"risk_score_withheld": "string"
},
"profile_merged": false,
"paid_amount_note": "string"
}The response is the shape from the schema, not live data.
Try it in your sandbox
Whether a member connected Medicare Blue Button or VA records
Status, connected and last-sync times only, and only when the member shares their medical history with you (shared false and an empty list otherwise). Read-only: connecting is the member's act in the app.
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/patients/$MEMBER_REF/connections" \
-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}/connections",
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)}/connections`);
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",
"shared": false,
"connections": [
{
"provider": "medicare_blue_button",
"status": "connected",
"environment": "production",
"connected_at": "string",
"last_sync_at": "string",
"has_summary": false,
"synthetic": false
}
],
"connect_in_app_only": false
}The response is the shape from the schema, not live data.
Try it in your sandbox
HCC audit evidence for a member (never a score)
Every ICD-10-CM code in the claims YOU imported for the member, each with the dated claims that document it (claim_ref, part, setting, place of service, procedure codes present, service dates). When an official CMS-HCC model is loaded the codes are grouped into condition categories after the hierarchy (suppressed categories listed, unmapped codes kept); otherwise condition_categories is null with reason hcc_model_not_loaded: Norvya never ships or guesses a mapping. risk_score is always null. The claims are not filtered for risk-adjustment-eligible encounter types (CMS's filtering logic is not implemented). 409 claims_baa_required.
Parameters
Service year; default all.
Errors specific to this call
409claims_baa_requiredAll 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/risk-adjustment-evidence" \
-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}/risk-adjustment-evidence",
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)}/risk-adjustment-evidence`);
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());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 |
|---|---|---|
claims.import_completed | claims.import.write | A claims import you sent finished committing (sent to that client only). |
Generated from the Partner API router and developers_catalog.py. The router docstring is the source of each description.