Norvya Developers
Get a sandbox
API reference · Operations

Billing & scheduling

Built — gated17 endpoints · 3 webhook events · OpenAPI tag billing

For practice-management and revenue-cycle systems. Appointments both ways: send yours by your own appointment id (idempotent; cancelling never deletes) so the pre-visit brief is ready before each visit, and read every appointment of the practice, including those patients booked in Norvya, on the practice's clock with cursor paging. Norvya does not send its own reminders for appointments your system sent unless the practice turns that on. Care-management minutes and remote-monitoring days per patient per program per month (CCM, PCM, chronic pain, RPM, RTM, APCM, CoCM, BHI, TCM), with the program's published reference marks reached and its reference codes, each labelled a candidate for your biller's review: never a charge, an eligibility determination or a code selected for the patient. Billing candidates for staff review. No Surprises Act Good Faith Estimates from your codes and charges, with Norvya's required-contents check and a PDF in the patient's language. Not exposed: claim submission, ERA posting and eligibility-file intake; your practice-management system remains the claims engine. Treating practices only.

Who calls it
Core for Practice management & billing; useful for Concierge doctors, Independent practices, Medical travel desks, Behavioral health practices, Rehab & nutrition practices
Before production
A BAA with each practice. The care-management view also needs the practice to switch on care-management documentation in Norvya (after its own compliance review).
Sandbox
Build against synthetic members first. What each sandbox holds
Standards
No Surprises Act Good Faith Estimates

Markets that use this family

Scopes

ScopeAllows
billing.estimates.writeIssue a No Surprises Act Good Faith Estimate for a patient from your system: your codes and your charges, the same required-contents check as Norvya's own screen (treating practices only).
billing.readRead care-management time and remote-monitoring days per patient per month, with the program's published reference marks reached, as candidates for your biller's review (never a charge or an eligibility determination); billing candidates for staff review; and Good Faith Estimates (treating practices only).
scheduling.readRead your practice's appointments (booked in Norvya or sent by your system), one practice zone, cursor-paginated, plus the scheduling settings (treating practices only).
scheduling.writeSend appointments from your practice-management system, idempotently by your own appointment id; cancel never deletes. Change whether Norvya sends its own reminders for them (treating practices only).

Endpoints

Your practice's zone, providers and reminder setting

GET/v1/scheduling/settingsscheduling.read

The practice zone Norvya stores appointments on (from your practice's Norvya hours; UTC when none are set), its active clinicians and staff as staff_ref values to map your own provider list to (names and roles only), and whether Norvya sends its own reminders for appointments your system sends (off by default: your system usually reminds).

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/scheduling/settings" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "practice_zone": "string",
  "zone_basis": "string",
  "norvya_reminders_for_partner_appointments": false,
  "providers": [
    {
      "staff_ref": "string",
      "name": "string",
      "role": "string",
      "title": "string"
    }
  ],
  "statuses": [
    "string"
  ],
  "past_booked_window_hours": 0
}

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

Turn Norvya's reminders for your appointments on or off

PUT/v1/scheduling/settingsscheduling.write

Set norvya_reminders_for_partner_appointments. While false, Norvya sends no appointment reminder (push, in-app or email) for an appointment your system sent, so a patient is not reminded twice. Appointments booked in Norvya itself are reminded either way. Audited as a settings change.

Request body

Content type: application/json (required)

norvya_reminders_for_partner_appointmentsbooleanrequired

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s -X PUT "https://norvya.fly.dev/v1/scheduling/settings" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "norvya_reminders_for_partner_appointments": false
  }'
Response 200
{
  "practice_zone": "string",
  "zone_basis": "string",
  "norvya_reminders_for_partner_appointments": false,
  "providers": [
    {
      "staff_ref": "string",
      "name": "string",
      "role": "string",
      "title": "string"
    }
  ],
  "statuses": [
    "string"
  ],
  "past_booked_window_hours": 0
}

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

Create or update an appointment by YOUR appointment id

PUT/v1/appointments/ext/{external_id}scheduling.write

Idempotent: the same external_id from your client always lands on the same Norvya appointment (another client's ids are a separate namespace). The appointment becomes an ordinary Norvya appointment, so the pre-visit brief and your staff's screens see it. `start` must carry a UTC offset. A booked appointment that started more than 24 hours ago is refused (booked_in_past: send history as completed, no_show or cancelled). An id stays bound to its first patient (409 external_id_member_mismatch). Your own change is never sent back to you as a webhook.

Parameters

external_idpath · stringrequired

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

Request body

Content type: application/json (required)

member_refstringrequired
startstringrequired

ISO 8601 with a UTC offset; a time without one is refused (start_timezone_required).

duration_minutesintegerrequired
timezonestring | null

IANA zone the visit is on; default the practice's zone.

statusstring

booked | cancelled | completed | no_show

visit_typestring | null
providerobject | null
locationstring | null
telehealthboolean
cancellation_reasonstring | null

Errors specific to this call

409external_id_member_mismatch

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s -X PUT "https://norvya.fly.dev/v1/appointments/ext/$EXTERNAL_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "member_ref": "mr_3f9c1a0b7d2e4c6a8b1d0e2f",
    "start": "2026-10-20T09:30:00-04:00",
    "duration_minutes": 30,
    "timezone": "America/New_York",
    "status": "booked",
    "visit_type": "follow_up",
    "provider": {
      "staff_ref": "stf_1a2b3c4d5e6f",
      "label": "Dr. Ana Ruiz"
    },
    "location": "Main Street office",
    "telehealth": false,
    "cancellation_reason": "string"
  }'
Response 200
{
  "appointment_ref": "apt_5e7a9c1b3d5f7a9c1e3a5b7d",
  "external_id": "PM-000123",
  "source": "partner",
  "member_ref": "string",
  "status": "booked",
  "start_utc": "2026-10-20T13:30:00Z",
  "end_utc": "string",
  "start_local": "2026-10-20T09:30:00-04:00",
  "duration_minutes": 0,
  "timezone": "America/New_York",
  "practice_zone": "string",
  "visit_type": "string",
  "provider": {
    "staff_ref": "string",
    "label": "string"
  },
  "location": "string",
  "telehealth": false,
  "norvya_reminders": false,
  "created_at": "string",
  "updated_at": "string",
  "cancelled_at": "string"
}

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

One of YOUR appointments, by your id

GET/v1/appointments/ext/{external_id}scheduling.read

An appointment your client sent. Anything else answers 404 appointment_not_found.

Parameters

external_idpath · 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/appointments/ext/$EXTERNAL_ID" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "appointment_ref": "apt_5e7a9c1b3d5f7a9c1e3a5b7d",
  "external_id": "PM-000123",
  "source": "partner",
  "member_ref": "string",
  "status": "booked",
  "start_utc": "2026-10-20T13:30:00Z",
  "end_utc": "string",
  "start_local": "2026-10-20T09:30:00-04:00",
  "duration_minutes": 0,
  "timezone": "America/New_York",
  "practice_zone": "string",
  "visit_type": "string",
  "provider": {
    "staff_ref": "string",
    "label": "string"
  },
  "location": "string",
  "telehealth": false,
  "norvya_reminders": false,
  "created_at": "string",
  "updated_at": "string",
  "cancelled_at": "string"
}

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

Cancel one of YOUR appointments (never deleted)

DELETE/v1/appointments/ext/{external_id}scheduling.write

DELETE means cancel: the status becomes cancelled and the row stays, so its history is legible and the brief no longer counts it. Repeating it is harmless. Returns the appointment.

Parameters

external_idpath · stringrequired
reasonquery · string | null

All responses

200Successful Response401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s -X DELETE "https://norvya.fly.dev/v1/appointments/ext/$EXTERNAL_ID" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "appointment_ref": "apt_5e7a9c1b3d5f7a9c1e3a5b7d",
  "external_id": "PM-000123",
  "source": "partner",
  "member_ref": "string",
  "status": "booked",
  "start_utc": "2026-10-20T13:30:00Z",
  "end_utc": "string",
  "start_local": "2026-10-20T09:30:00-04:00",
  "duration_minutes": 0,
  "timezone": "America/New_York",
  "practice_zone": "string",
  "visit_type": "string",
  "provider": {
    "staff_ref": "string",
    "label": "string"
  },
  "location": "string",
  "telehealth": false,
  "norvya_reminders": false,
  "created_at": "string",
  "updated_at": "string",
  "cancelled_at": "string"
}

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

Your practice's appointments, booked in Norvya or sent by a practice system

GET/v1/appointmentsscheduling.read

Ordered by start time. Includes appointments patients booked in Norvya (so your system can mirror them) and every practice system's appointments; external_id is filled only for your own. Patients no longer linked to your practice are left out. Pass next_cursor back as ?cursor= for the next page.

Parameters

fromquery · string | null

YYYY-MM-DD on the practice's clock; default today.

toquery · string | null

YYYY-MM-DD inclusive; default from + 30 days; at most 366 days.

statusquery · string | null

booked | checked_in | completed | cancelled | no_show

sourcequery · string | null

norvya | partner

cursorquery · string | null
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/appointments" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "items": [
    {
      "appointment_ref": "apt_5e7a9c1b3d5f7a9c1e3a5b7d",
      "external_id": "PM-000123",
      "source": "partner",
      "member_ref": "string",
      "status": "booked",
      "start_utc": "2026-10-20T13:30:00Z",
      "end_utc": "string",
      "start_local": "2026-10-20T09:30:00-04:00",
      "duration_minutes": 0,
      "timezone": "America/New_York",
      "practice_zone": "string",
      "visit_type": "string",
      "provider": {
        "staff_ref": "string",
        "label": "string"
      },
      "location": "string",
      "telehealth": false,
      "norvya_reminders": false,
      "created_at": "string",
      "updated_at": "string",
      "cancelled_at": "string"
    }
  ],
  "next_cursor": "string",
  "practice_zone": "string",
  "zone_basis": "clinic_schedule",
  "window": {}
}

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

One appointment of your practice

GET/v1/appointments/{appointment_ref}scheduling.read

The resource an appointment.booked / appointment.cancelled webhook points to. Another practice's appointment, an unknown ref and one whose patient is no longer linked are the same 404 appointment_not_found.

Parameters

appointment_refpath · stringrequired

Errors specific to this call

404appointment_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/appointments/$APPOINTMENT_REF" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "appointment_ref": "apt_5e7a9c1b3d5f7a9c1e3a5b7d",
  "external_id": "PM-000123",
  "source": "partner",
  "member_ref": "string",
  "status": "booked",
  "start_utc": "2026-10-20T13:30:00Z",
  "end_utc": "string",
  "start_local": "2026-10-20T09:30:00-04:00",
  "duration_minutes": 0,
  "timezone": "America/New_York",
  "practice_zone": "string",
  "visit_type": "string",
  "provider": {
    "staff_ref": "string",
    "label": "string"
  },
  "location": "string",
  "telehealth": false,
  "norvya_reminders": false,
  "created_at": "string",
  "updated_at": "string",
  "cancelled_at": "string"
}

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

Care-management minutes and remote days per patient per program, for your biller

GET/v1/billing/care-managementbilling.read

Every patient with an enrolment overlapping the month or minutes filed under a program, per program: enrolment and consent state, minutes (by activity) and, for remote monitoring, device days per 30-day period, whether the program's first published reference mark was reached, the marks reached, what is still missing, and the program's reference codes. Each row is status candidate_for_biller_review: never a charge, an eligibility determination or a code selected for the patient. The same evidence Norvya's staff month view shows (care_programs). Needs care management enabled for your practice.

Parameters

monthquery · string | null

YYYY-MM on the practice's clock; default the current month.

programquery · string | null

ccm | pcm | chronic_pain | rpm | rtm | apcm | cocm | bhi | tcm

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/billing/care-management" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "service_month": "string",
  "practice_zone": "string",
  "zone_basis": "string",
  "items": [
    {
      "member_ref": "string",
      "program": "ccm",
      "family": "time_based",
      "service_month": "2026-10",
      "status": "candidate_for_biller_review",
      "enrolment": {},
      "minutes": 0,
      "time_entries": 0,
      "minutes_by_activity": {},
      "interactive_call_minutes": 0,
      "remote_periods": [
        {}
      ],
      "tcm_periods": [
        {}
      ],
      "threshold": {
        "met": false,
        "basis": "minutes",
        "marks_reached": [
          "clinical_staff_first"
        ],
        "next_mark": {}
      },
      "reference": {
        "codes": [
          "99490",
          "99439",
          "99491",
          "99437",
          "99487",
          "99489"
        ],
        "add_on_codes": [],
        "add_on_marks": [],
        "mark_minutes": {},
        "codes_verified": false,
        "source": "string",
        "attached_to_member": false
      },
      "gaps": [
        {}
      ],
      "concurrency": [
        {}
      ]
    }
  ],
  "reference_as_of": "string",
  "notice": "string"
}

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

One patient's care-management month, for your biller

GET/v1/patients/{member_ref}/care-managementbilling.read

The rows of GET /v1/billing/care-management for one patient (the resource billing.threshold_met points to). A patient with nothing filed this month answers an empty programs list.

Parameters

member_refpath · stringrequired
monthquery · string | null

YYYY-MM; default the current month.

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-management" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "member_ref": "string",
  "service_month": "string",
  "practice_zone": "string",
  "programs": [
    {
      "member_ref": "string",
      "program": "ccm",
      "family": "time_based",
      "service_month": "2026-10",
      "status": "candidate_for_biller_review",
      "enrolment": {},
      "minutes": 0,
      "time_entries": 0,
      "minutes_by_activity": {},
      "interactive_call_minutes": 0,
      "remote_periods": [
        {}
      ],
      "tcm_periods": [
        {}
      ],
      "threshold": {
        "met": false,
        "basis": "minutes",
        "marks_reached": [
          "clinical_staff_first"
        ],
        "next_mark": {}
      },
      "reference": {
        "codes": [
          "99490",
          "99439",
          "99491",
          "99437",
          "99487",
          "99489"
        ],
        "add_on_codes": [],
        "add_on_marks": [],
        "mark_minutes": {},
        "codes_verified": false,
        "source": "string",
        "attached_to_member": false
      },
      "gaps": [
        {}
      ],
      "concurrency": [
        {}
      ]
    }
  ],
  "notice": "string"
}

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

Patients whose recorded data match published CMS criteria: for staff review

GET/v1/billing/candidatesbilling.read

Patients whose RECORDED data match the published CMS criteria for CCM, PCM, APCM (by level) or a possibly-due annual wellness visit, each with the criteria met and the record each came from, what Norvya does not know (payer, QMB status, consent, enrolment elsewhere, Part B date, the clinician's judgement) and staff_must_confirm: true. For staff review, not an eligibility determination; no code is attached to a patient and no amount is shown. Patients already enrolled in the program are left out.

Parameters

programquery · string | null

ccm | pcm | apcm | awv

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/billing/candidates" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "as_of": "string",
  "engine": "string",
  "care_management_enabled": false,
  "counts": {},
  "items": [
    {
      "member_ref": "string",
      "program": "ccm",
      "label": "candidate_for_review",
      "level": "string",
      "criteria_met": [
        {}
      ],
      "unknowns": [
        "string"
      ],
      "coverage": {},
      "open_enrollments": [
        {}
      ],
      "previously_declined_on": "string",
      "staff_must_confirm": false
    }
  ],
  "criteria": {},
  "limitations": [
    "string"
  ],
  "staff_must_confirm": false,
  "notice": "string"
}

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

Issue a Good Faith Estimate for a patient

POST/v1/patients/{member_ref}/good-faith-estimatesbilling.estimates.write

A No Surprises Act estimate (45 CFR 149.610) for an uninsured or self-pay patient, from YOUR codes and charges (Norvya never chooses a code or a price). Lines without a provider belong to your practice and take its name, organisation NPI, TIN and address unless you pass convening_*. The same required-contents check as Norvya's own screen: anything §149.610(c) asks for that is missing is refused as 422 {code: required_contents_missing, missing: [...]}, and nothing is recorded. The patient's name and date of birth come from Norvya. Issued documents are retained six years and never edited; delivered in the app when delivery_method is in_app.

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)

triggerstringrequired

scheduled | requested

triggered_onstring | null

YYYY-MM-DD: the scheduling or request date; default today on the practice's clock.

primary_servicestringrequired
service_datestring | null

YYYY-MM-DD; required when trigger is scheduled.

delivery_methodstring

in_app | email | paper | mail

linesarray of objectrequired
separately_scheduledarray of string | null
diagnosis_codesarray of string | null
convening_npistring | null
convening_tinstring | null
convening_locationstring | null
convening_statestring | null
notestring | null

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/good-faith-estimates" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "trigger": "scheduled",
    "triggered_on": "2026-10-16",
    "primary_service": "Diabetes follow-up visit with A1c",
    "service_date": "string",
    "delivery_method": "string",
    "lines": [
      {
        "description": "Office visit, established patient",
        "service_code": "99213",
        "code_type": "CPT",
        "quantity": 0,
        "expected_charge": "150.00",
        "diagnosis_codes": [
          "E11.9"
        ],
        "provider_name": "string",
        "npi": "string",
        "tin": "string",
        "location": "string",
        "state": "string",
        "role": "string"
      }
    ],
    "separately_scheduled": [
      "string"
    ],
    "diagnosis_codes": [
      "string"
    ],
    "convening_npi": "string",
    "convening_tin": "string",
    "convening_location": "string",
    "convening_state": "string",
    "note": "string"
  }'
Response 201
{
  "estimate_ref": "gfe_8b0d2f4a6c8e0a2b4d6f8a0c",
  "member_ref": "string",
  "version": 0,
  "reason": "initial",
  "is_current": false,
  "issued_on": "string",
  "primary_service": "string",
  "service_date": "string",
  "total_cents": 0,
  "expected_patient_total_cents": 0,
  "retain_until": "string",
  "delivered_via": "string",
  "acknowledged": false,
  "content_sha256": "string",
  "verified": false,
  "timing": {},
  "request_status": "string",
  "document": {},
  "not_legal_advice": "string"
}

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

A patient's Good Faith Estimates from your practice

GET/v1/patients/{member_ref}/good-faith-estimatesbilling.read

Every estimate version your practice issued for the patient (through the API or Norvya's own screen), newest first, without the document body.

Parameters

member_refpath · stringrequired

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/good-faith-estimates" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "member_ref": "string",
  "items": [
    {
      "estimate_ref": "gfe_8b0d2f4a6c8e0a2b4d6f8a0c",
      "member_ref": "string",
      "version": 0,
      "reason": "initial",
      "is_current": false,
      "issued_on": "string",
      "primary_service": "string",
      "service_date": "string",
      "total_cents": 0,
      "expected_patient_total_cents": 0,
      "retain_until": "string",
      "delivered_via": "string",
      "acknowledged": false,
      "content_sha256": "string",
      "verified": false,
      "timing": {},
      "request_status": "string",
      "document": {},
      "not_legal_advice": "string"
    }
  ]
}

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

One Good Faith Estimate, with the issued document

GET/v1/good-faith-estimates/{estimate_ref}billing.read

The issued snapshot (what the PDF shows), its SHA-256 and whether it still verifies, and the timing status of its request. Another practice's estimate and an unknown ref are the same 404 estimate_not_found.

Parameters

estimate_refpath · stringrequired

Errors specific to this call

404estimate_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/good-faith-estimates/$ESTIMATE_REF" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "estimate_ref": "gfe_8b0d2f4a6c8e0a2b4d6f8a0c",
  "member_ref": "string",
  "version": 0,
  "reason": "initial",
  "is_current": false,
  "issued_on": "string",
  "primary_service": "string",
  "service_date": "string",
  "total_cents": 0,
  "expected_patient_total_cents": 0,
  "retain_until": "string",
  "delivered_via": "string",
  "acknowledged": false,
  "content_sha256": "string",
  "verified": false,
  "timing": {},
  "request_status": "string",
  "document": {},
  "not_legal_advice": "string"
}

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

The estimate as a PDF, in the patient's language

GET/v1/good-faith-estimates/{estimate_ref}/pdfbilling.read

Rendered by the same builder as Norvya's own copy, in the patient's app language (or ?lang=), with the English disclaimers kept as the binding text. A disclosure: audited as an export before it is returned and refused when the audit log is down.

Parameters

estimate_refpath · stringrequired
langquery · string | null

An app language code; default the patient's.

All responses

200The estimate as a PDF in the patient's language.401Unauthorized403Forbidden404Not Found409Conflict422Unprocessable Entity429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/good-faith-estimates/$ESTIMATE_REF/pdf" \
  -H "Authorization: Bearer $TOKEN"

Issue a new version of a Good Faith Estimate

POST/v1/good-faith-estimates/{estimate_ref}/reissuebilling.estimates.write

A new version for a change in the items or charges, or after a reschedule (good_faith_estimate.issue: the same builder and §149.610(c) required-contents check as the first issue; a refusal records nothing). The previous version is kept, marked superseded, never edited; the PDF of the new version is rendered on request like any other. 409 request_closed for a cancelled request, 409 estimate_superseded for an older version.

Parameters

estimate_refpath · stringrequired

Request body

Content type: application/json (required)

reasonstring | null

change | reschedule; default: reschedule when the request was rescheduled after the current version, else change.

linesarray of objectrequired
separately_scheduledarray of string | null
diagnosis_codesarray of string | null
convening_npistring | null
convening_tinstring | null
convening_locationstring | null
convening_statestring | null
notestring | null

Errors specific to this call

409request_closed409estimate_superseded

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/good-faith-estimates/$ESTIMATE_REF/reissue" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "string",
    "lines": [
      {
        "description": "Office visit, established patient",
        "service_code": "99213",
        "code_type": "CPT",
        "quantity": 0,
        "expected_charge": "150.00",
        "diagnosis_codes": [
          "E11.9"
        ],
        "provider_name": "string",
        "npi": "string",
        "tin": "string",
        "location": "string",
        "state": "string",
        "role": "string"
      }
    ],
    "separately_scheduled": [
      "string"
    ],
    "diagnosis_codes": [
      "string"
    ],
    "convening_npi": "string",
    "convening_tin": "string",
    "convening_location": "string",
    "convening_state": "string",
    "note": "string"
  }'
Response 201
{
  "estimate_ref": "gfe_8b0d2f4a6c8e0a2b4d6f8a0c",
  "member_ref": "string",
  "version": 0,
  "reason": "initial",
  "is_current": false,
  "issued_on": "string",
  "primary_service": "string",
  "service_date": "string",
  "total_cents": 0,
  "expected_patient_total_cents": 0,
  "retain_until": "string",
  "delivered_via": "string",
  "acknowledged": false,
  "content_sha256": "string",
  "verified": false,
  "timing": {},
  "request_status": "string",
  "document": {},
  "not_legal_advice": "string"
}

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

Record that the scheduled item was rescheduled

POST/v1/good-faith-estimates/{estimate_ref}/reschedulebilling.estimates.write

good_faith_estimate.reschedule: the §149.610(b)(1)(vi) clock restarts from scheduled_on and the current version then needs a new one (timing status reissue_needed): POST .../reissue next. 422 service_date_before_scheduling, scheduled_on_in_future; 409 request_closed.

Parameters

estimate_refpath · stringrequired

Request body

Content type: application/json (required)

scheduled_onstring | null

YYYY-MM-DD: when the item was rescheduled; default today on the practice's clock.

service_datestringrequired

YYYY-MM-DD: the new date of service.

Errors specific to this call

409request_closed

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/good-faith-estimates/$ESTIMATE_REF/reschedule" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "scheduled_on": "2026-10-20",
    "service_date": "2026-11-12"
  }'
Response 200
{
  "estimate_ref": "gfe_8b0d2f4a6c8e0a2b4d6f8a0c",
  "member_ref": "string",
  "version": 0,
  "reason": "initial",
  "is_current": false,
  "issued_on": "string",
  "primary_service": "string",
  "service_date": "string",
  "total_cents": 0,
  "expected_patient_total_cents": 0,
  "retain_until": "string",
  "delivered_via": "string",
  "acknowledged": false,
  "content_sha256": "string",
  "verified": false,
  "timing": {},
  "request_status": "string",
  "document": {},
  "not_legal_advice": "string"
}

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

Cancel the estimate request (the item will not take place)

POST/v1/good-faith-estimates/{estimate_ref}/cancelbilling.estimates.write

good_faith_estimate.cancel: the request closes (request_status cancelled); issued versions stay on file for the six-year retention and stay readable. 409 request_closed when already closed.

Parameters

estimate_refpath · stringrequired

Request body

Content type: application/json

Errors specific to this call

409request_closed

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/good-faith-estimates/$ESTIMATE_REF/cancel" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "string"
  }'
Response 200
{
  "estimate_ref": "gfe_8b0d2f4a6c8e0a2b4d6f8a0c",
  "member_ref": "string",
  "version": 0,
  "reason": "initial",
  "is_current": false,
  "issued_on": "string",
  "primary_service": "string",
  "service_date": "string",
  "total_cents": 0,
  "expected_patient_total_cents": 0,
  "retain_until": "string",
  "delivered_via": "string",
  "acknowledged": false,
  "content_sha256": "string",
  "verified": false,
  "timing": {},
  "request_status": "string",
  "document": {},
  "not_legal_advice": "string"
}

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
appointment.bookedscheduling.readAn appointment was booked in Norvya (your own writes are never echoed).
appointment.cancelledscheduling.readAn appointment was cancelled in Norvya (your own writes are never echoed).
billing.threshold_metbilling.readA patient's documented care-management time (or remote-monitoring days) for one program reached the program's first published reference mark this month.

Generated from the Partner API router and developers_catalog.py. The router docstring is the source of each description.

↑ ↓ to moveEnter to open