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 SDK
npm install @mindbill/node@latestCreate the client on your server, with MINDBILL_API_KEY loaded in its environment. The examples use Node.js with TypeScript.
import { MindBillClient } from "@mindbill/node";
export const mindbill = new MindBillClient({
apiKey: process.env.MINDBILL_API_KEY!,
});Shared helper for direct API requests
Save this beside your client. The directory, search, and some action examples use api() for endpoints the SDK does not yet wrap.
// Server-side helper for endpoints not yet wrapped by the Node SDK.
export async function api(path: string, method = "GET", body?: unknown, key?: string) {
const response = await fetch(`https://app.mindbill.org/partner/v2${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.MINDBILL_API_KEY}`,
...(body === undefined ? {} : { "Content-Type": "application/json" }),
...(key ? { "Idempotency-Key": key } : {}),
},
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error(`MindBill request failed: ${response.status}`);
return response.json();
}These examples use a key for one organization. Multi-organization partners also set the SDK’s organizationId and the API’s x-mindbill-org-id header.
3. Create a bill
Download the example bill.json, replace its administrator placeholders with a directory entry for your test claim, and put a synthetic PDF beside it as synthetic-final-report.pdf.
Find a supported claims administrator
Use this when you do not already have a directory ID. Search by name, review the matching entry and its payer options, and copy its ID and name into your bill. Use offset to page through the results.
import { api } from "./mindbill-api";
const directory = await api("/claims-administrators?q=Sedgwick&limit=20");
console.log(directory.results);
// Review the matching administrator and its payer options.
// Set bill.json → claim.claimsAdministrator to its id and name.The response has a top-level results array and total. See the directory reference for routing fields.
See the example bill fields
{
"externalId": "report_demo_001",
"billingMode": "med_legal",
"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",
"employer": "Example Manufacturing",
"dateOfInjury": "2026-06-20",
"injuryState": "CA",
"claimsAdministrator": {
"id": "REPLACE_WITH_DIRECTORY_ID",
"name": "REPLACE_WITH_DIRECTORY_NAME"
}
},
"service": {
"date": "2026-08-25"
},
"billingProvider": {
"name": "Example Evaluations Medical Group, Inc.",
"taxId": "12-3456789",
"npi": "1234567893",
"phone": "213-555-0100",
"address": {
"line1": "100 Example Avenue",
"city": "Los Angeles",
"state": "CA",
"postalCode": "90012"
}
},
"renderingProvider": {
"name": "Avery Example, MD",
"npi": "1234567893",
"taxonomy": "208D00000X"
},
"serviceLocation": {
"name": "Main office",
"address": {
"line1": "100 Example Avenue",
"city": "Los Angeles",
"state": "CA",
"postalCode": "90012"
},
"placeOfServiceCode": "11"
},
"diagnoses": [
"M25.512"
],
"serviceLines": [
{
"code": "ML201",
"units": 1
}
]
}Optional: check EAMS before submitting
For a claim with an ADJ number, save a partner-linked claim first, then request an EAMS check. Run this after loading billInput and before submitting it; import api from the helper in step 2.
const savedClaim = await api("/claims", "POST", {
patient: { ...billInput.patient, externalId: "patient_demo_001" },
claim: { ...billInput.claim, externalId: "claim_demo_001", adjNumber: "ADJ1234567" },
}, "demo-provision-claim-001");
const preflight = await api(
`/claims/${encodeURIComponent(savedClaim.claimId)}/eams-preflight`, "POST", {},
);
console.log(preflight.status, preflight.candidates);
// Review candidate claims administrators before choosing bill.claim.claimsAdministrator.EAMS returns candidate administrators for review, not a verified current adjuster. Sandbox returns not_performed. Review the candidates and update your bill’s administrator before submission.
import { readFile } from "node:fs/promises";
import { mindbill } from "./mindbill";
const billInput = JSON.parse(await readFile("./bill.json", "utf8"));
const report = await readFile("./synthetic-final-report.pdf");
const bill = await mindbill.createAndSubmitBill({
bill: billInput,
submission: { route: "ebill" },
documents: [{
filename: "synthetic-final-report.pdf",
documentType: "final_report",
contentBase64: report.toString("base64"),
}],
}, "demo-create-report-001");
console.log(bill.id);This creates and submits the bill; it does not save a draft. Keep bill.id for the steps below. Reuse the idempotency key only when retrying this exact submission; use a new key for a new operation. Sandbox submissions never reach payers.
4. Get the bill’s status
const status = await mindbill.getBillStatus(bill.id);
console.log(status);
// Full bill details and the actions currently available:
const currentBill = await mindbill.getBill(bill.id);
const lifecycle = await mindbill.getBillLifecycle(bill.id);Use status for a summary and lifecycle for the bill’s history and available actions. For automatic updates, add webhooks.
5. Perform an action
Choose an action allowed by the current lifecycle. These are separate examples for eligible bills.
Correct a rejected bill by sending the complete corrected bill snapshot.
import { api } from "./mindbill-api";
await api(`/bills/${encodeURIComponent(bill.id)}/actions`, "POST", {
action: "resubmit", reason: "Corrected the claim number.",
bill: { ...billInput, claim: { ...billInput.claim, claimNumber: "DEMO-12345-CORRECTED" } },
submission: { route: "ebill" },
}, "demo-resubmit-001");Dispute denied or underpaid lines. Replace the line-item ID with the disputed line from this bill; MindBill resolves the payer claim control number from the bill’s evidence.
import { api } from "./mindbill-api";
await api(`/bills/${encodeURIComponent(bill.id)}/actions`, "POST", {
action: "second_review",
lineItems: [{ lineItemId: "YOUR_DISPUTED_LINE_ITEM_ID", reason: "Please review the denied service against the attached report." }],
route: "ebill",
}, "demo-second-review-001");await mindbill.performBillAction(bill.id, {
action: "close", reason: "No further collection is needed.",
}, "demo-close-001");await mindbill.performBillAction(bill.id, {
action: "reopen", reason: "A new payment review is needed.",
}, "demo-reopen-001");import { api } from "./mindbill-api";
await api(`/bills/${encodeURIComponent(bill.id)}/actions`, "POST", {
action: "add_note", note: "Requested an updated explanation of review.",
}, "demo-note-001");Direct request examples use api() from step 2. See bill actions for the full action list, required evidence, and eligibility rules.
6. Search and filter bills
Use the dashboard endpoint for lists, reports, and totals across matching bills.
import { api } from "./mindbill-api";
const dashboard = await api(
"/bill-dashboard?status=denied,rejected&q=Example&page=1&pageSize=25&sort=balanceDue&dir=desc",
);
console.log(dashboard.data.items, dashboard.data.total, dashboard.data.balanceTotal);Filter by status, age, administrator, or provider; sort and paginate the results. data.total and data.balanceTotal cover all matching bills, including those beyond the current page.
List bills for synchronization
// Cursor list is for synchronization/iteration, not dashboard free-text search.
const page = await mindbill.listBills({ limit: 100 });
if (page.nextCursor) {
const nextPage = await mindbill.listBills({ limit: 100, cursor: page.nextCursor });
}listBills also filters by state and your external bill, patient, or claim IDs. Continue until nextCursor is empty.
7. Save organization and provider data Optional
Save your practice profile once, then use savedProviderId in later bills. The profile write requires orgs:write; reading it requires orgs:read.
import { api } from "./mindbill-api";
const profile = await api("/organization/billing-profile", "PUT", {
billingProviders: [{
externalId: "practice_demo_001",
name: "Example Evaluations Medical Group, Inc.",
taxId: "12-3456789", npi: "1234567893", billType: "Professional",
phone: "213-555-0100", billingStreet: "100 Example Avenue",
billingCity: "Los Angeles", billingState: "CA", billingZip: "90012",
}],
}, "demo-save-provider-001");
const provider = profile.data.billingProviders.find(
(item: { externalId?: string }) => item.externalId === "practice_demo_001",
);
if (!provider) throw new Error("Saved provider was not returned.");
const nextBillInput = { ...billInput, billingProvider: { savedProviderId: provider.id } };Updates match providers by ID or external ID and do not delete existing entries. Continue sending rendering-provider and service-location fields in each bill snapshot. See the API reference for organization provisioning and saved profiles.
Before using a live key, complete the sandbox checks. For treatment services, follow the treatment billing quickstart.