mindbill/docs

Learn MindBill

Add billing to your product

In this tutorial, you will create one draft bill, attach its payer packet, and put the complete review and billing lifecycle inside your product.

Before you startAn HTTPS backendAny frontend frameworkA sandbox keySynthetic data only
1

Choose your client

REST is the contract. The Node, React, and Angular packages remove boilerplate but are optional.

Terminal
# Pick only what your app uses
npm install @mindbill/node@0.8
npm install @mindbill/react@0.14
npm install @mindbill/angular@0.2

# Server environment — never expose this value to browser code
MINDBILL_API_KEY=mb_test_...
MINDBILL_ORG_ID=org_...
server/mindbill.js
// Any server runtime with fetch
export async function mindbill(path, init = {}) {
  return fetch(`https://app.mindbill.org/api/partner/v2${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.MINDBILL_API_KEY}`,
      "Content-Type": "application/json",
      ...init.headers,
    },
  });
}

// Prefer typed calls? @mindbill/node wraps the same HTTP API.
// const mindbill = new MindBillClient({ apiKey, organizationId });
Checkpoint

Your API key appears only in server code. Do not prefix it with NEXT_PUBLIC_.

2

Create one bill snapshot

Send the exact values that should print on this bill. Reusable provider and location profiles are optional.

Med-legal bill
// Create the draft on your server.
const bill = await mindbill.createBill({
  externalId: evaluation.id,
  billingMode: "med_legal",
  patient: {
    externalId: patient.id,
    firstName: patient.firstName,
    lastName: patient.lastName,
    dateOfBirth: patient.dateOfBirth,
    address: patient.address,
  },
  claim: {
    externalId: claim.id,
    claimNumber: claim.number,
    adjNumber: claim.adjNumber,
    employer: claim.employer,
    dateOfInjury: claim.dateOfInjury,
    claimsAdministrator: { name: claim.payerName },
  },
  service: { date: evaluation.examDate },
  billingProvider,
  renderingProvider,
  serviceLocation,
  diagnoses: report.diagnosisCodes,
  serviceLines: [
    { code: "ML201", modifiers: ["95"], units: 1 },
  ],
}, `bill:${evaluation.id}`);

// This is the only new identifier your application must retain.
await db.evaluation.update({ mindbillBillId: bill.id });
Professional bill
// IME, treatment, malpractice, and other professional billing
const bill = await mindbill.createBill({
  externalId: encounter.id,
  billingMode: "professional",
  patient,
  claim,
  service: { date: encounter.date },
  billingProvider,
  renderingProvider,
  serviceLocation,
  diagnoses: ["M25.512"],
  serviceLines: [
    { code: "99205", units: 1, charge: 475.00 },
    { code: "99080", units: 1, charge: 125.00 },
  ],
}, `bill:${encounter.id}`);
Keep your database

Your patient, case, report, and provider records can remain canonical.

Store one ID

Use bill.id for review, status, EORs, payments, appeals, and close.

Retry safely

Reuse the same idempotency key when retrying the same logical write.

3

Attach the payer packet

Documents are part of the bill, but inclusion is always explicit and reversible before submission.

server/create-billing-draft.ts
// Add only documents intended for the payer packet.
await mindbill.uploadBillDocument(bill.id, {
  file: finalReport,
  filename: "final-report.pdf",
  documentType: "final_report",
  externalId: report.id,
}, `final-report:${report.id}`);

// Repeat for proof_of_service, form_122, w9, or other support.
// Never add medical_records unless the user intentionally selects them.
Usually selected

Final report, proof of service, Form 122, W-9, letter of attestation, and required forms.

User selected only

Medical records and arbitrary supporting documents.

Separate workflow

The attorney report-service packet is not the payer billing packet.

4

Mint a browser session

This is the only server route the connected component needs after bill creation.

server/session-route.ts
// Express example. The same four steps work in any server framework.
app.post("/api/mindbill/session", async (request, response) => {
  const user = await requireUser(request);
  const { billId } = request.body;

  // Use your own authorization model here.
  await assertUserCanAccessBill(user, billId);

  const session = await mindbill.createBrowserSession({
    component: "bill-review",
    billId,
    allowedOrigin: "https://your-product.example",
    expiresIn: 900,
  });

  response.json({
    token: session.token,
    expiresAt: session.expiresAt,
  });
});
Why this is safe

The token is scoped to one bill, bound to the exact browser origin, and expires after 15 minutes. Your permanent key never reaches the browser.

5

Render the billing lifecycle

The component loads the draft, saves edits, searches payers, manages attachments, submits, and shows the next valid action.

React — Billing.tsx
// components/Billing.tsx
"use client";

import { ConnectedBillLifecycle } from "@mindbill/react";
import "@mindbill/react/styles.css";

export function Billing({ billId }: { billId: string }) {
  return (
    <ConnectedBillLifecycle
      billId={billId}
      sessionEndpoint="/api/mindbill/session"
      appearance={{
        accentColor: "#17666b",
        fontFamily: "inherit",
      }}
    />
  );
}
Angular — billing.component.ts
import { Component } from "@angular/core";
import { MindBillBillLifecycleComponent } from "@mindbill/angular";

@Component({
  standalone: true,
  imports: [MindBillBillLifecycleComponent],
  template: `
    <mindbill-bill-lifecycle
      [billId]="billId"
      sessionEndpoint="/api/mindbill/session"
      [appearance]="{ preset: 'clinical-blue' }"
    />
  `,
})
export class BillingComponent {
  billId = "bill_...";
}

What you should see

A prefilled, editable bill—not another onboarding flow

Known patient, claim, provider, location, diagnosis, and service values are already present. Users fill only missing fields, verify the payer packet, and submit.

See React and Angular examples →
6

Read status and act

One bill ID follows the claim from draft through payment, denial, resubmission, and close.

DraftSubmittedAcceptedProcessedPaid or action needed
server/status.ts
// Server-side reconciliation or your own reporting UI
const { data: status } = await mindbill.getBillStatus(billId);

console.log(status.state, status.balanceDue, status.availableActions);

// In React, ConnectedBillLifecycle already refreshes status and renders
// only the actions valid for the current bill state.
server/second-review.ts
const review = await mindbill.createBillReview(billId, {
  type: "second_review",
  reason: "The report satisfies the documented criteria.",
  attachmentIds: supportingAttachmentIds,
}, `second-review:${yourReviewId}`);

await mindbill.submitBillReview(
  billId,
  review.data.id,
  `submit-review:${yourReviewId}`,
);
app/api/mindbill/webhook/route.ts
// 1. Verify MindBill-Signature against the exact raw body.
// 2. Deduplicate on event.id.
// 3. Re-read authoritative state instead of trusting cached UI state.
if (event.type === "bill.status_changed") {
  const { data: status } = await mindbill.getBillStatus(event.data.billId);
  await db.billingStatus.upsert(status);
}
RejectedEdit the bill and resubmit it
Denied or partially paidAdd support and submit Second Bill Review
EOR availableView the EOR PDF and post payment
Any open stateClose the bill when appropriate

Where to go next

Choose the level of control you need

API reference

Every operation

Use the SDK for typed application code. Use these operation pages for request fields, response schemas, errors, and raw HTTP examples.