This page is published by Geach Technologies, LLC, trading as ChartVoyant, of Cleveland, Tennessee. It documents the format of the electronic health information (EHI) export the product produces, so that a practice, a patient, a successor vendor, or an auditor can read an export using ordinary tools and without our help. It is published to meet the documentation requirement of 45 CFR § 170.315(b)(10).
1. What an export contains
An export is everything the system holds for the requested scope — the designated record set — not the interoperability subset. It is deliberately broader than the FHIR API described by § 170.315(g)(10): that API exposes US Core resources, while this export carries the ledger, correspondence, intake responses, scheduling history, documents, and every other table in which a practice's data lives.
Two scopes are produced:
- Single patient — § 170.315(b)(10)(i). Every record related to one patient.
- Patient population — § 170.315(b)(10)(ii). Every record the practice holds.
Both are produced on demand from the running system by a practice administrator, and both
are limited to administrators (§ 170.315(b)(10)(iii)). Every export writes a permanent
ehi.exported entry to the practice's tamper-evident audit log naming who ran it,
the scope, and how many records left.
2. The archive
An export is a single ZIP archive (Deflate). Its layout:
manifest.json machine-readable description of this export
README.txt the same, in prose
data/<table>.ndjson one file per table; one record per line
File names under data/ are the database table names. No file is split; a
table with no matching records is still present, as an empty file, so that "absent" and
"empty" are distinguishable.
3. Record encoding
Each .ndjson file is newline-delimited JSON, UTF-8, one JSON
object per line, terminated by a newline. Each object is keyed by column name, and every
column of the table is present on every line — a missing value is null, never an
absent key.
| Type | Encoding |
|---|---|
| Text, numbers, booleans | The corresponding JSON type. |
| Dates and timestamps | ISO 8601 strings with a UTC offset, e.g. "2026-09-09T14:05:00.000Z". |
| JSON / JSONB columns | Inlined as JSON, not as a quoted string. |
| Arrays | JSON arrays, elements encoded by these same rules. |
Binary (bytea) | An object {"$binary":"<base64>"}. |
| Large integers | Decimal strings, to avoid precision loss. |
| NULL | null. |
Records are ordered by primary key where the table has a single-column one, so two exports of unchanged data are byte-comparable.
4. manifest.json
Read this first. It describes the export completely enough to process it without inspecting the data files.
{
"formatName": "ChartVoyant EHI Export",
"formatVersion": "1.0",
"specification": "https://chartvoyant.com/ehi-export/",
"certificationCriterion": "45 CFR 170.315(b)(10)",
"generatedAt": "2026-09-09T14:05:00.000Z",
"scope": { "kind": "patient", "patientId": "…" },
"practice": { "tenantId": "…" },
"generatedBy": { "principalId": "…", "principalType": "user", "roles": ["admin"] },
"encoding": { … },
"tables": [
{
"table": "conditions",
"rows": 14,
"columns": [ { "name": "id", "type": "uuid" }, … ],
"includedVia": "patient_id",
"redactedColumns": []
}
],
"excludedTables": [ { "table": "…", "reason": "…" } ],
"redactions": [ { "table": "…", "column": "…", "reason": "…" } ],
"totals": { "tables": 96, "rows": 4213, "uncompressedBytes": 8123456 }
}
scope.kind is "population" or "patient".
columns gives each column's name and its SQL type, which is the schema for the
matching .ndjson file.
5. How a table relates to the patient
includedVia says why a table is in a single-patient export:
"patient_id"— the table names the patient directly."via:<parent>.<column>"— the table's rows were selected because<column>points at rows of<parent>that belong to the patient. For exampleclaim_linesis includedvia:claims.claim_id: a claim line is part of a patient's record because its claim is."tenant"— a population export, where scoping is the practice itself.
Relationships are resolved from the database's own foreign keys rather than a curated list, so the export follows the schema as it changes.
6. What is deliberately not in the file
Two omissions, both disclosed inside the export itself rather than left to be discovered.
Redacted columns. A small number of columns hold authentication material
— password hashes, API keys, webhook signing secrets, one-time codes. That is not health
information, and writing it into a file that is downloaded to a workstation would turn a
compliance feature into a credential leak. Those columns carry a marker string instead of
their value. The rows and every other column are exported intact, and
manifest.redactions names every redacted column with its reason.
Excluded tables (single-patient exports only). Practice-level
configuration with no relationship to any individual — fee schedules, note templates, room
lists — is not part of one patient's record. Every such table is listed in
manifest.excludedTables with the reason, so the omission can be checked rather
than assumed. A population export excludes nothing.
7. Limits
An export is produced synchronously and delivered as a download, so it is bounded at 250,000 records or 200 MB of uncompressed data, whichever comes first. Exceeding either produces a clear error, never a silently truncated file. A practice past those bounds should export patients individually or contact info@chartvoyant.com to arrange a bulk transfer.
8. Reading an export
No special tooling is required. Unzip, then read the manifest and the lines:
unzip ehi-export-all-patients-2026-09-09.zip -d export
jq . export/manifest.json | head -40
# every condition recorded for the patient
jq -r '.code_display' export/data/conditions.ndjson
# load one table into pandas
python3 -c "import pandas; print(pandas.read_json('export/data/patients.ndjson', lines=True))"
Because every line is an independent JSON object, files can be streamed and need never be held in memory whole.
9. Reading an export back into ChartVoyant
ChartVoyant imports its own export. Upload the .zip through the practice
migration importer and it is recognised from manifest.json — no unpacking, no
conversion. Demographics, problems, medications, allergies, results, vitals, immunizations,
procedures, encounters, notes and coverage are read into the receiving chart; the importer
lists any table it does not map rather than dropping it silently, and rows the source chart
had deleted are not resurrected.
This is a clinical import, not a database restore. Identifiers, audit rows and internal bookkeeping are deliberately not carried across — the receiving practice gets a new chart, not a copy of another system's primary keys.
Exports produced before 10 September 2026 are missing the patient record.
A defect in the export's table discovery meant a single-patient archive contained everything
about the patient except the patients row itself, so it has no demographics and
cannot be imported. Re-export from ChartVoyant and the file will be complete. Population
exports were unaffected.
10. Versioning
formatVersion follows major.minor. A minor bump adds files,
tables, or manifest fields; a reader that ignores unknown keys keeps working. A
major bump changes or removes something a reader may already depend on. This
page always documents the current version, and prior versions remain published.
11. Questions
Format questions, and requests for a bulk transfer beyond the limits in §7: info@chartvoyant.com. A practice's own patients should direct right-of-access requests to their practice, which can produce a single-patient export from the ChartVoyant administration screen.