Components

React

Add a complete connected billing workspace or compose submission, search, reporting, and lifecycle surfaces individually.

Add billing to your application

Install the package; components include their own styles. First connect an authenticated server session route; the examples below assume that route is ready.

Terminal
pnpm add @mindbill/react@0.73.0
  • Billing page: ConnectedBillingWorkspace provides task queues, All Bills, reports, bill details, and a Settings tab.
  • Case billing tab: BillSubmissionForm before submission; ConnectedBillLifecycle afterward.
  • Billing settings: use the built-in Settings tab, or mount BillingSettings separately. The server requires an admin-authorized session.
Example: place components in your existing routes
billing.jsx
"use client";
import {
  ConnectedBillingWorkspace, ConnectedBillLifecycle,
  BillSubmissionForm, BillingSettings, type BillSubmissionFormProps,
} from "@mindbill/react";

// Sidebar -> /billing. The workspace owns bill selection and detail navigation.
export function BillingPage({ canManageBilling }: { canManageBilling: boolean }) {
  return <ConnectedBillingWorkspace
    sessionEndpoint="/api/mindbill/session"
    showSettings={canManageBilling}
    billingSettings={{ sessionEndpoint: "/api/mindbill/settings-session" }}
    style={{ height: "calc(100dvh - 96px)", minHeight: 0 }}
    onCreateBill={() => { window.location.href = "/billing/new"; }}
  />;
}

// Case -> Billing tab after Report. Host server authorizes the case and resolves
// its saved bill ID; return a bill-scoped session from this separate host route.
export function CaseBilling({ caseId, billId }: { caseId: string; billId: string }) {
  return <ConnectedBillLifecycle billId={billId}
    sessionEndpoint={"/api/cases/" + encodeURIComponent(caseId) + "/billing-session"} />;
}

// Map existing structured case data to BillSubmissionInput, persist result.billId
// using your existing case metadata/API, and keep unknown fields editable.
export function NewBill({ canManageBilling, ...billProps }: Pick<
  BillSubmissionFormProps, "initialBill" | "attachments" | "onSubmitted"
> & { canManageBilling: boolean }) {
  return <BillSubmissionForm {...billProps}
    {...(canManageBilling ? { billingSettings: {
      sessionEndpoint: "/api/mindbill/settings-session"
    } } : {})}
    sessionEndpoint="/api/mindbill/session" />;
}

// Optional standalone settings page; the workspace already has a Settings tab.
// This administrator-only endpoint grants organization:manage.
export function PracticeBillingSettings() {
  return <BillingSettings sessionEndpoint="/api/mindbill/settings-session" />;
}

Keep the returned billId in your existing case metadata. Send your case ID as externalId for correlation; it is not a uniqueness guarantee. Never create a bill on component mount.

Give the workspace a bounded height and min-height: 0 in flex ancestors when your app disables page scrolling.

Component catalog

All React exports and when to use them
ExportUse it whenOwns API calls
FeeScheduleCalculatorCalculate multiple California treatment lines with modifiers, sources, and coding review. Integration guide.Through your reference client
BillSubmissionFormYou want the complete form, reference data, validation, attachments, and atomic Submit action.Yes
ReportAutofillReview optional, capability-gated PDF suggestions before applying them to empty fields.Yes, or through your adapter
BillSubmission*SectionYou want the same component-owned form state with individually composable sections.Yes, through the parent
ConnectedBillingWorkspaceYou want Bill Tasks, All Bills, reports, settings, and per-bill lifecycle in one integrated surface.Yes
ConnectedBillSearchYou need the authoritative bill registry with patient, bill, claim, status, and A/R filters.Yes
ConnectedBillTasksDashboardYou need actionable billing queues grouped by task and age.Yes
ConnectedServiceLineItemsReportYou need original submissions grouped by procedure code.Yes
ConnectedProductivityReportYou need created, transmitted, submitted, and acceptance performance by biller.Yes
BillingDashboardYou need receivables KPIs, aging buckets, search, filters, and a responsive bill list, plus optional settings.Settings only
BillListYou need only the searchable and filterable bill directory.No
BillAgingSummaryYou need only receivables and aging KPIs.No
BillingReportYou need grouped status, payer, or aging reporting.No
BillStatusAgingMatrixYou need the status × aging management grid with drill-down cells and totals.No
BillReadOnlyFormYou want the same bill layout after submission without editing.No
ConnectedBillLifecycleYou want the complete post-submission workflow.Yes
useBillLifecycleYou want custom post-submission lifecycle UI.Yes
ConnectedBillStatusYou need compact status and valid next actions.Yes
useBillStatusYou want custom status UI.Yes
BillStatusSummaryYou need a presentational status card.No
BillLifecycleActionsYou need state-aware actions in your own layout.No
BillLifecycleProgressYou need the horizontal bill lifecycle.No
BillSnapshotSummaryYou need a compact submitted CMS-1500 snapshot.No
BillExplanationOfReviewYou need the consolidated EOR, denial, remittance, and payment reconciliation surface.No
BillRemittanceCardYou need payer-reported, posted, and balance amounts.No
BillPayerContactCardYou need payer and adjuster follow-up contacts.No
BillPaymentLedgerYou need posted payment history.No
BillActivityTimelineYou want to render the bill's complete history.No
MindBillBillReviewYou prefer the hosted post-submission review surface.Hosted
MindBillBillTimelineYou prefer the hosted timeline surface.Hosted

Complete submission form

BillSubmissionForm owns which fields exist and which are required. It renders red asterisks, validates the values, resolves billing reference data through a short-lived browser session, lets users review prefilled documents and add uploads, and renders the Submit button. It never creates a MindBill draft.

Submission session permissions

This excerpt assumes your server has authenticated the user, checked membership, and selected the organization credential. See the complete server route for origin checks and error handling.

server/submission-session.ts
app.post("/api/mindbill/submission-session", async (req, res) => {
  const user = await requireSignedInUser(req);

  res.json(await mindbill.createBrowserSession({
    subject: user.id,
    permissions: ["bills:create", "payers:read"],
    allowedOrigin: process.env.APP_ORIGIN!,
    expiresIn: 900,
  }));
});
CaseBilling.tsx
import { BillSubmissionForm } from "@mindbill/react";

<BillSubmissionForm
  initialBill={toBillSnapshot(caseRecord)}
  attachments={selectedBillingDocuments}
  sessionEndpoint="/api/mindbill/submission-session"
  appearance={{ preset: "orange-bright" }}
  submitLabel="Submit bill"
  onSubmitted={({ billId }) => rememberBillId(billId)}
/>

Optionally pass reportAutofill with a separately authorized connection, or use ReportAutofill in a custom form. The report autofill guide covers access, explicit review, and preserving existing values.

Prefill case data and final-report attachments

Map data you already have: patient name, date of birth and address; claim, employer and injury date; service date; diagnosis codes; procedure codes, modifiers and units. Keep missing values editable. Confirm a canonical claims administrator in the payer picker; a text label from a report is not a verified payer ID.

Use your existing structured extraction results, or opt into the capability-gated report autofill component from React 0.67.0. Review suggestions before applying them to empty fields. Do not infer clinical codes or fabricate missing information.

host-billing-adapters.jsx — source field names belong to your app
// Host adapter example: rename these source fields to your existing structured data.
// This med-legal adapter omits treatment charges and line-level diagnosis pointers.
// For professional treatment, use /learn/treatment-quickstart.
// Do not infer clinical codes or upload every case document during SDK setup.
export function billFromCase(caseRecord) {
  return {
    externalId: String(caseRecord.id),
    patient: {
      firstName: caseRecord.patient.firstName ?? "",
      lastName: caseRecord.patient.lastName ?? "",
      dateOfBirth: caseRecord.patient.dateOfBirth ?? "", // YYYY-MM-DD
      address: {
        line1: caseRecord.patient.address?.line1 ?? "",
        city: caseRecord.patient.address?.city ?? "",
        state: caseRecord.patient.address?.state ?? "",
        postalCode: caseRecord.patient.address?.postalCode ?? "",
      },
    },
    claim: {
      claimNumber: caseRecord.claimNumber ?? "",
      employer: caseRecord.employerName,
      dateOfInjury: caseRecord.dateOfInjury,
    },
    service: { date: caseRecord.dateOfService ?? "" },
    diagnoses: caseRecord.diagnosisCodes ?? [],
    serviceLines: (caseRecord.billingLines ?? []).map((line) => ({
      code: line.code, modifiers: line.modifiers, units: line.units,
    })),
    // Optional: billingProvider, renderingProvider, serviceLocation from
    // your saved host settings. Otherwise let the user supply/review them.
  };
}

// Only pass the finalized report. An empty list leaves upload to the user.
// documentId identifies a HOST document, not a MindBill document.
export function finalReportSources(documentId) {
  if (!documentId) return [];
  return [{
    id: documentId, fileName: "final-report.pdf",
    documentType: "final_report", autoAttached: true, removable: true,
    loadBlob: async () => {
      // Existing authenticated host endpoint: enforce document/case ownership.
      const response = await fetch("/api/documents/" + encodeURIComponent(documentId), {
        credentials: "same-origin", cache: "no-store",
      });
      if (!response.ok) throw new Error("Unable to load finalized report");
      return response.blob();
    },
  }];
}

Pass billFromCase(caseRecord) as initialBill and finalReportSources(finalizedReportId) as attachments. All supplied source attachments are included unless removed: do not pass every case document or rely on selected: false. Users can add proof of service and other supporting PDFs in the form, including a final report edited outside your app.

BillSubmissionFormSynthetic data · edit and run
import { useState } from "react";
import { BillSubmissionForm } from "@mindbill/react";

const initialBill = {
  externalId: "report_9f7a",
  billingMode: "med_legal",
  patient: {
    externalId: "patient_42", firstName: "Alex", lastName: "Morgan",
    dateOfBirth: "1988-03-05",
    address: { line1: "100 Main St", city: "Fresno", state: "CA", postalCode: "93721" },
  },
  claim: {
    externalId: "claim_17", claimNumber: "WC-44871",
    employer: "Northstar Foods", dateOfInjury: "2025-05-30", injuryState: "CA",
    claimsAdministrator: { name: "Republic Indemnity" },
  },
  service: { date: "2026-08-26" },
  billingProvider: {
    name: "Northstar Evaluations", taxId: "123456789", npi: "1023401122",
    address: { line1: "236 W Mountain St", city: "Pasadena", state: "CA", postalCode: "91103" },
  },
  renderingProvider: {
    name: "Morgan Chen, MD", npi: "1098765432",
    licenseNumber: "A140748", licenseState: "CA",
  },
  serviceLocation: {
    name: "West Covina Exam Office", placeOfServiceCode: "11",
    address: { line1: "1050 West Lakes Dr", city: "West Covina", state: "CA", postalCode: "91790" },
  },
  diagnoses: ["M25.562"],
  serviceLines: [{ code: "ML201", modifiers: ["95"], units: 1, charge: 2015 }],
};

const attachments = [
  { id: "doc_report", fileName: "final-report.pdf", documentType: "final_report", selected: true },
  { id: "doc_pos", fileName: "proof-of-service.pdf", documentType: "proof_of_service", selected: true },
];

export default function App() {
  const [notice, setNotice] = useState("Review the snapshot and payer packet.");
  return <main className="review-demo">
    <p className="notice">{notice}</p>
    <BillSubmissionForm
      initialBill={initialBill}
      attachments={attachments}
      appearance={{ preset: "clinical-blue" }}
      submitLabel="Submit bill"
      onSubmit={async ({ sourceAttachmentIds, uploads }) => {
        setNotice(
          "Ready to submit " + (sourceAttachmentIds.length + uploads.length) + " documents atomically."
        );
      }}
    />
  </main>;
}
Patient and injury

Responsive two-column fields, paste-friendly MM/DD/YYYY dates, required asterisks, and authenticated ZIP-to-city/state completion.

Diagnosis and routing

Complete server-backed ICD-10 search with common-injury quick picks and removable chips, plus canonical claims-administrator search backed by MindBill payer routing IDs.

Service lines

Searchable workers'-comp procedure and modifier controls, evaluation-mode modifier defaults, medical-legal fee-schedule amounts, totals, valid manual CPT/HCPCS entry, and one automatically maintained empty line.

Attachments

Removable source documents, a locked auto-attached practice W-9, and click, panel-drop, or whole-page PDF upload.

Individual form sections

Use the named section exports when your product needs to place form sections in its own page shell. BillSubmissionForm remains the single state, validation, lookup, upload, and submission engine; its children only control composition and order.

CustomBillSubmission.tsx
import {
  BillSubmissionActions,
  BillSubmissionAttachmentsSection,
  BillSubmissionClaimSection,
  BillSubmissionForm,
  BillSubmissionHeader,
  BillSubmissionPatientSection,
  BillSubmissionProvidersSection,
  BillSubmissionServiceLinesSection,
} from "@mindbill/react";

<BillSubmissionForm
  initialBill={bill}
  attachments={documents}
  sessionEndpoint="/api/mindbill/submission-session"
  onSubmitted={({ billId }) => rememberBillId(billId)}
>
  <BillSubmissionHeader />
  <BillSubmissionPatientSection />
  <BillSubmissionClaimSection />
  <BillSubmissionProvidersSection />
  <BillSubmissionServiceLinesSection />
  <BillSubmissionAttachmentsSection />
  <BillSubmissionActions />
</BillSubmissionForm>
Composable submission sectionsSynthetic data · edit and run
import {
  BillSubmissionActions,
  BillSubmissionAttachmentsSection,
  BillSubmissionClaimSection,
  BillSubmissionForm,
  BillSubmissionHeader,
  BillSubmissionPatientSection,
  BillSubmissionProvidersSection,
  BillSubmissionServiceLinesSection,
} from "@mindbill/react";

const initialBill = {
  externalId: "report_9f7a",
  billingMode: "med_legal",
  patient: {
    externalId: "patient_42", firstName: "Alex", lastName: "Morgan",
    dateOfBirth: "1988-03-05",
    address: { line1: "100 Main St", city: "Fresno", state: "CA", postalCode: "93721" },
  },
  claim: {
    externalId: "claim_17", claimNumber: "WC-44871",
    employer: "Northstar Foods", dateOfInjury: "2025-05-30", injuryState: "CA",
    claimsAdministrator: { name: "Republic Indemnity" },
  },
  service: { date: "2026-08-26" },
  billingProvider: {
    name: "Northstar Evaluations", taxId: "123456789", npi: "1023401122",
    address: { line1: "236 W Mountain St", city: "Pasadena", state: "CA", postalCode: "91103" },
  },
  renderingProvider: {
    name: "Morgan Chen, MD", npi: "1098765432",
    licenseNumber: "A140748", licenseState: "CA",
  },
  serviceLocation: {
    name: "West Covina Exam Office", placeOfServiceCode: "11",
    address: { line1: "1050 West Lakes Dr", city: "West Covina", state: "CA", postalCode: "91790" },
  },
  diagnoses: ["M25.562"],
  serviceLines: [{ code: "ML201", modifiers: ["95"], units: 1, charge: 2015 }],
};

const attachments = [
  { id: "doc_report", fileName: "final-report.pdf", documentType: "final_report", selected: true },
  { id: "doc_pos", fileName: "proof-of-service.pdf", documentType: "proof_of_service", selected: true },
];



export default function App() {
  return <main className="review-demo">
    <BillSubmissionForm
      initialBill={initialBill}
      attachments={attachments}
      appearance={{ preset: "orange-bright" }}
      onSubmit={async (value) => console.log("submit", value)}
    >
      <BillSubmissionHeader />
      <BillSubmissionPatientSection />
      <BillSubmissionClaimSection />
      <BillSubmissionProvidersSection />
      <BillSubmissionServiceLinesSection />
      <BillSubmissionAttachmentsSection />
      <BillSubmissionActions />
    </BillSubmissionForm>
  </main>;
}

Dashboard, aging, bill list, and reporting

ConnectedBillingWorkspace is the default partner integration. It owns fetching, filters, drill-down navigation, selected views, loading and error states, and per-bill lifecycle actions. Use an organization-wide session with bills:create, bills:read, bills:act, documents:read, payers:read, and eors:read. A create-only submission session cannot load the workspace.

Billing.tsx
import { ConnectedBillingWorkspace } from "@mindbill/react";

<ConnectedBillingWorkspace
  sessionEndpoint="/api/mindbill/session"
  appearance={{ preset: "calm-clinical" }}
  onCreateBill={() => navigate("/billing/new")}
  style={{ height: "calc(100dvh - 96px)", minHeight: 0 }}
/>

From React 0.64.0, both ConnectedBillingWorkspace and BillingDashboard include a Settings tab by default. Set showSettings={false} to hide it. Set billingSettings={{ sessionEndpoint: "/api/mindbill/settings-session" }} for a dedicated administrator session; otherwise the workspace reuses its main connection and BillingDashboard uses /api/mindbill/session. The server requires organization:manage and the component does not expand permissions. Use onSettingsSaved(profile) to refresh host state after a save; the callback receives OrganizationProfileData. The workspace also accepts initialView="settings". See the settings integration example.

React 0.50.0 includes a Payment review tab for confirmed cash, with received-date filters, search, totals, bill drill-down, and page export. Set initialView="payments" or use ConnectedPaymentReview independently. See payment-review data and access rules. The workspace manages its own bill-detail navigation.

Use ConnectedBillSearch independently when your product already has its own navigation. It searches bill identifiers, patient and administrator names, statuses, procedure codes, and dates, and combines that search with status, billing-provider, claims-administrator, A/R-age, and date filters.

AllBills.tsx
import { ConnectedBillSearch } from "@mindbill/react";

<ConnectedBillSearch
  sessionEndpoint="/api/mindbill/session"
  initialQuery={{
    q: "Example 99213",
    dateField: "service",
    from: "2026-09-01",
    to: "2026-09-30",
    status: "submitted",
  }}
  onSelectBill={(bill) => openBill(bill.id)}
/>

The connected dashboard and reports use the same thin-client session contract. Counts and drill-downs come from MindBill's authoritative server-side queries, while CSV and PDF controls remain in the component surface.

BillingReports.tsx
import {
  ConnectedBillTasksDashboard,
  ConnectedProductivityReport,
  ConnectedServiceLineItemsReport,
} from "@mindbill/react";

<ConnectedBillTasksDashboard sessionEndpoint="/api/mindbill/session" />
<ConnectedServiceLineItemsReport sessionEndpoint="/api/mindbill/session" />
<ConnectedProductivityReport sessionEndpoint="/api/mindbill/session" />

The operations exports accept the same normalized bill summaries. They calculate receivables and aging in the browser, render responsive tables or mobile cards, and keep navigation under your application's control.

BillingDashboard.tsx
import { BillingDashboard } from "@mindbill/react";

<BillingDashboard
  bills={bills}
  billingSettings={{ sessionEndpoint: "/api/mindbill/settings-session" }}
  heading="Billing operations"
  description="Search every bill and act on aging balances."
  onSelectBill={(bill) => navigate(`/billing/${bill.id}`)}
  appearance={{ preset: "orange-bright" }}
/>
BillingDashboardSynthetic receivables · search and filter
import { BillingDashboard } from "@mindbill/react";

const bills = [
  { id: "bill_1042", billNumber: 1042, patientName: "Jordan Lee", claimNumber: "WC-78142", payerName: "Republic Indemnity", state: "processed", submittedAt: "2026-05-14", totalCharge: 2015, totalPaid: 650, balanceDue: 1365 },
  { id: "bill_1041", billNumber: 1041, patientName: "Morgan Cruz", claimNumber: "WC-77908", payerName: "State Compensation Insurance Fund", state: "accepted", submittedAt: "2026-06-29", totalCharge: 2015, totalPaid: 0, balanceDue: 2015 },
  { id: "bill_1039", billNumber: 1039, patientName: "Taylor Kim", claimNumber: "WC-76881", payerName: "Sedgwick", state: "submitted", submittedAt: "2026-08-19", totalCharge: 1300, totalPaid: 0, balanceDue: 1300 },
  { id: "bill_1036", billNumber: 1036, patientName: "Alex Morgan", claimNumber: "WC-75117", payerName: "Gallagher Bassett", state: "closed", submittedAt: "2026-04-02", totalCharge: 2015, totalPaid: 2015, balanceDue: 0 },
];

export default function App() {
  return <main className="operations-demo">
    <BillingDashboard
      bills={bills}
      showSettings={false} // This synthetic demo has no authenticated settings session.
      heading="Billing operations"
      description="Search every bill and act on aging balances."
      appearance={{ preset: "orange-bright" }}
      onSelectBill={(bill) => alert("Open " + bill.id)}
    />
  </main>;
}

BillingReport groups the same data by payer, lifecycle status, or aging bucket. buildBillingReportCsv returns a ready-to-download or copyable CSV without requiring a second reporting schema.

BillingReport.tsx
import {
  BillingReport,
  buildBillingReportCsv,
} from "@mindbill/react";

<BillingReport bills={bills} groupBy="payer" />;

const csv = buildBillingReportCsv(bills, "payer");
BillingReportSynthetic reporting · group and export
import { useState } from "react";
import { BillingReport, buildBillingReportCsv } from "@mindbill/react";

const bills = [
  { id: "bill_1042", patientName: "Jordan Lee", payerName: "Republic Indemnity", state: "processed", submittedAt: "2026-05-14", totalCharge: 2015, totalPaid: 650, balanceDue: 1365 },
  { id: "bill_1041", patientName: "Morgan Cruz", payerName: "State Compensation Insurance Fund", state: "accepted", submittedAt: "2026-06-29", totalCharge: 2015, totalPaid: 0, balanceDue: 2015 },
  { id: "bill_1039", patientName: "Taylor Kim", payerName: "Sedgwick", state: "submitted", submittedAt: "2026-08-19", totalCharge: 1300, totalPaid: 0, balanceDue: 1300 },
];

export default function App() {
  const [groupBy, setGroupBy] = useState("payer");
  return <main className="operations-demo">
    <nav className="report-controls">
      <select value={groupBy} onChange={(event) => setGroupBy(event.target.value)}>
        <option value="payer">Payer</option><option value="status">Status</option><option value="aging">Aging</option>
      </select>
      <button onClick={() => navigator.clipboard.writeText(buildBillingReportCsv(bills, groupBy))}>Copy CSV</button>
    </nav>
    <BillingReport bills={bills} groupBy={groupBy} appearance={{ preset: "orange-bright" }} />
  </main>;
}

BillStatusAgingMatrix renders the management view billing teams expect from legacy tools: one row per lifecycle status, one column per 0–30 / 31–60 / 61–90 / 91+ aging bucket, clickable counts with outstanding balances, and row, column, and grand totals. Every onSelectCell payload carries { state, bucket, count, balance, bills } — the exact bills behind the count — so a drill-down never needs a second query. Pin your lifecycle ordering with stateOrder; buildBillStatusAgingMatrix and buildBillStatusAgingCsv expose the same aggregation presentation-free.

BillStatusAgingMatrix.tsx
import { BillStatusAgingMatrix } from "@mindbill/react";

<BillStatusAgingMatrix
  bills={bills}
  appearance={{ preset: "clinical-blue" }}
  onSelectCell={(cell) => setDrillDown(cell)}
/>

{drillDown ? <BillList bills={drillDown.bills} onSelectBill={openBill} /> : null}

Search and date filters

React 0.67.0 adds patient, rendering-provider, and claims-administrator selectors backed by the complete authorized filter inventory, independent of the current page. Use canonical IDs: patientId, renderingProviderId, and claimsAdministrator in browser registry queries; the last serializes to claimsAdminId on the API. Filters never expand record access.

The connected bill registry searches patient and claims-administrator names, bill and claim identifiers, external IDs, statuses, procedure codes, and dates. Every search word must match; matching ignores case. Enter text and an optional service-date or submission-date range, then press Search or Enter. Status, age, patient, rendering-provider, and claims-administrator filters apply immediately. Clear resets all filters.

For host-owned BillingDashboard rows, filtering happens immediately in the supplied array. Include dateOfService and procedureCodes for those searches. Connected queries use q, dateField (service or submitted), and inclusive from/to dates. See the dashboard query reference.

Organization onboarding

OrganizationOnboarding starts with the pay-to billing provider, then rendering providers, locations, and W-9. Organization details appear in a separate disclosure and remain required when requested by the onboarding checklist. The wizard saves through an organization-wide organization:manage session, and onCompleted fires when billing setup is complete.

React 0.65.0 adds three sections to BillingSettings: Billing profiles, Claims administrators, and Team. Each administrative list loads when its section is selected. Team requires the separate team:manage permission and manages existing MindBill login accounts only; it does not invite users or change host-app roles. Physician signatures are configured in MindBill rendering-provider settings. See the settings permission guide.

From React 0.47.0, settings accept EIN or SSN with an explicit tax ID type. Saved SSNs are encrypted and masked in responses. A blank saved SSN field preserves it, a replacement changes it, and the clear button requests removal on save. Use organizationProfileOptions(profile) for SSN-backed saved billing choices so submission sends a provider reference rather than a masked identifier. See the saved-profile details below.

BillingSetup.tsx
import { OrganizationOnboarding } from "@mindbill/react";

<OrganizationOnboarding
  sessionEndpoint="/api/mindbill/settings-session"
  appearance={{ preset: "clinical-blue" }}
  onCompleted={() => enableBillingFeatures()}
/>

If your app already stores and extracts W-9s, React 0.50.0's W9Upload renders the upload, current document, extraction progress, retry, and review states using your host callbacks. It does not create another document store or parser. See choose one W-9 storage owner.

Saved profiles, provider references, and tax ID handling

If your app already stores billing provider, rendering provider, service locations, and W-9 documents, keep that ownership and prefill from it. Otherwise offer MindBill's settings components to avoid building and maintaining duplicate input screens. See the complete billing-settings guide for setup and permissions.

Use a separate admin-authorized session endpoint for organization:manage. Do not grant it to every billing user. Avoid persisting duplicate tax identifiers; keep sensitive values out of browser storage, logs, analytics, screenshots, and coding-agent prompts.

For saved choices during bill creation, GET /partner/v2/organization/billing-profile accepts an organization-wide browser session with bills:create and returns a masked organization profile. Bill-scoped sessions cannot perform this lookup. Settings writes still require organization:manage; reading choices does not grant permission to edit them.

From React 0.62.0, connected forms automatically load saved choices when profileOptions is omitted. Pass a separate billingSettings session configuration to enable the prebuilt Add/edit settings flow for administrators. Explicit profileOptions override the lookup; an empty object suppresses it.

Automatic saved choices and administrator settings — React 0.62.0
// Requires @mindbill/react >=0.62.0.
"use client";
import { BillSubmissionForm, type BillSubmissionFormProps,
  type BillSubmissionInput } from "@mindbill/react";

export function ProfileBasedBill({ initialBill, canManageBilling }: {
  initialBill: BillSubmissionInput; canManageBilling: boolean;
}) {
  return <BillSubmissionForm initialBill={initialBill}
    sessionEndpoint="/api/mindbill/session"
    profileDisplay="compact"
    {...(canManageBilling ? { billingSettings: {
      sessionEndpoint: "/api/mindbill/settings-session"
    } } : {})} />;
}
// The connected form reads saved choices automatically when profileOptions is omitted.
// The billing endpoint needs organization-wide bills:create, not organization:manage.
// billingSettings enables the prebuilt Add/edit settings flow. Its SEPARATE endpoint
// must enforce the administrator role server-side and grant organization:manage.
// Hiding a button is not an authorization check. Omit billingSettings for other users.
// Existing case values remain unchanged until the user explicitly selects a profile.

// Host-managed alternative: explicit choices override automatic organization lookup.
export function HostProfileBill({ initialBill, savedBillingProvider,
  savedRenderingProvider, savedServiceLocation }: {
    initialBill: BillSubmissionFormProps["initialBill"];
    savedBillingProvider: NonNullable<BillSubmissionInput["billingProvider"]>;
    savedRenderingProvider: NonNullable<BillSubmissionInput["renderingProvider"]>;
    savedServiceLocation: NonNullable<BillSubmissionInput["serviceLocation"]>;
  }) {
  const profileOptions = {
    billingProviders: [{ id: "practice-1", label: "Main practice", value: savedBillingProvider }],
    renderingProviders: [{ id: "doctor-1", label: "Rendering doctor", value: savedRenderingProvider }],
    serviceLocations: [{ id: "office-1", label: "Main office", value: savedServiceLocation }],
  };
  return <BillSubmissionForm initialBill={initialBill} profileOptions={profileOptions}
    profileDisplay="compact" sessionEndpoint="/api/mindbill/session" />;
}
// profileOptions={} also suppresses the organization-wide lookup when it is unwanted.
// Bill-scoped sessions cannot read organization-wide choices. Do not widen access.
// Omit profileDisplay (or use "expanded") to show all fields.
// Selecting a profile does not mutate the saved profile.
// SSN-backed choices retain savedProviderId; the server resolves the identifier.
// Never put taxIdLast4 in taxId, browser storage, telemetry or agent prompts.
// Settings supports taxIdType: "EIN" | "SSN" (default EIN).
// Saved SSNs are encrypted. Responses contain empty taxId, taxIdLast4 and
// taxIdConfigured. In settings: blank preserves, replacement changes, clear removes.
// Custom settings API writes: omit taxId to preserve; taxId: "" explicitly clears.
// Do not send response-only taxIdLast4/taxIdConfigured in a settings write.

profileDisplay="compact" presents saved choices first; "expanded" keeps all fields visible. Host-managed options need no extra storage or organization lookup. Settings support taxIdType: "EIN" | "SSN"; EIN remains the default. Select SSN explicitly rather than putting it in an EIN field.

Saved SSNs are encrypted and returned as an empty taxId with taxIdLast4 and taxIdConfigured, not as plaintext. In settings, leaving a saved SSN input blank preserves it; entering a replacement changes it; the explicit clear action removes it on save. The components handle these write semantics. Custom API forms should omit taxId to preserve it and send taxId: "" to clear it; do not send read-only masking metadata back in a settings write.

organizationProfileOptions(profile) creates a server-resolved provider reference for an SSN-backed profile. Do not copy the last four digits into a bill's tax ID. The browser/API create contract accepts billingProvider: { savedProviderId }. Corrections and duplicates can use billingProvider: { sourceBillId } to reuse that bill's immutable provider snapshot. Both references are resolved within the authenticated organization. Existing TypeScript code should narrow reference versus inline-provider values before reading fields such as name or taxId.

Treatment and authorization requests

Use billingMode="professional" in the initial bill for treatment charges and per-line diagnosis selection. RfaDashboard provides authorization tracking, draft creation and revision-aware editing, clinical PDF upload, authorized signing, packet and cover-sheet review, directory destination selection, explicit fax sending, delivery evidence, receipt and information-request recording, item decisions, and a task board for no-response follow-up, matching incoming response faxes, and posting utilization review decisions. Supporting PDFs can be selected in the creation form. Use RfaTaskBoard to embed the work queue separately. Configure its role-matched permissions, human signer identity, and environment; it defaults to read-only sandbox. See the treatment quickstart and RFA integration guide.

Notification settings

Administrative recipient list (React 0.52.0): NotificationRecipientsSettings (alias ConnectedNotificationRecipientsSettings) lets an administrator invite any authorized email address, including someone without a console account. Choose practice-wide or explicitly assigned-bill access, status/payment alerts, aging reminders, quiet hours and daily/weekly billing-activity digests. Everything stays off until the email owner reviews and confirms the invitation; the list includes pending states and a disable action.

components/BillingRecipients.tsx
"use client";
import { useMemo } from "react";
import {
  NotificationRecipientsSettings,
  type NotificationRecipientsAdapter,
} from "@mindbill/react";

export function BillingRecipients({ identityKey, csrfToken }: {
  identityKey: string; // changes for admin, practice OR sandbox/live environment
  csrfToken: string;
}) {
  const adapter = useMemo<NotificationRecipientsAdapter>(() => {
    async function request(method: string, suffix = "", body?: unknown) {
      // YOUR authenticated admin route; not a MindBill browser-session endpoint.
      const response = await fetch("/api/billing/notification-recipients" + suffix, {
        method, credentials: "same-origin", cache: "no-store",
        headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken },
        ...(body === undefined ? {} : { body: JSON.stringify(body) }),
      });
      if (!response.ok) throw new Error("Notification request failed");
      return response.json(); // host returns API data, not the { data } envelope
    }
    return {
      load: offset => request("GET", "?offset=" + offset),
      invite: input => request("POST", "", input), // preserve input.requestId
      disable: async externalUserId => {
        await request("DELETE", "/" + encodeURIComponent(externalUserId));
      },
    };
  }, [identityKey, csrfToken]);
  return <NotificationRecipientsSettings identityKey={identityKey} adapter={adapter}
    appearance={{ preset: "mindbill" }} />;
}

Connect load, invite and disable to your authenticated host administrator route, return upstream data rather than its envelope, and preserve invitation request IDs on unchanged retries. Forward reportDigest (off, daily or weekly) through your server adapter. Keep API keys and bill-assignment authority on the server. See the invitation API, receipt handling and required host checks. Digests contain activity counts, not financial reports or patient attachments; sandbox sends no email.

Personal preferences

NotificationSettings (alias ConnectedNotificationSettings) adds a default-off email preferences panel to your existing settings page. It supports explicit consent, status updates, 30/60/90-day aging reminders, quiet hours, daily/weekly billing-activity digests, and unsubscribe for any partner's users, including people without a console account. Return reportDigest in your adapter's preference snapshot. New or changed digest schedules require fresh consent.

components/BillingNotificationSettings.tsx
"use client";
import { useMemo } from "react";
import {
  NotificationSettings,
  type NotificationSettingsAdapter,
  type NotificationSettingsSnapshot,
} from "@mindbill/react";

export function BillingNotificationSettings({ identityKey, csrfToken }: {
  identityKey: string; // Change when signed-in user, practice, or environment changes.
  csrfToken: string;   // Issued by your existing host CSRF protection.
}) {
  const adapter = useMemo<NotificationSettingsAdapter>(() => {
    const endpoint = "/api/mindbill/notification-settings";
    let pending: { payload: string; id: string } | null = null;
    async function request(method: string, body?: unknown, requestId?: string) {
      const response = await fetch(endpoint, {
        method, credentials: "same-origin", cache: "no-store",
        headers: {
          "Content-Type": "application/json",
          ...(method === "GET" ? {} : { "X-CSRF-Token": csrfToken }),
          ...(requestId ? { "X-Notification-Request-Id": requestId } : {}),
        },
        ...(body === undefined ? {} : { body: JSON.stringify(body) }),
      });
      if (!response.ok) {
        if (response.status === 409) pending = null; // Requires a NEW checkbox confirmation.
        throw new Error("Could not confirm notification settings");
      }
      return response;
    }
    async function load(): Promise<NotificationSettingsSnapshot> {
      return (await request("GET")).json();
    }
    return {
      load,
      save: async update => {
        const payload = JSON.stringify(update);
        if (pending?.payload !== payload) pending = { payload, id: crypto.randomUUID() };
        await request(update.enabled ? "PUT" : "DELETE",
          update.enabled ? update : undefined, pending.id);
        const snapshot = await load(); // PUT is not a complete settings response.
        pending = null;
        return snapshot;
      },
      unsubscribe: async () => { pending = null; await request("DELETE"); return load(); },
    };
  }, [identityKey, csrfToken]);

  return <NotificationSettings identityKey={identityKey} adapter={adapter}
    appearance={{ preset: "calm-clinical" }} />;
}

Your adapter calls an authenticated host-server route and reloads GET after each PUT or DELETE. The server owns identity, verified email, practice/assigned-bill access, consent records, and the permanent API key. Change identityKey when the host user, practice, or environment changes. Use appearance, className, and style for theming. See the copyable server adapter and required host checks before wiring this up.

Post-submission setup

Once a bill exists, add one authenticated server route that exchanges your signed-in user for a short-lived, organization-scoped browser session restricted to that submitted bill.

Bill-scoped session permissions

This excerpt assumes existing authentication, bill-access checks, and the correct organization credential. Apply the server route protections before issuing sessions.

server/bill-session.ts
app.post("/api/mindbill/bills/:billId/session", async (req, res) => {
  const user = await requireSignedInUser(req);
  const { billId } = req.params;
  await requireBillAccess(user, billId);

  res.json(await mindbill.createBrowserSession({
    subject: user.id,
    permissions: ["bills:read", "bills:act", "documents:read", "eors:read"],
    resource: { billId },
    allowedOrigin: process.env.APP_ORIGIN!,
    expiresIn: 900,
  }));
});

Complete post-submission lifecycle

Pass the returned billId. ConnectedBillLifecycle never creates or edits a pre-submission draft.

SubmittedBill.tsx
import { ConnectedBillLifecycle } from "@mindbill/react";

export function SubmittedBill({ billId }: { billId: string }) {
  return (
    <ConnectedBillLifecycle
      billId={billId}
      sessionEndpoint={`/api/mindbill/bills/${billId}/session`}
      appearance={{ preset: "orange-bright" }}
    />
  );
}

The component includes lifecycle progress, the frozen bill snapshot, a consolidated EOR and payment reconciliation surface, rich claims-administrator directory details, history, packet preview, and a sticky state-aware action bar for Second Review, correction, IBR, lien, payment, or closure when eligible.

Shared bill detail sections and related records

React 0.66.0 exports BillDetailLayout and BillDetailSection, also used by BillReadOnlyForm. A section accepts a title, description, actions, and validationIssues containing severity: "error" | "warning" and a message. Errors appear in red and warnings in amber, with accessible text lists. Supply your authoritative validation results; these visual components do not validate or submit a bill.

PatientDetails.tsx
import { BillDetailLayout, BillDetailSection } from "@mindbill/react";

export function PatientDetails() {
  return <BillDetailLayout header={<h2>Bill details</h2>}>
    <BillDetailSection title="Patient" validationIssues={[
      { severity: "error", message: "Enter the patient's date of birth." },
      { severity: "warning", message: "Review the mailing address." },
    ]}>
      <p>Your patient fields or read-only details</p>
    </BillDetailSection>
  </BillDetailLayout>;
}

From React 0.67.0, ConnectedBillLifecycle derives client validation and rejection field issues for editable current bills in incomplete, draft, or rejected states. An explicit validationIssues value overrides that default. Other states do not gain draft validation errors.

The layout accepts optional header, actions, and sidebar slots. Sections accept headerClassName, bodyClassName, className, and style for host styling. On BillReadOnlyForm, group validationIssues by patient, claim, providers, services, or attachments.

Pass onPatientClick(patient), onRenderingProviderClick(provider), and onClaimsAdministratorClick(administrator) to BillReadOnlyForm or ConnectedBillLifecycle to make those names open your own related-record view. IDs are optional on historical snapshots: check for a canonical ID before applying a filter, and do not infer identity from a name. On these standalone detail components, names remain plain text without callbacks. In React 0.67.0, ConnectedBillingWorkspace supplies default callbacks that open All Bills filtered by the canonical entity ID and reset prior filters and pagination. Override the corresponding workspace callback to use your host navigation. Historical entities without IDs remain plain text; names are never used as guessed IDs. These callbacks do not change the bill's saved data or grant access to another record.

From React 0.48.0, selecting an Original Bill or another submission opens that submission's Bill details, not the history tab. Historical details are read-only: current-bill actions and balances are not presented as historical facts. Older submissions without a stored snapshot show an explicit availability warning instead of substituting the current bill.

The current bill also includes built-in team notes with author and time across submission attempts and Forward copy. Forwarding previews one combined PDF, then requires confirmation before emailing it; it does not resubmit the bill or change its status. Sandbox forwarding is simulated and sends no email. See communications and permissions.

Pass your own case contacts as courtesyCopyRecipientOptions to offer named To/CC choices alongside manual email entry. For a workspace, use getCourtesyCopyRecipientOptions(billId) to keep suggestions specific to the selected bill. The standalone BillCourtesyCopyForm accepts recipientOptions; each CourtesyCopyRecipientOption has an email and optional name.

components/bill-email-options.tsx
// Suggested contacts from your own case record, not automatic recipients.
<ConnectedBillLifecycle
  billId={billId}
  sessionEndpoint={`/api/mindbill/bills/${billId}/session`}
  courtesyCopyRecipientOptions={caseContacts.map((contact) => ({
    email: contact.email,
    name: contact.name,
  }))}
/>

// A workspace can resolve a different contact list for each bill.
<ConnectedBillingWorkspace
  sessionEndpoint="/api/mindbill/session"
  getCourtesyCopyRecipientOptions={(billId) => contactsByBillId[billId] ?? []}
/>

The Second Review dialog supports per-line corrections to units, modifiers, and the explicitly reviewed charge. It does not guess a new charge when units or modifiers change. These corrections apply to the new review submission, preserving the original bill's history.

ConnectedBillLifecycleSynthetic data · edit and run
import {
  BillActivityTimeline,
  BillExplanationOfReview,
  BillLifecycleActions,
  BillLifecycleProgress,
  BillSnapshotSummary,
} from "@mindbill/react";

const review = {
  bill: {
    id: "bill_demo_1038", billNumber: 1038, status: "Submitted",
    billingMode: "med_legal", dos: "2026-08-26",
    billingSnapshot: {
      billingProvider: { name: "Northstar Evaluations", taxId: "12-3456789", npi: "1023401122", billType: "Professional", phone: "626-555-0194", billingStreet: "236 W Mountain St", billingCity: "Pasadena", billingState: "CA", billingZip: "91103" },
      renderingProvider: { name: "Dr. Morgan Chen", specialty: "Psychiatry", npi: "1023401122", taxonomy: "2084P0800X", licenseNumber: "A140748", licenseState: "CA", isQME: true },
      placeOfService: { name: "West Covina Exam Office", street: "1050 West Lakes Dr, Ste 225", city: "West Covina", state: "CA", zip: "91790", posCode: "11" },
    },
    lineItems: [{ id: "line_1", code: "ML201", modifiers: ["-95"], units: 1, charge: 2015, serviceDate: "2026-08-26" }],
    attachments: [
      { id: "doc_report", filename: "final-report.pdf", documentType: "final_report", description: "Final medical-legal report" },
      { id: "doc_pos", filename: "proof-of-service.pdf", documentType: "proof_of_service", description: "Proof of service" },
    ],
    totalCharge: 2015, totalPaid: 0, balanceDue: 2015,
  },
  patient: { name: "Alex Morgan", firstName: "Alex", lastName: "Morgan", dob: "1988-03-05" },
  injury: { claimNumber: "W8331838-0001", employer: "Northstar Foods", doi: "2025-05-30", adjNumber: "ADJ21439594", claimsAdminId: "payer_republic", claimsAdminName: "Republic Indemnity", claimPatternStatus: { state: "match", label: "Claim pattern matches Republic Indemnity" } },
};

const lifecycle = {
  ...review,
  bill: { ...review.bill, status: "Denied" },
  lifecycle: {
    state: "denied", nativeStatus: "Denied", submittedAt: "2026-07-27",
    agingDays: 29, updatedAt: "2026-08-25",
    actions: [
      { id: "view_eor", label: "View EOR", enabled: true },
      { id: "second_review", label: "Submit second review", enabled: true, primary: true },
      { id: "close", label: "Close bill", enabled: true },
    ],
  },
  eors: [{ id: "eor_1", filename: "EOR-1038.pdf", description: "Explanation of Review", addedAt: "2026-08-25", contentUrl: "#eor" }],
  activity: [
    { id: "evt_3", type: "bill.denied", createdAt: "2026-08-25T17:42:18Z", description: "Medical necessity or frequency" },
    { id: "evt_2", type: "eor.received", createdAt: "2026-08-25T17:41:02Z", description: "EOR-1038.pdf received" },
    { id: "evt_1", type: "bill.submitted", createdAt: "2026-07-27T16:08:00Z", actor: "Taylor R." },
  ],
  payments: [],
  remittance: {
    billedAmount: 2015, expectedAmount: 2015, payerAllowedAmount: 0,
    payerReportedPaid: 0, postedPrincipal: 0, postedAdditional: 0,
    totalPostedCash: 0, balanceDue: 2015,
    denialReason: "Medical necessity or frequency",
  },
  delivery: {
    payerName: "Republic Indemnity",
    contacts: {
      adjusterName: "Jordan Lee", adjusterPhone: "(213) 555-0188",
      adjusterEmail: "jordan.lee@example.test", faxNumber: "(213) 555-0199",
      mailingAddress: "PO Box 19600, Irvine, CA 92623",
    },
  },
};

export default function App() {
  return <main className="review-demo">
    <BillLifecycleProgress {...lifecycle.lifecycle} appearance={{ preset: "orange-bright" }} />
    <BillSnapshotSummary {...lifecycle} appearance={{ preset: "orange-bright" }} />
    <BillExplanationOfReview
      remittance={lifecycle.remittance}
      eors={lifecycle.eors}
      payments={lifecycle.payments}
      submittedAt={lifecycle.lifecycle.submittedAt}
      onOpenEor={(eor) => alert("Preview " + eor.filename)}
      appearance={{ preset: "orange-bright" }}
    />
    <BillLifecycleActions actions={lifecycle.lifecycle.actions} onAction={() => {}} appearance={{ preset: "orange-bright" }} />
    <BillActivityTimeline events={lifecycle.activity} appearance={{ preset: "orange-bright" }} />
  </main>;
}

Custom lifecycle UI

useBillLifecycle exposes the same authoritative submitted state and post-submission actions.

BillingToolbar.tsx
import { useBillLifecycle } from "@mindbill/react";

function BillingToolbar({ billId }: { billId: string }) {
  const bill = useBillLifecycle({
    billId,
    sessionEndpoint: `/api/mindbill/bills/${billId}/session`,
  });

  if (bill.isLoading) return <p>Loading…</p>;
  if (bill.error) return <button onClick={bill.refresh}>Try again</button>;

  return (
    <>
      <strong>{bill.data?.lifecycle.state}</strong>
      <button onClick={() => bill.closeBill({ reason: "Completed" })}>
        Close bill
      </button>
    </>
  );
}

Status surfaces

Open the full lifecycle demo to inspect every state or walk one synthetic bill from Sent through payment and closure.

ConnectedBillStatus loads and refreshes authoritative status. useBillStatus exposes the same client state for a custom layout.

BillStatus.tsx
import { ConnectedBillStatus } from "@mindbill/react";

<ConnectedBillStatus
  billId={billId}
  sessionEndpoint={`/api/mindbill/bills/${billId}/session`}
  refreshInterval={30_000}
/>
ConnectedBillStatusSynthetic data · edit and run
import { useState } from "react";
import { ConnectedBillStatus } from "@mindbill/react";

const status = {
  billId: "bill_demo_1038", state: "denied", nativeStatus: "Denied",
  submittedAt: "2026-07-27", agingDays: 29, updatedAt: "2026-08-25",
  totalCharge: 2015, totalPaid: 0, balanceDue: 2015,
};

export default function App() {
  const [message, setMessage] = useState("");
  return <main className="demo">
    <ConnectedBillStatus
      billId={status.billId} enabled={false} initialData={status}
      appearance={{ preset: "orange-bright" }}
      actions={[
        { id: "denial", label: "View denial", onClick: () => setMessage("Denial opened") },
        { id: "review", label: "Second review", primary: true, onClick: () => setMessage("Second review opened") },
      ]}
    />
    {message && <p>{message}</p>}
  </main>;
}

BillStatusSummary is the data-only version when your app already has the status.

StatusCard.tsx
import { BillStatusSummary } from "@mindbill/react";

<BillStatusSummary
  status="processed"
  submittedAt="2026-08-12T17:00:00Z"
  agingDays={13}
  totalCharge={2015}
  totalPaid={0}
  balanceDue={2015}
  actions={[{ id: "eor", label: "View EOR", onClick: openEor }]}
/>
BillStatusSummarySynthetic data · edit and run
import { useState } from "react";
import { BillStatusSummary } from "@mindbill/react";

export default function App() {
  const [message, setMessage] = useState("");
  return <main className="demo">
    <BillStatusSummary
      status="processed" submittedAt="2026-08-12" agingDays={13}
      updatedAt="2026-08-25" totalCharge={2015} totalPaid={0}
      balanceDue={2015} appearance={{ preset: "mindbill" }}
      actions={[
        { id: "eor", label: "View EOR", onClick: () => setMessage("Opening EOR") },
        { id: "payment", label: "Post payment", primary: true, onClick: () => setMessage("Payment opened") },
      ]}
    />
    {message && <p>{message}</p>}
  </main>;
}

Lifecycle surfaces

These presentational exports use fields already returned by the lifecycle endpoint. Compose them only when the complete connected workspace is more UI than you need.

BillProgress.tsx
import { BillLifecycleProgress } from "@mindbill/react";

<BillLifecycleProgress
  state={bill.lifecycle.state}
  nativeStatus={bill.lifecycle.nativeStatus}
  submittedAt={bill.lifecycle.submittedAt}
  agingDays={bill.lifecycle.agingDays}
/>
BillLifecycleProgressSynthetic data · edit and run
import { BillLifecycleProgress } from "@mindbill/react";

export default function App() {
  return <main className="demo">
    <BillLifecycleProgress
      state="second_review"
      nativeStatus="Second Bill Review submitted"
      submittedAt="2026-07-27T16:08:00Z"
      agingDays={29}
      appearance={{ preset: "orange-bright" }}
    />
  </main>;
}
BillSnapshot.tsx
import { BillSnapshotSummary } from "@mindbill/react";

<BillSnapshotSummary
  bill={bill.bill}
  patient={bill.patient}
  injury={bill.injury}
  delivery={bill.delivery}
/>
BillSnapshotSummarySynthetic data · edit and run
import { BillSnapshotSummary } from "@mindbill/react";

const review = {
  bill: {
    id: "bill_demo_1038", billNumber: 1038, status: "Submitted",
    billingMode: "med_legal", dos: "2026-08-26",
    billingSnapshot: {
      billingProvider: { name: "Northstar Evaluations", taxId: "12-3456789", npi: "1023401122", billType: "Professional", phone: "626-555-0194", billingStreet: "236 W Mountain St", billingCity: "Pasadena", billingState: "CA", billingZip: "91103" },
      renderingProvider: { name: "Dr. Morgan Chen", specialty: "Psychiatry", npi: "1023401122", taxonomy: "2084P0800X", licenseNumber: "A140748", licenseState: "CA", isQME: true },
      placeOfService: { name: "West Covina Exam Office", street: "1050 West Lakes Dr, Ste 225", city: "West Covina", state: "CA", zip: "91790", posCode: "11" },
    },
    lineItems: [{ id: "line_1", code: "ML201", modifiers: ["-95"], units: 1, charge: 2015, serviceDate: "2026-08-26" }],
    attachments: [
      { id: "doc_report", filename: "final-report.pdf", documentType: "final_report", description: "Final medical-legal report" },
      { id: "doc_pos", filename: "proof-of-service.pdf", documentType: "proof_of_service", description: "Proof of service" },
    ],
    totalCharge: 2015, totalPaid: 0, balanceDue: 2015,
  },
  patient: { name: "Alex Morgan", firstName: "Alex", lastName: "Morgan", dob: "1988-03-05" },
  injury: { claimNumber: "W8331838-0001", employer: "Northstar Foods", doi: "2025-05-30", adjNumber: "ADJ21439594", claimsAdminId: "payer_republic", claimsAdminName: "Republic Indemnity", claimPatternStatus: { state: "match", label: "Claim pattern matches Republic Indemnity" } },
};

export default function App() {
  return <main className="demo">
    <BillSnapshotSummary
      bill={review.bill}
      patient={review.patient}
      injury={review.injury}
      delivery={{ payerName: "Republic Indemnity", contacts: {} }}
      appearance={{ preset: "clinical-blue" }}
    />
  </main>;
}
BillFollowUp.tsx
import {
  BillExplanationOfReview,
  BillRemittanceCard,
  BillPayerContactCard,
  BillPaymentLedger,
} from "@mindbill/react";

<BillExplanationOfReview
  remittance={bill.remittance}
  eors={bill.eors}
  payments={bill.payments}
  submittedAt={bill.lifecycle.submittedAt}
  onOpenEor={previewEor}
/>

// Lower-level legacy surfaces remain available for custom layouts.
<BillRemittanceCard remittance={bill.remittance} />
<BillPayerContactCard delivery={bill.delivery} />
<BillPaymentLedger payments={bill.payments} />
BillExplanationOfReviewSynthetic data · edit and run
import { BillExplanationOfReview } from "@mindbill/react";

const remittance = {
  billedAmount: 2015, expectedAmount: 2015, payerAllowedAmount: 650,
  payerReportedPaid: 503.75, postedPrincipal: 450, postedAdditional: 53.75,
  totalPostedCash: 503.75, balanceDue: 1511.25,
  denialReason: "Payment reduced pending additional documentation.",
};

const eors = [{
  id: "eor_1", filename: "EOR-1038.pdf", description: "Explanation of Review",
  addedAt: "2026-08-25T17:42:18Z", contentUrl: "#eor",
}];

const payments = [{
  id: "payment_1", method: "check", checkNumber: "4811505",
  status: "deposited", depositDate: "2026-08-25", checkReceived: true,
  receivedDate: "2026-08-23", amount: 503.75, principalAmount: 450,
  feeAmount: 53.75, feeReason: "Penalty and interest", source: "paper",
  postedAt: "2026-08-25T17:42:18Z", updatedAt: null, note: "Partial payment",
}];

export default function App() {
  return <main className="demo">
    <BillExplanationOfReview
      remittance={remittance}
      eors={eors}
      payments={payments}
      submittedAt="2026-08-12T17:00:00Z"
      onOpenEor={(eor) => alert("Preview " + eor.filename)}
      appearance={{ preset: "orange-bright" }}
    />
  </main>;
}
BillRemittanceCardSynthetic data · edit and run
import { BillRemittanceCard } from "@mindbill/react";

export default function App() {
  return <main className="demo">
    <BillRemittanceCard
      remittance={{
        billedAmount: 2015,
        expectedAmount: 2015,
        payerAllowedAmount: 650,
        payerReportedPaid: 503.75,
        postedPrincipal: 450,
        postedAdditional: 53.75,
        totalPostedCash: 503.75,
        balanceDue: 1511.25,
        denialReason: "Payment reduced pending additional documentation.",
      }}
      appearance={{ preset: "orange-bright" }}
    />
  </main>;
}
BillPayerContactCardSynthetic data · edit and run
import { BillPayerContactCard } from "@mindbill/react";

export default function App() {
  return <main className="demo">
    <BillPayerContactCard
      delivery={{
        payerName: "Republic Indemnity",
        contacts: {
          adjusterName: "Jordan Lee",
          adjusterPhone: "(213) 555-0188",
          adjusterEmail: "jordan.lee@example.test",
          faxNumber: "(213) 555-0199",
          mailingAddress: "PO Box 19600, Irvine, CA 92623",
        },
      }}
      appearance={{ preset: "clinical-blue" }}
    />
  </main>;
}
BillPaymentLedgerSynthetic data · edit and run
import { BillPaymentLedger } from "@mindbill/react";

const payments = [{
  id: "payment_1", method: "check", checkNumber: "4811505",
  status: "deposited", depositDate: "2026-08-25", checkReceived: true,
  receivedDate: "2026-08-23", amount: 503.75, principalAmount: 450,
  feeAmount: 53.75, feeReason: "Penalty and interest", source: "paper", postedAt: "2026-08-25T17:42:18Z",
  updatedAt: null, note: "Partial payment",
}];

export default function App() {
  return <main className="demo">
    <BillPaymentLedger payments={payments} appearance={{ preset: "orange-bright" }} />
  </main>;
}

Actions and history

BillLifecycleActions renders the action list returned with lifecycle data. Keep the server response authoritative.

BillActions.tsx
import { BillLifecycleActions } from "@mindbill/react";

<BillLifecycleActions
  actions={bill.lifecycle.actions}
  onAction={(action) => openAction(action.id)}
  showUnavailable
/>
BillLifecycleActionsSynthetic data · edit and run
import { useState } from "react";
import { BillLifecycleActions } from "@mindbill/react";

const actions = [
  { id: "view_eor", label: "View EOR", enabled: true },
  { id: "second_review", label: "Submit second review", enabled: true, primary: true },
  { id: "post_payment", label: "Post payment", enabled: false, reason: "No payable EOR line remains." },
  { id: "close", label: "Close bill", enabled: true },
];

export default function App() {
  const [message, setMessage] = useState("Choose an available action.");
  return <main className="demo">
    <h2>Denied · $2,015.00 due</h2>
    <p>{message}</p>
    <BillLifecycleActions
      actions={actions}
      showUnavailable
      appearance={{ preset: "orange-bright" }}
      onAction={(action) => setMessage(action.label + " selected")}
    />
  </main>;
}

BillActivityTimeline renders bill.activity. Webhooks remain the durable server-side signal for your own database and analytics.

BillHistory.tsx
import { BillActivityTimeline } from "@mindbill/react";

<BillActivityTimeline
  events={bill.activity}
  appearance={{ preset: "orange-bright" }}
/>
BillActivityTimelineSynthetic data · edit and run
import { BillActivityTimeline } from "@mindbill/react";

const events = [
  { id: "evt_4", type: "bill.denied", createdAt: "2026-08-25T17:42:18Z", description: "Medical necessity or frequency" },
  { id: "evt_3", type: "eor.received", createdAt: "2026-08-25T17:41:02Z", description: "EOR-1038.pdf received" },
  { id: "evt_2", type: "bill.accepted", createdAt: "2026-08-13T09:16:00Z" },
  { id: "evt_1", type: "bill.submitted", createdAt: "2026-08-12T16:08:00Z", actor: "Taylor R." },
];

export default function App() {
  return <main className="demo">
    <BillActivityTimeline
      events={events}
      appearance={{ preset: "clinical-blue" }}
    />
  </main>;
}

Browser clients

Use the framework-neutral clients for submitted bill reads and lifecycle actions outside React rendering or inside your own state layer.

billing-client.ts
import {
  createBillLifecycleClient,
  createBillStatusClient,
} from "@mindbill/react";

const lifecycle = createBillLifecycleClient({
  billId,
  sessionEndpoint: `/api/mindbill/bills/${billId}/session`,
});
const bill = await lifecycle.getLifecycle();

const status = await createBillStatusClient({
  billId,
  sessionEndpoint: `/api/mindbill/bills/${billId}/session`,
}).getStatus();

Hosted wrappers

MindBillBillReview and MindBillBillTimeline wrap hosted post-submission surfaces when native composition is not practical.

HostedBilling.tsx
import { MindBillBillReview, MindBillBillTimeline } from "@mindbill/react";

<MindBillBillReview sessionToken={token} embedUrl={reviewUrl} />
<MindBillBillTimeline sessionToken={token} embedUrl={timelineUrl} />
MindBillBillReviewLive wrapper · add a session to connect
import { useState } from "react";
import { MindBillBillReview } from "@mindbill/react";

export default function App() {
  const [error, setError] = useState("");
  return <main className="demo">
    <h2>Hosted bill review</h2>
    <p>Replace these two values with a session minted by your server.</p>
    <MindBillBillReview
      sessionToken="demo-session-replace-me"
      embedUrl="https://app.mindbill.org/embed/bill-review"
      appearance={{ theme: "light", accentColor: "#ff4f0a" }}
      onMindBillError={(event) => setError(event.detail.message)}
    />
    {error && <p className="error">{error}</p>}
  </main>;
}
MindBillBillTimelineLive wrapper · add a session to connect
import { useState } from "react";
import { MindBillBillTimeline } from "@mindbill/react";

export default function App() {
  const [error, setError] = useState("");
  return <main className="demo">
    <h2>Hosted bill timeline</h2>
    <p>Replace these two values with a session minted by your server.</p>
    <MindBillBillTimeline
      sessionToken="demo-session-replace-me"
      embedUrl="https://app.mindbill.org/embed/bill-timeline"
      appearance={{ theme: "light", accentColor: "#1677ff" }}
      onMindBillError={(event) => setError(event.detail.message)}
    />
    {error && <p className="error">{error}</p>}
  </main>;
}

Appearance and utilities

BILL_SUBMISSION_REQUIRED_FIELDS

The canonical required fields used by BillSubmissionForm.

validateBillSubmission

Run the same submission validation outside the rendered form.

mindBillThemePresets

mindbill, orange-bright, clinical-blue, and the generic warm calm-clinical preset.

resolveMindBillAppearance

Resolve a preset plus token overrides.

mindBillAppearanceStyle

Convert appearance tokens to CSS custom properties.

ensureTrailingProcedureLine

Keep exactly one empty procedure row after populated rows.

summarizeBillingDashboard

Calculate outstanding balance, paid totals, status counts, and aging buckets.

buildBillingReportRows

Group normalized bill summaries by payer, lifecycle status, or aging bucket.

buildBillingReportCsv

Export the grouped report as CSV.

buildBillStatusAgingMatrix

Aggregate bills into the status × aging grid with per-cell bills and totals.

buildBillStatusAgingCsv

Export the status × aging grid as CSV.