Migration Guide to Stable

Migrating from v2.2.2 to v20240412

This guide walks through every endpoint of the Synchronizer API v2.2.2 and documents the breaking changes you need to handle to move to the NexHealth API v20240412, along with concrete alternatives for behavior that was removed (most notably include values on list endpoints, and the pagination scheme).

How to read this guide:

  1. Read the cross-cutting sections first - version selection, pagination, and includes. They apply to many endpoints.
  2. Then use the endpoint-by-endpoint reference as a checklist for the endpoints you actually call.

Legend used throughout:

StatusMeaning
UnchangedSame request and response contract in v20240412. You only need to switch the version header.
⚠️ ChangedEndpoint exists in v20240412 but has breaking changes (params, response shape, semantics, pagination).
RemovedEndpoint does not exist in v20240412. A replacement endpoint is documented.
NewEndpoint only exists in v20240412 (useful as a replacement/alternative).

1. Selecting the API version

Versioning moved from a vendored Accept header to the dedicated Nex-Api-Version header.

v2.2.2 request:

curl "https://nexhealth.info/patients?subdomain=demo&location_id=1" \
  -H "Accept: application/vnd.Nexhealth+json;version=2" \
  -H "Authorization: Bearer <token>"

v20240412 request:

curl "https://nexhealth.info/patients?subdomain=demo&location_id=1" \
  -H "Nex-Api-Version: v20240412" \
  -H "Authorization: Bearer <token>"

Details:

  • Send Nex-Api-Version: v20240412 on every request, including requests to endpoints that are unchanged from v2.2.2.
  • If the header is absent, requests with the legacy Accept: application/vnd.Nexhealth+json;version=2 header (or no version at all) resolve to v2.2.2. Migrating means adding the header everywhere, not swapping URLs - paths stay the same.
  • v3.0.0 is accepted as a SemVer alias of v20240412; the docs and this guide use v20240412.
  • Authentication is unchanged: POST /authenticates with your API key still returns the Bearer token used on subsequent calls.
  • Endpoints marked ✅ below are served by the exact same implementation in both versions - v20240412 falls through to them transparently. You do not need to wait to switch the header for those.

2. Response envelope

The success envelope keeps its overall shape, with one change on cursor-paginated list endpoints:

Fieldv2.2.2v20240412
codetrue/falseunchanged
descriptioninformational messages (ignored params, deprecations)unchanged - watch this field: deprecation notices and payload-change notes are surfaced here
errorerror messagesunchanged
dataresource or arrayunchanged, except GET /patients (see Patients)
counttotal number of matching records on paginated listsremoved on cursor-paginated lists (kept only on GET /patient_recalls)
page_info-new on cursor-paginated lists (see below)

3. Pagination: limit/offset → cursor

This is the most widespread breaking change. In v2.2.2, list endpoints used offset pagination (page + per_page).
In v20240412, redefined list endpoints use cursor (keyset) pagination.

v2.2.2 (offset)

GET /appointments?...&page=3&per_page=100
  • page - 1-based page number.
  • per_page - number of records per page.
  • Response contains count with the total number of matching records.

v20240412 (cursor)

GET /appointments?...&per_page=100                     # first page
GET /appointments?...&per_page=100&end_cursor=MTA0OA   # next page
GET /appointments?...&per_page=100&start_cursor=MTAyNA # previous page
  • per_page - number of records per page.
  • start_cursor / end_cursor - opaque cursor strings. Mutually exclusive; passing both is a 400.
  • Response gains a page_info object and (generally) loses count:
{
  "code": true,
  "data": [ ... ],
  "page_info": {
    "has_previous_page": false,
    "has_next_page": true,
    "start_cursor": "MTAyNA",
    "end_cursor": "MTA0OA"
  }
}

How to iterate

To walk a collection forward, repeat the same request, each time passing the previous response's page_info.end_cursor value as the end_cursor query parameter, until has_next_page is false.

Yes - end_cursor fetches the next page. The cursors name the boundaries of the page you already have (start_cursor = its first record, end_cursor = its last), so continuing forward means resuming from the end boundary, and going backward means resuming from the start boundary.

First request - no cursor params:

GET /payments?subdomain=demo&location_id=1&updated_since=2024-04-12T00:00:00Z&per_page=100
Nex-Api-Version: v20240412
Authorization: Bearer <token>
{
  "code": true,
  "data": [ ...100 payments... ],
  "page_info": {
    "has_previous_page": false,
    "has_next_page": true,
    "start_cursor": "MTAyNA",
    "end_cursor": "MTA0OA"
  }
}

Next page - same request plus the end_cursor value returned above:

GET /payments?subdomain=demo&location_id=1&updated_since=2024-04-12T00:00:00Z&per_page=100&end_cursor=MTA0OA
Nex-Api-Version: v20240412
Authorization: Bearer <token>

Repeat until a response comes back with "has_next_page": false. To walk backward instead, pass the response's page_info.start_cursor value as the start_cursor query parameter.

Migration notes

  • You cannot jump to page N and you cannot compute "total pages": count is no longer returned on cursor endpoints. If your integration displayed totals or random-accessed pages, redesign around sequential iteration (has_next_page).
  • Cursors are opaque. Don't construct or parse them; only echo back the values from page_info. Requests with malformed cursors are rejected (400).
  • Cursors encode the sort position. Don't change the sort param (or filters) mid-iteration; cursors from one ordering are not valid for another.
  • One exception to the general story: GET /payment_plans already uses cursor pagination in v2.2.2 - nothing changes there.
  • GET /patient_recalls (cursor in v20240412) still returns count alongside page_info, and temporarily falls back to offset behavior if you pass a legacy page param. Treat that fallback as transitional - move to cursors.

Every endpoint in the reference section carries a Pagination note stating whether and how this applies to it.

4. include parameters: what changed, and the alternatives

v20240412 deliberately removes include support from most list (index) endpoints - embedding related records on every row of a collection was the main source of slow, oversized list responses. The general pattern:

  • List endpoints return flat objects with foreign keys (patient_id, provider_id, charge_id, …) and no embedded records.
  • Detail endpoints (GET /<resource>/{id}) keep include support - in most cases with more include values than v2.2.2 had.
  • Dedicated endpoints exist for most relations you previously embedded - procedures, insurance coverages, charges, payments, adjustments, appointments. None of them requires a per-patient filter, so you can crawl the whole collection and join it to its parents client-side - no need for one request per patient.

When you migrate a call that used include on a list endpoint, pick one of:

  1. Fetch the related collection in bulk from its dedicated endpoint and join client-side on the foreign key (patient_id, appointment_id, …). Best for syncs. What each endpoint minimally requires:

    • GET /insurance_coverages - no filters at all.
    • GET /procedures?updated_since=… - updated_since alone is a valid filter (pass your last sync time, or an
      old date for a full backfill).
    • GET /charges|payments|adjustments?location_id=…&updated_since=… - same idea, scoped per location.
    • GET /appointments?location_id=…&start=…&end=… - time-window based.

    Per-record filters (patient_id, patient_ids[], appointment_id, …) remain available for when you want a single parent's records on demand - they are optional, not required.

  2. Fetch details per record via GET /<resource>/{id}?include[]=…. Best when you drill into a handful of records, not whole collections.

Index endpoints that kept their includes (unchanged from v2.2.2): GET /providers (locations, availabilities, appointment_types), GET /operatories (appointment_types, appt_categories), and GET /appointment_types (descriptors).

The per-endpoint sections below map every removed include value to its concrete replacement.

5. Webhooks

The /webhook_endpoints CRUD is unchanged. The /webhook_endpoints/{id}/webhook_subscriptions endpoints changed in ways that matter for migration:

  • Subscriptions are pinned to the API version they were created with. A subscription created via v2.2.2 keeps delivering v2.2.2-shaped payloads. To receive v20240412-shaped payloads (e.g. the flat appointment entity), create a new subscription using Nex-Api-Version: v20240412 and delete the v2.2.2 one once you've cut over.
  • The v20240412 event catalog is a superset of v2.2.2's. Every v2.2.2 resource_type/event still exists. New resources:
    Adjustment, AdjustmentType, Charge, Claim, FeeSchedule, Message, Onboarding, Payment, PaymentType, TreatmentPlan (with *_created / *_updated / *_deleted, plus adjustment_insertion, payment_insertion, and onboarding_availability_ready).
  • GET …/webhook_subscriptions: subdomain is now optional. Omit it to list every subscription on the endpoint (including non-institution subscriptions); pass it to scope to one institution.
  • POST …/webhook_subscriptions: subdomain must be omitted for resources not owned by an institution (currently Onboarding) - passing it returns 400. For all other resources it remains required.

6. Endpoint-by-endpoint reference

Summary table (all public v2.2.2 endpoints)

v2.2.2 endpointStatusv20240412
POST /authenticatesunchanged
GET /institutions, GET /institutions/{id}unchanged
GET /locations, GET /locations/{id}, GET /locations/{id}/appointment_descriptorsunchanged
GET /patients⚠️cursor pagination, envelope + entity changes, includes removed
POST /patients⚠️ (additive)return_existing_if_match option
GET /patients/{id}⚠️entity changes; includes kept
GET /patients/{id}/insurance_coveragesGET /insurance_coverages?patient_id=…
GET /appointments⚠️cursor, patient_ids[], includes removed, cancelled/deleted semantics
POST /appointments⚠️working-hours validation removed; appointments_per_timeslot
GET /appointments/{id}unchanged (includes intact)
PATCH /appointments/{id}⚠️ (additive)reschedule/check-in/operatory/note updates
GET /appointments/{id}/appointment_descriptorsunchanged
GET /appointment_slotsGET /available_slots
GET/POST /availabilities, GET/PATCH/DELETE /availabilities/{id}/working_hours
GET/POST /appointment_types, GET/PATCH/DELETE /appointment_types/{id}, GET /appointment_types/{id}/appointment_descriptorsunchanged
GET /providers, GET /providers/{id}⚠️cursor + availability entity change
GET /operatories⚠️cursor pagination only
GET /operatories/{id}unchanged
GET /procedures⚠️updated_afterupdated_since, new date filters, cursor
GET /patient_recalls⚠️cursor pagination
GET /patient_recalls/{id}unchanged
GET /recall_types, GET /recall_types/{id}unchanged
GET /document_types, GET /document_types/{id}unchanged
GET /insurance_plans⚠️includes removed, cursor, new filters
GET /insurance_plans/{id}⚠️ (mostly additive)
  • fee_schedule include, deleted_at
GET /adjustments, GET /charges, GET /payments (+ /{id} variants)⚠️rebuilt for v20240412 - treat as new endpoints
GET /payment_plans, GET /payment_plans/{id}unchanged (already cursor-paginated)
GET /sync_statusunchanged
GET/POST /webhook_endpoints, PATCH/DELETE /webhook_endpoints/{id}unchanged
GET/POST …/webhook_subscriptions, PATCH/DELETE …/webhook_subscriptions/{subscription_id}⚠️version pinning, subdomain rules, new events

Authenticates

POST /authenticates - ✅ Unchanged

  • Same request/response. Keep sending your API key in the Authorization header; the returned Bearer token works for v20240412 requests exactly as for v2.2.2.
  • Pagination: not paginated (unchanged).

Institutions

GET /institutions, GET /institutions/{id} - ✅ Unchanged

  • Identical contract in v20240412.
  • Pagination: not paginated in either version.

Locations

GET /locations, GET /locations/{id}, GET /locations/{id}/appointment_descriptors - ✅ Unchanged

  • Identical contract in v20240412.
  • Pagination: not paginated in either version.

Patients

GET /patients - ⚠️ Changed

Request changes

Changev2.2.2v20240412
Paginationpage / per_pagestart_cursor / end_cursor / per_page (cursor)
include[]upcoming_appts, procedures, insurance_coverages, adjustments, charges, payments, addressremoved entirely
appointment_date_start / appointment_date_endembedded matching appointments in the responseremoved
sortfields name, created_at; default namefields id, updated_at; default id

Unchanged filters: subdomain (required), location_id (required), name, email, phone_number, date_of_birth, inactive, foreign_id, updated_since, new_patient, non_patient, location_strict.

Response changes

  • Envelope: data is now the array of patient objects directly. In v2.2.2 it was a nested map:
    {"data": {"patients": [...]}}{"data": [...]}.
  • Removed from the patient object: location_ids, balance (performance), preferred_locale (use preferred_language).
  • Added: hipaa object (privacy, consent, authorization + their dates).
  • count is gone; use page_info.

Include alternatives

v2.2.2 include valuev20240412 alternative
addressalready in the response - each patient's bio field carries the address (address_line_1, address_line_2, city, state, zip_code); GET /patients/{id}?include[]=address also returns it as a structured address object
upcoming_apptsGET /appointments?location_id=…&start=<now>&end=<horizon> in bulk, grouped by patient_id; or GET /patients/{id}?include[]=upcoming_appts per patient
proceduresGET /procedures?updated_since=… in bulk, joined on patient_id (patient_id filter available for a single patient)
insurance_coveragesGET /insurance_coverages in bulk - no filters required - joined on patient_id
adjustmentsGET /adjustments?location_id=…&updated_since=… in bulk, joined on patient_id
chargesGET /charges?location_id=…&updated_since=… in bulk, joined on patient_id
paymentsGET /payments?location_id=…&updated_since=… in bulk, joined on patient_id
(appointments via appointment_date_start/end)GET /appointments?location_id=…&start=…&end=… (optionally narrowed with patient_ids[])
  • Pagination: offset → cursor (breaking). Total count no longer returned.

POST /patients - ⚠️ Changed (additive, non-breaking)

  • New optional return_existing_if_match (default false). Default behavior matches v2.2.2: creating a patient that matches an existing one (date of birth + name + phone number) returns 400. With true, the existing patient is returned with 200 instead (a fresh create still returns 201). Adopt it if you currently parse the 400 to find existing patients.
  • Request body and response shape otherwise unchanged.
  • Pagination: not applicable.

GET /patients/{id} - ⚠️ Changed

  • Includes kept - all v2.2.2 values still work: upcoming_appts, last_visited_appointment, children, guarantor, provider, procedures, insurance_coverages, adjustments, charges, payments, patient_alerts, address.
  • Removed from the patient object: balance (use ✨ GET /guarantor_balances?... for balance data), preferred_locale (use preferred_language).
  • Added: hipaa object.
  • Changed within includes: the adjustments, charges, and payments includes return the new v20240412 transaction shapes (see the Adjustments, Charges and Payments API reference); procedures items gain treatment_plan_ids.
  • Pagination: not paginated (unchanged).

GET /patients/{id}/insurance_coverages - ❌ Removed

Replaced by the dedicated Insurance endpoints:

  • GET /insurance_coverages?patient_id={id} - same data, plus updated_since and active filters (active defaults to true, matching the old endpoint which only returned active coverages). The patient_id filter is optional: drop it to crawl every coverage for the institution in one paginated sweep instead of requesting per patient.
  • GET /insurance_coverages/{id} - single coverage with embedded subscriber details.
  • Pagination: the old endpoint was offset-paginated; the replacement is cursor-paginated.

Appointments

GET /appointments - ⚠️ Changed

Request changes

Changev2.2.2v20240412
Paginationpage / per_pagestart_cursor / end_cursor / per_page (cursor)
patient_idsingle integerdeprecated - still accepted but no longer documented; responses carry a deprecation notice in description. Use patient_ids[] (max 25 ids)
location_idrequiredoptional - but at least one of location_id or foreign_id is required; omitting location_id searches all locations your token can access
include[]patient (default!), guarantor, descriptors, booking_details, procedures, operatory, appointment_typeremoved entirely
sort-new; field updated_at (sort=-updated_at for descending). Default order: created_at ascending

Unchanged filters: start, end (required datetimes), timezone, unavailable, nex_only, updated_since, appointment_type_id, foreign_id, provider_ids[], operatory_ids[], created_by.

cancelled filter semantics changed - appointments deleted in the EHR are treated as cancelled:

cancelled paramv2.2.2 returnedv20240412 returns
omittedactive + cancelled (EHR-deleted always excluded)everything - active + cancelled + EHR-deleted
truecancelled and not EHR-deletedcancelled or EHR-deleted
falseactive onlyactive only (same)

If you relied on v2.2.2's default, the closest v20240412 equivalent is cancelled=false (you lose the cancelled-but-not-deleted rows; fetch them with a second cancelled=true call if needed).

Response changes

  • Rows are now flat appointment objects (id, patient_id, provider_id, provider_name, start_time, end_time, confirmed, patient_missed, note, unavailable, timestamps, …). The v2.2.2 default embedded patient object, and every other embed, is gone.
  • count is gone; use page_info.

Include alternatives

v2.2.2 include valuev20240412 alternative
patient (v2.2.2 default)fetch patients in bulk via GET /patients and join on patient_id; or GET /appointments/{id}?include[]=patient when drilling into one appointment
guarantorGET /appointments/{id}?include[]=guarantor, or GET /patients/{patient_id}?include[]=guarantor
descriptorsGET /appointments/{id}/appointment_descriptors (dedicated endpoint, unchanged from v2.2.2)
booking_detailsGET /appointments/{id}?include[]=booking_details
proceduresGET /procedures?updated_since=… in bulk, joined on appointment_id; or GET /procedures?appointment_id={id} for a single appointment
operatoryGET /operatories/{operatory_id}
appointment_typeGET /appointment_types/{appointment_type_id}

GET /appointments/{id} still supports all seven include values at once - it is the drop-in way to rehydrate a full v2.2.2-shaped appointment.

  • Pagination: offset → cursor (breaking).

POST /appointments - ⚠️ Changed

  • Working-hours validation removed. v2.2.2 rejected bookings whose time fell outside the provider's synced working hours; v20240412 does not run this check - out-of-hours bookings are accepted and written back to the EHR. If your UX depended on the API rejecting invalid slots, validate against ✨ GET /available_slots before booking.
  • New optional appointments_per_timeslot (1-5, default 1): allows multiple bookings on one slot. Only for EHRs that support multi-booking (eClinicalWorks, NextGen); other EHRs return 400 when the value exceeds 1.
  • Body (appt{…}), notify_patient, response shape and 201 status are unchanged.
  • Pagination: not applicable.

GET /appointments/{id} - ✅ Unchanged

  • Same contract, including include[] (patient default, guarantor, descriptors, booking_details, procedures, operatory, appointment_type).
  • Pagination: not applicable.

PATCH /appointments/{id} - ⚠️ Changed (additive, non-breaking)

v2.2.2 could only write back confirmed and cancelled (both keep their v2.2.2 behavior). v20240412 adds to the appt body:

  • start_time + end_time - reschedule (must be provided together),
  • operatory_id - move the appointment,
  • note - update the EHR note,
  • checkin_at - write back check-in status.

EHR support for the new fields is limited:

  • start_time/end_time, operatory_id and note are only supported when the location's EHR supports appointment updates - Denticon, Dentrix, Dentrix Enterprise, Eaglesoft and Open Dental. For other EHRs, a request changing any of these fields is rejected with a 400 ("EMR does not support appointment update").

  • checkin_at is written back to the EHR for Denticon, Dentrix, Dentrix Enterprise, Eaglesoft, ModMed, NextGen and Open Dental.

  • Pagination: not applicable.

GET /appointments/{id}/appointment_descriptors - ✅ Unchanged

  • Same contract (descriptor_type filter).
  • Pagination: not paginated in either version.

Appointment Slots → Available Slots

GET /appointment_slots - ❌ Removed → ✨ GET /available_slots

The endpoint was renamed; the request and response contracts are otherwise compatible. To migrate without adopting any new features:

  1. Change the path from GET /appointment_slots to GET /available_slots (with the Nex-Api-Version: v20240412 header).
  2. Keep your query parameters exactly as they are. Every v2.2.2 parameter is accepted unchanged: start_date, days, lids[], pids[], and the optional operatory_ids[], appointment_type_id, slot_length, slot_interval, overlapping_operatory_slots.
  3. Do not send the new optional parameters (working_hour_label_id, working_hour_source, appointments_per_timeslot) - leaving them out preserves the existing behavior.
  4. Read the response as before - same structure (per-location slots[] + next_available_date). Each slot carries one new field, working_hour_label_id, which you can ignore.

One requirement was relaxed, which does not affect existing callers: pids[] is no longer strictly required - v20240412 accepts either pids[] or appointment_type_id. Requests that already send pids[] behave as in v2.2.2.

  • Pagination: not paginated in either version (bounded by days).

Availabilities → Working Hours

GET/POST /availabilities, GET/PATCH/DELETE /availabilities/{id} - ❌ Removed → ✨ /working_hours

The resource was renamed; the model gained labels and an explicit source. Mapping:

v2.2.2v20240412
GET /availabilitiesGET /working_hours
POST /availabilitiesPOST /working_hours
GET /availabilities/{id}GET /working_hours/{id}
PATCH /availabilities/{id}PATCH /working_hours/{id}
DELETE /availabilities/{id}DELETE /working_hours/{id}

Request changes

  • Body wrapper key renames from availability{…} to working_hour{…}. The fields inside are identical (provider_id, operatory_id, begin_time, end_time, days[], specific_date, custom_recurrence{…}, appointment_type_ids[], active).
  • Index filters: location_id, provider_id, operatory_id, active, ignore_past_dates unchanged; new label_id and source (manual | synced) filters.
  • include[]=appointment_types works on index and show, as in v2.2.2.

Response changes

  • synced (boolean) is removed → replaced by source ("manual" = created in NexHealth, "synced" = from the EHR) and a label object (id, location_id, name).

  • GET /working_hour_labels lists the labels usable in filters.

  • Pagination: v2.2.2 index was offset-paginated; GET /working_hours is cursor-paginated. The other verbs are not paginated.


Appointment Types

GET/POST /appointment_types, GET/PATCH/DELETE /appointment_types/{id}, GET /appointment_types/{id}/appointment_descriptors - ✅ Unchanged

  • Identical contract in v20240412, including include[]=descriptors on the two GET endpoints.
  • Pagination: not paginated in either version.

Providers

GET /providers - ⚠️ Changed

  • Pagination: offset (page/per_page) → cursor (breaking). count removed.
  • New sort param (field updated_at).
  • include[] kept: locations (default), availabilities, appointment_types. Filters unchanged (location_id, ids[], foreign_id, requestable, inactive, updated_since).
  • Response change inside availabilities include: each availability loses synced and gains source (manual/synced) and a label object - same change as the working-hours model above.
  • For OAuth applications: the appointments scope no longer grants access; only appointments:book_online does. (API-key auth is unaffected.)

GET /providers/{id} - ⚠️ Changed (entity only)

  • Same params and includes as v2.2.2. Only the availabilities entity change above applies.
  • Pagination: not applicable.

Operatories

GET /operatories - ⚠️ Changed

  • Pagination: offset → cursor (breaking). count removed. Everything else - filters (search_name, foreign_id, updated_since) and include[] (appointment_types, appt_categories; default appt_categories) - is unchanged.
  • Same OAuth-scope narrowing as providers (appointments:book_online only) for OAuth applications.

GET /operatories/{id} - ✅ Unchanged

  • Identical contract (includes intact).
  • Pagination: not applicable.

Procedures

GET /procedures - ⚠️ Changed

Changev2.2.2v20240412
Paginationpage / per_pagecursor
updated_afterfilter on update timerenamed to updated_since (inclusive, UTC)
Date-range filters-new: started_after, started_before, ended_after, ended_before (inclusive, on start_date/end_date)
sort-new; field updated_at
At-least-one-filter ruleone of provider_id, patient_id, appointment_id, updated_afterone of provider_id, patient_id, appointment_id, updated_since, started_after, started_before, ended_after, ended_before

location_id still cannot be the only filter. The list response entity is unchanged.

  • ✨ New: GET /procedures/{id} - fetch a single procedure. Caveat (documented on the endpoint): the list endpoint is not filtered by your token's location access, so an id from the list can 404 on the detail endpoint if the procedure belongs to an inaccessible location.
  • Pagination: offset → cursor (breaking).

Patient Recalls

GET /patient_recalls - ⚠️ Changed

  • Pagination: offset → cursor (breaking) - with two transitional quirks: the response still includes count, and a legacy page param still triggers old-style offset pagination. Don't rely on the fallback.
  • Filters unchanged: recall_id, patient_id, foreign_id, updated_since, due_after, sort (field date_due).

GET /patient_recalls/{id} - ✅ Unchanged

  • Pagination: not applicable.

Recall Types

GET /recall_types, GET /recall_types/{id} - ✅ Unchanged

  • Pagination: not paginated in either version.

Document Types

GET /document_types, GET /document_types/{id} - ✅ Unchanged

  • Pagination: not paginated in either version.

Insurance Plans

GET /insurance_plans - ⚠️ Changed

Changev2.2.2v20240412
Paginationpage / per_pagecursor
include[]patient_coverages, subscribersremoved
updated_since-new (inclusive, UTC)
include_deleted-new (soft-deleted plans excluded by default)
sort-new; field updated_at

payer_id and group_num filters are unchanged. The plan entity gains deleted_at.

Include alternatives

v2.2.2 include valuev20240412 alternative
patient_coveragesGET /insurance_coverages - whole collection, no filters required (patient_id, updated_since optional) - or GET /insurance_plans/{id}?include[]=patient_coverages per plan
subscribersGET /insurance_plans/{id}?include[]=subscribers; subscriber details also come embedded on ✨ GET /insurance_coverages/{id}
  • Pagination: offset → cursor (breaking).

GET /insurance_plans/{id} - ⚠️ Changed (additive)

  • include[] keeps patient_coverages and subscribers, and adds fee_schedule.
  • Entity gains deleted_at; response includes the plan's fee schedule when requested.
  • Pagination: not applicable.

Adjustments, Charges & Payments

The financial endpoints (/adjustments, /charges, /payments) were rebuilt from the ground up for v20240412 and have not been available in v2.2.2 for some time, so there is no working v2.2.2 integration to migrate - build against the v20240412 API reference directly. In short:

  • GET /adjustments, GET /charges, GET /payments - list endpoints; each requires at least one filter and supports updated_since + include_deleted for incremental syncs.

  • GET /adjustments/{id}, GET /charges/{id}, GET /payments/{id} - detail endpoints with include[] support for related records (patient, provider, guarantor, claim, charge, and type/plan resources).

  • POST /adjustments and ✨ POST /payments - new write-back endpoints (asynchronous 202 Accepted; the outcome is delivered via the adjustment_insertion / payment_insertion webhooks).

  • Supporting lookups: ✨ GET /adjustment_types, ✨ GET /payment_types, ✨ GET /claims, ✨ GET /guarantor_balances, ✨ GET /insurance_balances.

  • Pagination: all three list endpoints are cursor-paginated (see section 3).


Payment Plans

GET /payment_plans, GET /payment_plans/{id} - ✅ Unchanged

  • Same contract in both versions, including the index's required-filter rule (one of patient_id, guarantor_id, updated_since) and the show endpoint's include[] (patient, guarantor).
  • Pagination: the index endpoint is cursor-paginated in v2.2.2 as well - it launched with the new scheme. Nothing changes.

Sync Status

GET /sync_status - ✅ Unchanged

  • Pagination: not paginated in either version.

Webhook Endpoints

GET/POST /webhook_endpoints, PATCH/DELETE /webhook_endpoints/{id} - ✅ Unchanged

  • Identical contract in v20240412.
  • Pagination: not paginated in either version.

Webhook Subscriptions

See section 5 for the version-pinning model - it is the key migration concern here.

GET /webhook_endpoints/{id}/webhook_subscriptions - ⚠️ Changed

  • subdomain required → optional (omit to list all subscriptions on the endpoint, including non-institution ones such as Onboarding).
  • resource_type / event filter enums expanded (superset of v2.2.2 - see section 5).
  • Pagination: not paginated in either version.

POST /webhook_endpoints/{id}/webhook_subscriptions - ⚠️ Changed

  • Subscriptions created here are pinned to v20240412 payloads - this is how you upgrade webhook payload shapes.
  • subdomain: still required for institution-owned resources, but must be omitted for Onboarding subscriptions (400 otherwise).
  • resource_type / event accept the expanded v20240412 catalog.
  • Pagination: not applicable.

PATCH /webhook_endpoints/{id}/webhook_subscriptions/{subscription_id} - ⚠️ Changed

  • subdomain is now required (v2.2.2 took no subdomain on this endpoint). When the subscription isn't institution-scoped (e.g. Onboarding), pass the subdomain of any institution your API user can access. Body (new_endpoint_id, active) unchanged. After moving a subscription with new_endpoint_id, address it via the new endpoint id in the path.
  • Pagination: not applicable.

DELETE /webhook_endpoints/{id}/webhook_subscriptions/{subscription_id} - ✅ Unchanged (204)

  • Pagination: not applicable.

7. New in v20240412 (no v2.2.2 equivalent)

Beyond the endpoints already covered above (/available_slots, /working_hours, /working_hour_labels, /insurance_coverages, the rebuilt financial endpoints, GET /procedures/{id}), v20240412 adds - all cursor-paginated on their index endpoints:

Endpoint(s)Purpose
GET /adjustment_types, GET /adjustment_types/{id}Adjustment types (needed for POST /adjustments)
GET /payment_types, GET /payment_types/{id}Payment types (needed for POST /payments)
GET /claims, GET /claims/{id}Insurance claims (includes: patient, provider, guarantor, charges, charge_payouts)
GET /guarantor_balances, GET /guarantor_balances/{id}Guarantor account balances (replaces the removed patient balance attribute)
GET /insurance_balances, GET /insurance_balances/{id}Insurance accounts-receivable balances
GET /fee_schedules, GET /fee_schedules/{id}, GET /fee_schedule_proceduresFee schedules and per-procedure fees
GET /clinical_notesClinical notes for patient encounters
GET /treatment_plans, GET /treatment_plans/{id}Treatment plans (procedures reference them via treatment_plan_ids)
GET/POST /onboardings, GET /onboardings/{hashid}Programmatic location onboarding

8. Migration checklist

  1. Switch the version header: replace Accept: application/vnd.Nexhealth+json;version=2 with Nex-Api-Version: v20240412 on every request.
  2. Convert pagination loops on every list endpoint you use: drop page increments, iterate with page_info.end_cursor / has_next_page, and remove any reliance on count totals.
  3. Replace list-endpoint includes using the per-endpoint alternative tables (detail-endpoint includes or dedicated endpoints joined by foreign keys).
  4. Appointments: switch patient_idpatient_ids[]; ensure at least one of location_id/foreign_id; decide how to handle the new cancelled/deleted default (cancelled=false for active-only); stop expecting embedded patient objects on the index; re-add server-side slot validation via /available_slots if you relied on the working-hours booking rejection.
  5. Scheduling: move /appointment_slots calls to /available_slots and /availabilities CRUD to /working_hours (body key availabilityworking_hour; syncedsource/label).
  6. Patients: unwrap data.patientsdata, pick up preferred_language instead of preferred_locale, source balances from /guarantor_balances, and re-point patient-coverage reads to /insurance_coverages.
  7. Webhooks: re-create your subscriptions under Nex-Api-Version: v20240412 so payloads use the new entity shapes, then delete the v2.2.2-pinned subscriptions after cutover.
  8. Sort params: re-check every sort you send - several endpoints changed fields/defaults (e.g. patients:
    name/created_atid/updated_at).

Based on the v2.2.2 and v20240412 API specifications as of 2026-07-09.