Documentation v1 · Updated September 21, 2026 · Geach Technologies, LLC · Cleveland, Tennessee.

FHIR and SMART are live; ONC certification is in progress. The public FHIR and authorization hosts are available. Access requires an enabled practice, a registered app and the appropriate patient or practice authorization. An internal security assessment has been completed, with follow-up remediation ongoing; an independent external security review has not been completed. ChartVoyant is not yet CHPL-listed. Use the published practice directory, discovery and a successful authorized request to confirm access for a practice.

This guide, the practice directory, and the complete EHI export format are public. No account, payment or agreement is required to read them.

1. Find a practice and discover endpoints

Fetch the directory and choose the patient’s practice by its Organization name. Its Endpoint address is the exact FHIR base and authorization audience. The directory is generated from active customer organizations on every request and may be cached for five minutes. Internal synthetic test offices are excluded from the customer directory. Customer onboarding and name/slug changes appear automatically.

GET https://chartvoyant.com/fhir/service-base-urls.json
Accept: application/fhir+json

GET https://fhir.chartvoyant.com/r4/{tenantSlug}/metadata
GET https://fhir.chartvoyant.com/r4/{tenantSlug}/.well-known/smart-configuration
GET https://auth.chartvoyant.com/.well-known/openid-configuration

The directory is a FHIR R4 collection Bundle containing Organization and Endpoint resources. Resolve each Organization.endpoint reference against its corresponding Endpoint entry. Use the published address unchanged as aud. The synthetic demonstration base is separately configured; never infer a customer’s practice from a patient identifier.

OperationPublic endpoint
Authorizehttps://auth.chartvoyant.com/oauth/authorize
Tokenhttps://auth.chartvoyant.com/oauth/token
Introspecthttps://auth.chartvoyant.com/oauth/introspect
Revokehttps://auth.chartvoyant.com/oauth/revoke
Public signing keyshttps://auth.chartvoyant.com/oauth/jwks.json

Read endpoint URLs, capabilities, scopes and client authentication methods from discovery. Do not treat an ID token as an API access token. Authenticated FHIR responses are not cacheable. Public discovery supports CORS; resources use bearer authorization, not a ChartVoyant login cookie.

2. Register an app

Use ChartVoyant Support or info@chartvoyant.com with your app name, developer contact, public/native/confidential type, exact redirect URI list, optional EHR launch URL, requested scopes, practice, and public JWKS or HTTPS jwks_uri when using asymmetric authentication. Send no patient records, private keys or client secrets. We record the request, apply the same objective identity and configuration checks to every developer, and register and enable a complete eligible request within five business days. We identify missing configuration promptly and provide a dated explanation and support contact for any delay.

ChartVoyant administrators manage app registrations, practice access, allowed permissions, and activation. Public/native apps use none; confidential apps use client_secret_basic or private_key_jwt. Backend services require a practice’s explicit authorization and public RS384 or ES384 keys. Registration does not grant patient consent. Patient resource checkboxes determine the actual grant.

Redirect URIs must match registration exactly. Native apps may use exact registered HTTP loopback callbacks (localhost or 127.0.0.1) or HTTPS callbacks; custom URI schemes are currently unsupported; use the system browser, PKCE and secure operating-system token storage. Keep confidential secrets on a server. Public apps must not embed a secret. HTTPS JWKS endpoints must be publicly reachable without redirects and return public keys only. Publish new keys before using their kid, retain old keys while assertions can remain valid, and respect key-cache headers.

3. Authorization code flows

Every code flow requires a cryptographically random state, a new verifier of 43–128 permitted characters, and S256. Plain or missing PKCE is rejected. When requesting OpenID identity, include a fresh nonce. This browser example creates the shared parameters; replace the placeholders with your registered values and discovered endpoints.

const base = "https://fhir.chartvoyant.com/r4/your-practice";
const discovery = await fetch(base + "/.well-known/smart-configuration").then(r => r.json());
const b64url = bytes => btoa(String.fromCharCode(...bytes))
  .replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
const random = () => b64url(crypto.getRandomValues(new Uint8Array(32)));
const verifier = random(), state = random(), nonce = random();
const challenge = b64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))));
const params = new URLSearchParams({
  response_type: "code", client_id: "YOUR_REGISTERED_CLIENT_ID",
  redirect_uri: "https://your-app.example/callback", aud: base,
  scope: "launch/patient openid fhirUser offline_access patient/Patient.rs patient/Observation.rs",
  code_challenge: challenge, code_challenge_method: "S256", state, nonce
});
// Keep state, nonce and verifier in this app's protected authorization transaction.
location.assign(discovery.authorization_endpoint + "?" + params);

Patient standalone

Use the parameters above. The patient uses the existing portal account sign-in, chooses the office when applicable, and reviews consent. Portal enrollment verifies a code sent to a contact already held by the practice. They can uncheck a resource class or narrow a supported category. The callback receives a short-lived code and state. Verify state before redeeming the code. Use the returned patient context; the app cannot supply a different patient to expand access.

Clinician standalone

Use the same PKCE transaction with the following scope value. The clinician completes staff sign-in and MFA, selects a patient, and consents. The ID token’s fhirUser identifies the clinician’s Practitioner resource. Standalone chart choice remains part of the authorization context.

params.set("scope", "launch/patient openid fhirUser offline_access user/Patient.rs user/Observation.rs");

EHR launch

The clinician opens a chart and selects the registered SMART app tile. ChartVoyant invokes the app’s launch URL with iss and an opaque launch. Treat the launch value as a credential: do not log it. Validate iss against a chosen/published practice, fetch that base’s discovery, and use a new PKCE transaction with these changes:

const incoming = new URL(location.href).searchParams;
params.set("aud", incoming.get("iss"));
params.set("launch", incoming.get("launch"));
params.set("scope", "launch openid fhirUser offline_access user/Patient.rs user/Observation.rs");
// Redirect to this practice's discovered authorization_endpoint. Code exchange is unchanged.

The launch is bound to the originating clinician, app, patient and optional encounter. Its token response includes patient, encounter when present, need_patient_banner and smart_style_url. Honor those values when displaying the chart context.

Exchange the code and read a resource

Codes expire after 120 seconds and can be used once. POST form-encoded data to the discovered token endpoint. For a confidential symmetric client, authenticate with HTTP Basic using its client ID and secret. For a public client, send client_id with no secret. Asymmetric clients add a signed client assertion as described below.

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&client_id=YOUR_CLIENT_ID&code=RETURNED_CODE&redirect_uri=https%3A%2F%2Fyour-app.example%2Fcallback&code_verifier=YOUR_VERIFIER

HTTP/1.1 200 OK
Cache-Control: no-store
{
  "access_token": "OPAQUE_ACCESS_TOKEN",
  "token_type": "Bearer", "expires_in": 3600,
  "scope": "launch/patient openid fhirUser offline_access patient/Patient.rs",
  "patient": "RETURNED_PATIENT_ID",
  "id_token": "SIGNED_ID_TOKEN", "refresh_token": "OPAQUE_REFRESH_TOKEN"
}

GET /r4/your-practice/Patient/RETURNED_PATIENT_ID
Authorization: Bearer OPAQUE_ACCESS_TOKEN
Accept: application/fhir+json

This example shows the patient declining Observation sharing. Always use the returned scope, not the requested scope. Validate ID-token signature with the discovered JWKS, RS256 algorithm, issuer, client audience, expiry and nonce. Its fhirUser is a Patient or Practitioner URL. Re-fetch public keys on an unfamiliar kid and respect Cache-Control. Access tokens are opaque, last one hour and must only be sent to the intended resource server.

4. Backend services

After practice-authorized registration, sign a fresh JWT assertion using RS384 or ES384. Its iss and sub are your client ID, aud is the exact token endpoint, exp is no more than five minutes ahead, and jti is unique per request. Publish the matching kid in your registered public JWKS. Replayed assertions and unregistered algorithms/keys are rejected.

// JWT header (sign with your private key; never upload the private key)
{"alg":"RS384","typ":"JWT","kid":"YOUR_PUBLIC_KEY_ID"}
// JWT claims
{"iss":"YOUR_CLIENT_ID","sub":"YOUR_CLIENT_ID",
 "aud":"https://auth.chartvoyant.com/oauth/token",
 "iat":CURRENT_UNIX_SECONDS,"exp":CURRENT_UNIX_SECONDS_PLUS_300,
 "jti":"NEW_RANDOM_IDENTIFIER"}

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&scope=system%2FPatient.rs%20system%2FObservation.rs&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=YOUR_SIGNED_JWT

The response contains access_token, token_type, expires_in and the granted scope. Backend tokens have no refresh token or user ID token. Obtain a new access token with a fresh assertion. Every backend client is restricted to its enabled practice and allowed scopes. A patient or clinician token cannot start a population export.

5. Scope and token lifecycle

ScopeMeaning
patient/Observation.rsRead and search Observations within the patient context.
user/Observation.rsRead and search within the authenticated clinician’s permitted context.
system/Observation.rsPractice-authorized backend access.
patient/*.read, user/*.readSMART v1 read/search forms; consent expands supported resource classes.
.r / .s / .rsInstance read / search / both; write scopes are unsupported.
?category=system|codeSupported Condition/Observation category constraints restrict returned records.
openid fhirUserSigned identity claims; these are not resource permissions.
offline_accessPatient-approved access when the app user is away; refresh still ends on revocation or expiry.
online_accessRefresh access bounded by the authorizing login session.

Wildcards cover supported types and remain subject to consent and office policy. Included and reverse-included resources require their own granted scopes. A patient token cannot change patient or practice by altering a URL. Unsupported scopes are rejected or excluded from the grant; inspect the token response.

Each offline refresh token is valid for 95 days from issuance, including every replacement issued during rotation. Refreshing does not require a new sign-in or consent; the previous refresh token becomes invalid. Revocation, account changes and app disablement still end access immediately. Online refresh expires with the authorizing session, even if a refreshed access token requests narrower scopes. Do not use offline_access unless ongoing access has been explained to the patient.

POST /oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&client_id=YOUR_CLIENT_ID&refresh_token=CURRENT_REFRESH_TOKEN

POST /oauth/introspect
Authorization: Basic BASE64_CLIENT_ID_AND_SECRET
Content-Type: application/x-www-form-urlencoded

token=ACCESS_TOKEN

POST /oauth/revoke
Content-Type: application/x-www-form-urlencoded

client_id=YOUR_CLIENT_ID&token=CURRENT_REFRESH_TOKEN&token_type_hint=refresh_token

Use the registered client authentication method on refresh, introspection and revocation. Keep only the returned replacement refresh token. An optional refresh scope can narrow access. Patients can revoke a grant in Portal → Connected apps. App disablement, user logout/account revocation and grant revocation invalidate the associated refresh and derived access tokens on subsequent use. A successful revocation response is intentionally not proof that an unknown token existed.

6. FHIR R4 and US Core 6.1

The service is a read-only facade over the practice’s current records. Resource meta.profile lists the applicable profile; missing data uses profile-permitted absent-data representations. The CapabilityStatement is authoritative for supported interactions, profiles and combinations. GET searches and POST /{type}/_search return searchset Bundles. Follow the supplied link[next] until absent. Common parameters are _id and _lastUpdated; date comparisons use FHIR prefixes such as ge, gt, le and lt.

GET /r4/your-practice/Observation?patient=PATIENT_ID&category=laboratory&date=ge2026-01-01&_count=50
GET /r4/your-practice/MedicationRequest?patient=PATIENT_ID&intent=order&_include=MedicationRequest:medication
GET /r4/your-practice/Condition?patient=PATIENT_ID&_revinclude=Provenance:target
GET /r4/your-practice/DocumentReference/$docref?patient=PATIENT_ID
GET /r4/your-practice/Patient/PATIENT_ID/$everything
ResourceAdditional search parameters
MedicationDispensepatient, status, type, whenhandedover
RelatedPersonpatient
Specimenpatient
QuestionnaireResponsepatient, status, authored, questionnaire
Patientidentifier, name, family, gender, birthdate, death-date
PractitionerRolepractitioner, specialty
Endpointname, status
Practitioneridentifier, name
Organizationname, address
Locationname, address, address-city, address-state, address-postalcode
AllergyIntolerancepatient, clinical-status, code
CarePlanpatient, category, status, date
CareTeampatient, status, role
Conditionpatient, category, clinical-status, code, onset-date, recorded-date, asserted-date, abatement-date, encounter
Coveragepatient
Devicepatient, type
DiagnosticReportpatient, category, code, date, status
DocumentReferencepatient, category, type, date, period, status
Encounterpatient, date, class, type, status, identifier
Goaldescription, patient, lifecycle-status, target-date
Immunizationpatient, date, status
MedicationRequestpatient, intent, status, authoredon, encounter
Observationpatient, category, code, date, status
Procedurepatient, date, code, status
Provenancepatient
ServiceRequestpatient, category, code, status, authored
Medication_id and _lastUpdated; included from MedicationRequest.
GroupTenant group discovery and $export require backend Group permissions.

Observation profiles cover laboratory, vital signs (including BP components and pediatric measures), smoking, SDOH, clinical tests, surveys, pregnancy, occupation and screening/assessment. Condition includes problems/health concerns and encounter diagnoses; DiagnosticReport includes laboratory and note profiles. The remaining classes cover medications, allergies, immunizations, procedures, care plans/teams/goals, coverage, devices, documents, encounters, service requests, specimens, questionnaires, related people, directories and provenance.

7. Bulk Data 2.0

Discover the practice’s Group using a backend token with system/Group.rs. Request only authorized resource types. Kickoff requires these headers; optional _since is a FHIR instant and includes resources changed strictly after it. _outputFormat supports application/fhir+ndjson. A token with system/*.rs may request all supported export classes.

GET /r4/your-practice/Group/GROUP_ID/$export?_type=Patient,Observation&_since=2026-01-01T00%3A00%3A00Z
Authorization: Bearer BACKEND_ACCESS_TOKEN
Accept: application/fhir+json
Prefer: respond-async

HTTP/1.1 202 Accepted
Content-Location: STATUS_URL

GET STATUS_URL
Authorization: Bearer BACKEND_ACCESS_TOKEN

HTTP/1.1 202 Accepted
X-Progress: Preparing export

// When ready: 200 with an example manifest
{"transactionTime":"2026-09-13T12:00:00Z","request":"ORIGINAL_REQUEST_URL",
 "requiresAccessToken":true,
 "output":[{"type":"Patient","url":"SIGNED_FILE_URL","count":3}],"error":[]}

GET SIGNED_FILE_URL
Authorization: Bearer BACKEND_ACCESS_TOKEN

DELETE STATUS_URL
Authorization: Bearer BACKEND_ACCESS_TOKEN

Poll according to Retry-After when supplied, otherwise back off between polls. Ready files contain one FHIR resource per line. File links expire after 24 hours and still require a current authorized backend token for the original client/practice and sufficient scopes. Keep signatures intact. DELETE cancels the job and invalidates subsequent downloads. Check error entries and do not treat partial output as a complete export. Complete EHI export additionally includes non-FHIR records and original files; see its public format.

8. Limits and errors

Default FHIR budgets are 300 requests/second and 9,000/minute per principal, and 600/second and 18,000/minute per practice; an IP budget also applies. Operators can lower or raise configured limits consistently. A 429 response supplies Retry-After. Use pagination and Bulk Data for large authorized reads. Need a higher limit? Request one at info@chartvoyant.com with your app, the traffic it sends and the peak rate you need. There is no fee, and a budget we raise is raised for every app alike. Limits are not a fee or a condition of registration.

StatusClient action
400Correct unsupported/malformed parameters or an OAuth invalid_request/invalid_grant.
401Supply a valid current bearer token; reauthorize if revoked.
403Access is outside granted scope, patient, practice or enabled policy.
404Resource or interaction is unavailable; do not infer another patient’s existence.
410A known deleted resource is no longer available.
429Wait for Retry-After and reduce concurrency.
5xxRetry transient failures with backoff; contact support with a correlation ID.

FHIR errors use application/fhir+json OperationOutcome; OAuth endpoints use OAuth JSON error fields. Do not place patient data, codes, tokens or signed download links in support messages or analytics. Transport requires HTTPS; prepared edge policy requires TLS 1.2 or later. Source code and local tests are not proof of deployed TLS configuration.

9. Fees, terms and non-discrimination

No fees apply to patients, practices or app developers for this standardized API, registration, testing, production access, calls, Bulk Data, or its documentation. There is no revenue share for this interface. The separate proprietary API’s commercial agreement does not apply to SMART/FHIR access.

Use an authorized patient or practice grant and keep access within its actual scope. Protect credentials, use HTTPS, describe your app’s data handling to the user, honor revocation, and meet laws applicable to your app. These requirements apply equally to all developers. We do not require an app to buy another product, disclose source code, share revenue, or compete on preferred terms to obtain access. We do not withhold access because an app competes with ChartVoyant.

Nothing in these terms prohibits communications about usability, interoperability, security, user experiences or business practices protected under the certification program. Good-faith security reports and interoperability complaints can be sent through Support. We record and investigate access failures, provide an explanation and correction path, and support developers through registration and use. Reading this documentation requires no assent. Any change to access policy will be published here; existing integrations receive reasonable compatibility support.

Standards and contact

FHIR R4 · US Core 6.1 · SMART App Launch 2.0 · Bulk Data 2.0 · ONC API Conditions.

Support: info@chartvoyant.com · Support form. Registration target: five business days for a complete eligible request.