1. Get an API key
Create a sandbox organization in the developer console, then copy its key from API keys.
MINDBILL_API_KEY=your_sandbox_key\nAPP_ORIGIN=http://localhost:3000For Next.js, create .env.local in the project root, alongside package.json, even if you use src/app. Use the exact name MINDBILL_API_KEY in both your environment and server code. Set APP_ORIGIN to your frontend’s exact origin, including its port. Restart your dev server after setting these values.
Keep the key on your server; do not add a NEXT_PUBLIC_ prefix or put it in Angular environment.ts. Use invented patient data in sandbox; sandbox submissions never reach payers.
2. Install the library
Choose your frontend. The examples below will follow your selection.
npm install @mindbill/react@0.69.0For Next.js, copy the examples into the files shown below, using src/app instead of app if that is your project's layout. Or run the starter app.
npm install @mindbill/angular@latestUse an Angular 18–21 application. Put the component files below in src/app; step 5 includes the routes and router setup. Angular's development server does not implement API endpoints: run the backend from step 3 separately.
Connect the local frontend to your backend
If Angular runs at http://localhost:4200 and your backend at http://localhost:3000, use this proxy so the components' /api requests reach your server. Adjust the target for your backend, set the backend's APP_ORIGIN=http://localhost:4200, and keep your API key in that backend's environment. The Next.js handler also reads APP_ORIGIN.
{
"/api/**": {
"target": "http://localhost:3000",
"changeOrigin": false
}
}ng serve --proxy-config proxy.conf.jsonIn production, route /api to the authenticated backend under your application's origin, and serve Angular's entry page for frontend route reloads. See Angular's proxy guide.
3. Add a session route
Your backend checks the signed-in user and uses its API key to ask MindBill for a short-lived session token. It returns that token to your frontend, which calls MindBill directly. With a backend-only integration, your server calls MindBill using the API key. The API key stays on your server in both flows.
Choose your backend below. For Next.js, copy the function into app/api/mindbill/session/route.ts. Express, FastAPI, and other backends expose the same POST /api/mindbill/session contract. Each uses the server-held MINDBILL_API_KEY from step 1 to create a short-lived browser session.
// app/api/mindbill/session/route.ts
export async function POST(request: Request) {
const allowedOrigin = process.env.APP_ORIGIN;
if (!allowedOrigin || request.headers.get("origin") !== allowedOrigin) {
return Response.json({ error: "Origin not allowed" }, { status: 403 });
}
try {
const access = await authorizeBillingSession(request);
if (!access) return Response.json({ error: "Not authorized" }, { status: 403 });
const response = await fetch(
"https://app.mindbill.org/partner/v2/browser-sessions",
{
method: "POST",
headers: {
Authorization: `Bearer ${access.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
subject: access.subject,
allowedOrigin,
permissions: access.permissions,
...(access.resource ? { resource: access.resource } : {}),
expiresIn: 900,
}),
cache: "no-store",
signal: AbortSignal.timeout(10_000),
},
);
if (!response.ok) {
// Log the status only, never keys, session tokens, or response bodies.
console.error("MindBill session creation failed", response.status);
throw new Error("Session creation failed");
}
return Response.json(await response.json(), {
headers: { "Cache-Control": "no-store" },
});
} catch {
return Response.json(
{ error: "Billing session unavailable" },
{ status: 503 },
);
}
}
// YOUR host adapter, not an SDK export. Connect existing sign-in, CSRF,
// billing roles, and server-owned customer -> MindBill credential mapping.
// Use MINDBILL_API_KEY only for a single authorized organization.
// Return null for denied access. Never accept these values from browser input.
async function authorizeBillingSession(_request: Request): Promise<{
subject: string;
apiKey: string;
permissions: string[];
resource?: { billId: string };
} | null> {
throw new Error("Connect existing authentication and customer-key mapping first");
}Connect authorizeBillingSession to your existing authentication before trying the page; it fails closed until then. Resolve the active customer and its server-held credential from trusted membership data. For a full billing workspace, return the scopes below. Keep sandbox and live credentials separate.
Connect your existing authentication and customer-key mapping, and set APP_ORIGIN to your frontend’s exact origin. See the auth adapter recipe.
// Existing Express server; browser uses same-origin /api/mindbill/session.
app.post("/api/mindbill/session", async (req, res) => {
if (!process.env.APP_ORIGIN || req.get("origin") !== process.env.APP_ORIGIN)
return res.status(403).json({ error: "Origin not allowed" });
try {
const access = await authorizeBillingSession(req);
res.set("Cache-Control", "no-store").json(await mintSession(access));
} catch {
// Map host auth failures to 401/403; do not expose upstream response bodies.
res.status(503).json({ error: "Billing session unavailable" });
}
});
// Server only. Replace this placeholder with your EXISTING auth/tenant/role checks.
async function authorizeBillingSession(_request) {
throw new Error("Connect existing authentication and customer-key mapping first");
}
async function mintSession(access) {
const response = await fetch("https://app.mindbill.org/partner/v2/browser-sessions", {
method: "POST",
headers: { Authorization: "Bearer " + access.apiKey, "Content-Type": "application/json" },
body: JSON.stringify({
subject: access.subject, allowedOrigin: process.env.APP_ORIGIN,
permissions: access.permissions,
...(access.resource ? { resource: access.resource } : {}),
expiresIn: 900,
}),
signal: AbortSignal.timeout(10000),
cache: "no-store",
});
if (!response.ok) throw new Error("Unable to start billing session");
return response.json();
}Connect your existing authentication and customer-key mapping, and set APP_ORIGIN to your frontend’s exact origin. See the auth adapter recipe.
# Existing FastAPI app. Dependency: requests.
import os
import requests
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
def authorize_billing_session(request):
# Implement the required host adapter above. Fail closed until wired.
raise HTTPException(503, "Connect existing authentication and customer-key mapping first")
@app.post("/api/mindbill/session")
def create_billing_session(request: Request):
origin = os.environ.get("APP_ORIGIN")
if not origin or request.headers.get("origin") != origin:
raise HTTPException(403, "Origin not allowed")
access = authorize_billing_session(request)
payload = {
"subject": access["subject"], "allowedOrigin": origin,
"permissions": access["permissions"], "expiresIn": 900,
}
if access.get("resource"):
payload["resource"] = access["resource"]
try:
upstream = requests.post(
"https://app.mindbill.org/partner/v2/browser-sessions",
headers={"Authorization": "Bearer " + access["apiKey"]},
json=payload, timeout=10,
)
upstream.raise_for_status()
data = upstream.json()
except (requests.RequestException, ValueError):
raise HTTPException(502, "Unable to start billing session") from None
return JSONResponse(data, headers={"Cache-Control": "no-store"})# Server-to-server ONLY, after host authentication, role and tenant checks.
POST https://app.mindbill.org/partner/v2/browser-sessions
Authorization: Bearer <server-only-sandbox-key>
Content-Type: application/json
{
"subject": "<authenticated-user-id>",
"allowedOrigin": "http://localhost:3000",
"permissions": ["bills:create", "bills:read", "bills:act", "documents:read", "payers:read", "eors:read"],
"expiresIn": 900
}
# Return successful JSON with Cache-Control: no-store to that authenticated user.
# Reject invalid origins/unauthorized requests BEFORE calling MindBill.
# On failure return a generic error, never upstream response bodies.
# API-only: call bill endpoints server-to-server instead of minting browser tokens.
# https://docs.mindbill.org/api-reference/create-billWorkspace scopes: bills:create, bills:read, bills:act, documents:read, payers:read, and eors:read. Grant only the user’s permitted actions. For case-only access, resolve the saved bill server-side, set resource: { billId }, and omit bills:create. Use a separate administrator endpoint for organization:manage.
Troubleshoot “Billing session unavailable”
This message means your session route could not create a MindBill session. Confirm your host auth adapter is connected, the authorized customer has a configured key, and APP_ORIGIN exactly matches the browser origin. Save .env.local and restart your dev server after changing environment values.
If MindBill returns 401, check that you copied an active API key from the developer console. For 403, check that the key grants the permissions requested by the route. The example logs only the upstream status; keep keys, session tokens, and upstream response bodies out of logs and browser errors.
Never return an organization-wide session to a user who may access only one case. For multiple customers or access to a single bill, follow the authentication guide.
4. Add a single bill
Copy this page or component, then open /billing/new and fill out the form. Angular also needs the router setup in step 5. After submission, the component displays the bill’s status, documents, payments, actions, and shared notes with author names.
"use client";
import { useState } from "react";
import { BillSubmissionForm, ConnectedBillLifecycle } from "@mindbill/react";
export default function NewBillPage() {
// TODO: Load the current report from your backend.
const report = { id: "report_demo_001", mindbillBillId: null };
const [billId, setBillId] = useState<string | null>(report.mindbillBillId);
function handleSubmitted({ billId }: { billId: string }) {
setBillId(billId);
// TODO: Save billId on this report through your backend.
// PATCH /api/reports/:id with { mindbillBillId: billId }
}
if (billId) return <ConnectedBillLifecycle
billId={billId} sessionEndpoint="/api/mindbill/session"
/>;
return <BillSubmissionForm
sessionEndpoint="/api/mindbill/session"
initialBill={{
externalId: report.id,
patient: {
firstName: "", lastName: "", dateOfBirth: "",
address: { line1: "", city: "", state: "CA", postalCode: "" },
},
claim: { claimNumber: "" },
service: { date: "" },
serviceLines: [],
}}
onSubmitted={handleSubmitted}
/>;
}import { Component } from "@angular/core";
import {
MindBillBillLifecycleComponent, MindBillBillSubmissionComponent,
type BrowserBillCreateInput,
} from "@mindbill/angular";
@Component({
selector: "app-new-bill",
standalone: true,
imports: [MindBillBillLifecycleComponent, MindBillBillSubmissionComponent],
template: `
@if (billId) {
<mindbill-bill-lifecycle [billId]="billId"
sessionEndpoint="/api/mindbill/session" />
} @else {
<mindbill-bill-submission [initialBill]="initialBill"
sessionEndpoint="/api/mindbill/session"
(submitted)="handleSubmitted($event.bill.id)" />
}
`,
})
export class NewBillComponent {
// TODO: Load the current report from your backend before mounting.
report = { id: "report_demo_001", mindbillBillId: null };
billId: string | null = this.report.mindbillBillId;
handleSubmitted(billId: string) {
this.billId = billId;
// TODO: Save billId on this report through your backend.
// PATCH /api/reports/:id with { mindbillBillId: billId }
}
initialBill: BrowserBillCreateInput = {
externalId: this.report.id,
patient: {
firstName: "", lastName: "", dateOfBirth: "",
address: { line1: "", city: "", state: "CA", postalCode: "" },
},
claim: {
claimNumber: "", employer: "", dateOfInjury: "",
claimsAdministrator: { id: "", name: "" },
},
service: { date: "" },
billingProvider: {
name: "", taxId: "", npi: "", phone: "",
address: { line1: "", city: "", state: "CA", postalCode: "" },
},
renderingProvider: { name: "", npi: "", taxonomy: "" },
serviceLocation: {
address: { line1: "", city: "", state: "CA", postalCode: "" },
placeOfServiceCode: "11",
},
diagnoses: [], serviceLines: [],
};
}The empty fields are editable. The sample report represents a record already in your database: use its id as externalId, and save the returned billId in its mindbillBillId field. Use a report or billable work-item ID, since a case may have several bills; no separate ID-generation endpoint is needed.
Saving that link is a good default for reopening the bill. Load the report before mounting and initialize from its saved ID; show loading errors separately. Until you connect the TODOs, the example’s selection resets on reload, but the bill remains saved in MindBill and is available from the dashboard.
Save billId in your database Optional
Once your app has authentication and a database, replace the TODO with a request to your own backend. The handler updates the screen immediately and reports a failed save without submitting the bill again.
// Replace handleSubmitted in the React example above.
async function handleSubmitted({ billId }: { billId: string }) {
setBillId(billId); // The bill is already submitted, even if saving the link fails.
try {
const response = await fetch(`/api/reports/${encodeURIComponent(report.id)}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
// TODO: Include your app's CSRF token if required.
body: JSON.stringify({ mindbillBillId: billId }),
});
if (!response.ok) throw new Error("Could not save bill link");
} catch {
window.alert("Bill submitted, but saving its link failed. Recover it using externalId; do not submit again.");
}
}// Replace handleSubmitted inside NewBillComponent.
async handleSubmitted(billId: string) {
this.billId = billId; // Already submitted, even if saving the link fails.
try {
const response = await fetch(`/api/reports/${encodeURIComponent(this.report.id)}`, {
method: "PATCH",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
// TODO: Include your app's CSRF token if required.
body: JSON.stringify({ mindbillBillId: billId }),
});
if (!response.ok) throw new Error("Could not save bill link");
} catch {
window.alert("Bill submitted, but saving its link failed. Recover it using externalId; do not submit again.");
}
}The route below illustrates a Prisma-style database write. authorizeReport and db are your app’s integrations, not MindBill exports: adapt the imports and model fields to your existing code. The auth helper must validate sign-in, billing access, and CSRF, load the authorized report, and select that report organization’s server-held MindBill token. Return null when access is denied. Keep the token on the server.
// Uses YOUR auth helper and database client; adapt these imports/model names.
import { authorizeReport } from "@/lib/auth";
import { db } from "@/lib/db";
export async function PATCH(request: Request, context: {
params: Promise<{ id: string }>;
}) {
try {
const { id } = await context.params;
// Authenticates, checks billing access + CSRF, and loads the report.
// Returns { id, organizationId, apiToken } from server-owned data, or null.
const report = await authorizeReport(request, id);
if (!report) return Response.json({ error: "Forbidden" }, { status: 403 });
const body = await request.json().catch(() => null);
const billId = body?.mindbillBillId;
if (typeof billId !== "string" || !billId.trim()) {
return Response.json({ error: "Bill ID required" }, { status: 400 });
}
// This token belongs to the report's organization, selected by your server.
const response = await fetch(
`https://app.mindbill.org/partner/v2/bills/${encodeURIComponent(billId)}`,
{
headers: { Authorization: `Bearer ${report.apiToken}` },
cache: "no-store",
signal: AbortSignal.timeout(10_000),
},
);
if (!response.ok) throw new Error("Could not verify bill");
const bill = await response.json();
if (bill.id !== billId || bill.externalId !== report.id) {
return Response.json({ error: "Bill does not match report" }, { status: 409 });
}
// Prisma-style example: atomically set an empty link or accept the same ID.
const saved = await db.report.updateMany({
where: {
id: report.id, organizationId: report.organizationId,
OR: [{ mindbillBillId: null }, { mindbillBillId: billId }],
},
data: { mindbillBillId: billId },
});
if (saved.count !== 1) {
return Response.json({ error: "Report link changed; reload it" }, { status: 409 });
}
return Response.json({ mindbillBillId: billId }, {
headers: { "Cache-Control": "no-store" },
});
} catch {
return Response.json({ error: "Could not save bill link" }, { status: 503 });
}
}Implement PATCH /api/reports/:id in the backend you chose in step 3. Authenticate the request, check CSRF and access to the report, verify the returned MindBill bill with that organization's server key, and require its externalId to match the report ID. Atomically save the ID only when the existing link is empty or already equal. Return a conflict if another bill is linked; do not overwrite it.
The server verifies both the organization and externalId before storing mindbillBillId. Repeating the save with the same ID is safe; a different existing link returns a conflict. For a failed save or a closed browser, recover the ID using the lookup below.
Find the same bill after a reload
If the report has no saved mindbillBillId, your server can recover it using the report’s existing ID as externalId.
This optional Next.js page opens the matching bill at /billing/report.
// app/billing/report/page.tsx — runs on your server.
import Link from "next/link";
import { ConnectedBillLifecycle } from "@mindbill/react";
export default async function ReportBillingPage() {
// TODO: Authenticate the user and check access to this report.
const externalId = "report_demo_001"; // Your saved report or work-item ID.
const response = await fetch(
`https://app.mindbill.org/partner/v2/bills?externalId=${encodeURIComponent(externalId)}&limit=2`,
{
headers: { Authorization: `Bearer ${process.env.MINDBILL_API_KEY}` },
cache: "no-store",
},
);
if (!response.ok) throw new Error("Could not look up the bill");
const { data, nextCursor } = await response.json();
if (data.length > 1 || nextCursor) {
throw new Error("Multiple bills match; choose the intended bill");
}
return data[0] ? <ConnectedBillLifecycle
billId={data[0].id} sessionEndpoint="/api/mindbill/session"
/> : <Link href="/billing/new">Create a bill</Link>;
}In your authorized report-loading endpoint, call GET /partner/v2/bills?externalId=YOUR_REPORT_ID&limit=2 using the organization's server-held key. Return the matched bill ID with the report, then initialize NewBillComponent.billId from it or navigate to /billing/:id. If more than one bill matches or nextCursor is present, require a choice rather than taking the first result.
externalId does not enforce uniqueness or prevent duplicate submissions. Use a stable ID for each billable item. If multiple bills match, choose the intended one; a failed lookup must not be treated as “no bill.”
This also works if you choose not to store billId locally. The lookup API finds the bill even if the browser closed before your submission callback ran.
5. Add a bill dashboard
Copy this page and open /billing to find saved bills. The Add bill button opens the page from step 4.
"use client";
import { ConnectedBillingWorkspace } from "@mindbill/react";
export default function BillingPage() {
return <ConnectedBillingWorkspace
sessionEndpoint="/api/mindbill/session"
// Settings is included. For a separate administrator session (step 7):
// billingSettings={{ sessionEndpoint: "/api/mindbill/settings-session" }}
// Use showSettings={false} to hide the tab.
onCreateBill={() => window.location.assign("/billing/new")}
/>;
}The workspace loads bills and opens their details for you. No database or extra API route is needed.
import { Component, inject, signal, type OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { MindBillBillingDashboardComponent } from "@mindbill/angular";
import type { MindBillDashboardBill } from "@mindbill/angular";
import { loadDashboardBills } from "./load-dashboard";
@Component({
selector: "app-billing",
standalone: true,
imports: [MindBillBillingDashboardComponent],
template: `
@if (loading()) {
<p role="status">Loading bills…</p>
} @else if (error()) {
<p role="alert">{{ error() }}</p>
<button type="button" (click)="load()">Try again</button>
} @else {
<mindbill-billing-dashboard [bills]="bills()"
(billSelected)="openBill($event.id)" (createBill)="createBill()" />
}
`,
})
export class BillingComponent implements OnInit {
private readonly router = inject(Router);
readonly bills = signal<MindBillDashboardBill[]>([]);
readonly loading = signal(true);
readonly error = signal("");
ngOnInit() { void this.load(); }
async load() {
this.loading.set(true);
this.error.set("");
try { this.bills.set(await loadDashboardBills()); }
catch { this.error.set("Could not load bills. Check your billing session and try again."); }
finally { this.loading.set(false); }
}
openBill(id: string) {
void this.router.navigate(["/billing", id]);
}
createBill() { void this.router.navigate(["/billing/new"]); }
}Angular's dashboard displays the rows you supply. This page loads them on mount and shows loading, error, and retry states. Copy the loader below into the same directory. It uses the short-lived browser session from step 3 to fetch every page from the dashboard API; no permanent key enters Angular.
Required: dashboard loader
import type { MindBillDashboardBill } from "@mindbill/angular";
type BrowserSession = { token: string; apiBaseUrl?: string };
type DashboardItem = {
id: string; billNumber?: string | number; patientName: string;
claimNumber?: string; claimsAdministrator?: string;
status: { key: string }; submittedAt?: string | null; arAgeDays?: number | null;
totalCharge: number; totalPaid: number; balanceDue: number;
};
export async function loadDashboardBills(): Promise<MindBillDashboardBill[]> {
async function getSession(): Promise<BrowserSession> {
const response = await fetch("/api/mindbill/session", {
method: "POST", credentials: "same-origin",
headers: { "Content-Type": "application/json" }, body: "{}",
});
if (!response.ok) throw new Error("Could not start billing session.");
return response.json();
}
let session = await getSession();
const items: DashboardItem[] = [];
for (let page = 1; ; page++) {
const request = () => fetch(
`${(session.apiBaseUrl ?? "https://app.mindbill.org").replace(/\/$/, "")}/partner/v2/bill-dashboard?page=${page}&pageSize=100`,
{ headers: { Authorization: `Bearer ${session.token}` } },
);
let response = await request();
if (response.status === 401) { session = await getSession(); response = await request(); }
if (!response.ok) throw new Error("Could not load bills.");
const { data } = await response.json() as {
data: { items: DashboardItem[]; total: number; page: number; pageSize: number };
};
items.push(...data.items);
if (data.page * data.pageSize >= data.total) break;
if (!data.items.length) throw new Error("Bill pagination did not finish.");
}
return items.map((item) => ({
id: item.id, billNumber: item.billNumber, patientName: item.patientName,
claimNumber: item.claimNumber, payerName: item.claimsAdministrator,
state: item.status.key, submittedAt: item.submittedAt ?? undefined,
agingDays: item.arAgeDays ?? undefined,
totalCharge: item.totalCharge, totalPaid: item.totalPaid, balanceDue: item.balanceDue,
}));
}Totals cover the loaded rows. For large organizations, use a paginated view with server-provided totals instead of loading the complete registry.
Required: bill routes and router setup
Add the detail component below, then merge these routes into your existing router. Keep named routes such as billing/new and billing/settings before billing/:id.
import { Component, Input } from "@angular/core";
import { MindBillBillLifecycleComponent } from "@mindbill/angular";
@Component({
selector: "app-bill",
standalone: true,
imports: [MindBillBillLifecycleComponent],
template: `<mindbill-bill-lifecycle [billId]="id"
sessionEndpoint="/api/mindbill/session" />`,
})
export class BillComponent {
@Input({ required: true }) id!: string;
}import type { Routes } from "@angular/router";
import { BillingComponent } from "./billing.component";
import { NewBillComponent } from "./new-bill.component";
import { BillComponent } from "./bill.component";
export const routes: Routes = [
{ path: "billing", component: BillingComponent },
{ path: "billing/new", component: NewBillComponent },
// Add other named billing routes before the :id route.
{ path: "billing/:id", component: BillComponent },
];Add withComponentInputBinding() to your existing provideRouter call so the URL's id reaches BillComponent. Preserve your other routes and application providers.
import type { ApplicationConfig } from "@angular/core";
import { provideRouter, withComponentInputBinding } from "@angular/router";
import { routes } from "./app.routes";
export const appConfig: ApplicationConfig = {
providers: [
// Keep your existing application providers.
provideRouter(routes, withComponentInputBinding()),
],
};Your root component needs a RouterOutlet. If you already have an app shell, add the import and outlet to that shell.
import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
@Component({
selector: "app-root",
standalone: true,
imports: [RouterOutlet],
template: `<router-outlet />`,
})
export class AppComponent {}Ensure the application bootstrap uses that configuration. Keep the change-detection and polyfill setup generated by your Angular version.
import { bootstrapApplication } from "@angular/platform-browser";
import { AppComponent } from "./app/app.component";
import { appConfig } from "./app/app.config";
bootstrapApplication(AppComponent, appConfig).catch(console.error);See Angular's routing guide for integrating these routes into an existing application.
Add more when you need it
6. Pre-fill the create-bill form Optional
In step 4, replace the empty fields in initialBill with values you already have. For example:
// Replace initialBill in step 4 with your known values.
{
externalId: "report_demo_001",
patient: {
firstName: "Taylor", lastName: "Example", dateOfBirth: "1984-04-12",
address: {
line1: "100 Example Avenue", city: "Los Angeles",
state: "CA", postalCode: "90012",
},
},
claim: { claimNumber: "DEMO-12345" },
service: { date: "2026-09-08" },
serviceLines: [{ code: "ML201", units: 1 }],
}For Angular, fill the matching fields in the existing initialBill object and keep its other required fields. Users can edit the values and attach PDFs before submitting. See the field reference for more options.
7. Add billing settings Optional
React 0.64.0 includes a Settings tab in the billing workspace by default. Pass billingSettings with your administrator-only settings endpoint, or let it reuse an already authorized workspace session. Set showSettings={false} to hide the tab. A separate settings page is optional; the examples below also support standalone settings and Angular.
"use client";
import { BillingSettings } from "@mindbill/react";
export default function BillingSettingsPage() {
return <BillingSettings
sessionEndpoint="/api/mindbill/settings-session"
/>;
}import { Component } from "@angular/core";
import { MindBillOrganizationOnboardingComponent } from "@mindbill/angular";
@Component({
selector: "app-billing-settings",
standalone: true,
imports: [MindBillOrganizationOnboardingComponent],
template: `<mindbill-organization-onboarding
variant="settings"
sessionEndpoint="/api/mindbill/settings-session"
/>`,
})
export class BillingSettingsComponent {}Add POST /api/mindbill/settings-session to the backend from step 3, using the same session creation code with permissions ["organization:manage"]. Restrict it to administrators.
With a Next.js backend, place that handler in app/api/mindbill/settings-session/route.ts. The page above opens at /billing/settings.
Import BillingSettingsComponent into app.routes.ts and add { path: "billing/settings", component: BillingSettingsComponent } before billing/:id. Open /billing/settings to edit saved settings.
React bill forms load saved provider and location choices automatically with an organization-wide billing session. Add the separate billingSettings session prop to let administrators add choices from the form. See the practice settings guide for setup, W-9 attachment, and scoped-session behavior.
For email alerts, use the administrator recipient list in React, or build an Angular settings view over the same notification API. See Angular notification settings.
8. Add RFA components Optional · treatment billing
For treatment authorization and billing, start with the prebuilt RFA dashboard and the treatment billing quickstart.
Ready for real bills? Complete the sandbox checks. For editor setup or a full implementation brief, use the integration recipes.