Data Model & Grain
This page describes the shape of Candid Data Share — how the tables relate, what one row of each table represents (its grain), and the join paths that connect them. It complements the per-table column references linked from the Overview. It describes structure only and contains no sample data.
Entity hierarchy
Candid Data Share is organized around a single billing hierarchy, with financial summaries and reference tables hanging off of it.
- Encounter (
export_encounter) is the top-level clinical/billing event for a patient visit. Claim-level fields (claim_id,claim_status,claim_patient_control_number, …), every provider role, patient/guarantor identity, and the primary/secondary/tertiary insurance blocks are all flattened onto this one row. There is no separate claim table — claim attributes live on the encounter. - Service Line (
export_service_line) is one procedure/line item within an encounter (encounter_id). It carries up to four positional diagnosis pointers (diagnosis_{zero,one,two,three}_code) and themodifiersarray. - Charge is the billed A/R unit, identified by
charge_id. Charge attributes are exposed throughexport_charge_details; payments and adjustments tie back to the charge they offset viacharge_id. - Transaction (
export_transaction) is the financial ledger entry. Itstransaction_typeis one ofCHARGE,PAYMENT,ADJUSTMENT, orBALANCE_TRANSFER, and each balance-impacting type has a matching detail table joined ontransaction_id.
Grain reference (one row = ?)
Every table falls into one of three shapes. Knowing which one before you aggregate is the single most important thing to get right.
Current-state tables (one row per entity)
These are refreshed to reflect the latest state — there is only ever one row per ID, so there is nothing to deduplicate. Query them directly.
Version history tables (many rows per patient or note)
These keep one row per version, so a patient or note has many rows over time. Filter on the latest-record flag for current state; don’t aggregate across versions.
Transaction tables (many rows per charge)
These hold one row per financial event, so a single charge, service line, or encounter has many rows. To get a current total you aggregate across rows and filter out voids (see below).
export_transaction is the only ledger table — it carries amount_cents. The *_details tables annotate its rows (payment type, CARC codes, charge attributes) and have no amounts to aggregate.
None of these tables is append-only. Existing rows can change: when a transaction is voided, its voided_by_transaction_id is filled in, and updated_at also moves when related claim or batch data changes. For incremental ingestion, pull rows by updated_at daily and run a full refresh weekly to stay fully in sync.
Sum export_transaction.amount_cents (excluding voids) at whatever grain you need: charge_id, service_line_id, or encounter_id. Only the encounter_id rollup captures claim-level transactions (target_type = 'CLAIM'), which have no charge_id or service_line_id. Add counter_party_type (or counter_party_id) to the grouping to see how much of that balance sits with each counterparty — a payer, the patient, or a non-insurance payer.
Voids and re-adjudication
A void never changes the original transaction’s amount. Instead, a new, linked row reverses it:
voids_transaction_id— set on the reversing transaction, pointing at the transaction it reverses.voided_by_transaction_id— set on the original transaction once it has been reversed.
To get the current, live ledger, exclude both sides of every void:
Also note the sign convention on amount_cents: charges are positive, payments are usually negative, and reversals/refunds are positive.
Key join paths
All keys below are real columns on the published tables.
- Encounter → Service Line → Transaction:
export_encounter.encounter_id→export_service_line.encounter_id;export_service_line.service_line_id→export_transaction.service_line_id. - Transaction → detail tables:
export_transaction.transaction_id→export_{charge,payment,adjustment}_details.transaction_id, filtered bytransaction_type('CHARGE','PAYMENT','ADJUSTMENT'). - Tie payments/adjustments to their charge:
charge_idis present onexport_transaction,export_charge_details,export_payment_details, andexport_adjustment_details— aggregate bycharge_idto net payments and adjustments against the original billed amount. - Financial rollups:
export_financial_summaryjoins onencounter_id;export_service_line_financial_summaryandexport_service_line_projected_financialsjoin onservice_line_id. - Claim submissions:
export_claim_submission_historyjoins toexport_encounteronencounter_id(orclaim_id). Order a claim’s submissions bysubmission_number. - Batches:
export_transaction.batch_id→export_batch_details.batch_id(closed_at IS NULLmarks the open, current period). - Patients:
export_patient.mrn→export_encounter.patient_external_idwithin the sameorganization_id. Every otherexport_patient_*table joins toexport_patientonpatient_id. - Payers:
export_payeris keyed bypayer_uuid; onexport_transaction,counter_party_idequals the payer UUID whencounter_party_typeis a payer.payer_plan_group_idappears on several transaction tables as an identifier but has no dedicated lookup table in the data share. - Informational adjustments:
export_informational_adjustment_detailslinks directly toencounter_id,service_line_id, andcharge_id(non-balance-impacting; see the Financial Transactions overview).
Refresh cadence
Tables are refreshed nightly. Candid Data Share is not a live, real-time view of a patient’s balance and must not be used for patient invoicing - use the patient invoicing integration guide for that. For incremental ingestion guidance, see Candid Data Share: Incremental Data Pulls.