Skip to content

MCP reference / Billing

Billing and invoice tools

Eight tools covering the ledger of billable entries and the invoices issued from it. Voiding is a reversal that stays on the ledger, not a delete.

How to read this

Each tool below lists its arguments exactly as the input schema declares them: the type, whether it is required, the permitted values where the schema fixes them, and what the schema says the argument means. None of it is written here. An amount in cents, a default that applies when a field is left out, which action needs which id: those sentences come from the tool itself.

practiceId is never an argument. It comes from your token, and the write paths strip it if a client sends one anyway.

A tool that writes shows one example per action, because what an action needs is not what the input schema marks required: the schema requires action and leaves the rest to the action. Every example is a complete tools/call request whose arguments are checked, when this page is built, against the same object the handler parses. Errors come back inside a 200 response with isError set: the MCP overview has that envelope, and the full catalogue lists every tool at a glance.

Billing and invoices

The ledger of billable entries, and the invoices issued from it.

get_outstanding_balances

Read-onlyAny member

Every patient who owes the practice money, with the date of their oldest unpaid charge and how many days old it is. Pass olderThanDays to answer "who owes money older than 30 days". Payments are applied to the oldest charges first, so the age is the debt still genuinely owed.

Arguments

ToolTypeRequired?What it means
olderThanDaysintegerOptionalOnly patients whose oldest unpaid charge is MORE than this many days old — 30 means 31 and up, not 30 and up, which is what "older than 30 days" means to a practice. For "at least 30", pass 29. OMIT IT unless the caller named a number of days: there is no default age, and a filter nobody asked for hides the answer.

Example call

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_outstanding_balances",
    "arguments": {}
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the requested records" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.

list_billing_entries

Read-onlyOwner only

List billing entries across the whole practice, optionally bounded by a date range (the practice's own days, both included). Use list_patient_billing_entries for one patient, and for what a patient owes.

Arguments

ToolTypeRequired?What it means
fromstringOptionalOnly include records on or after this date. YYYY-MM-DD. Optional.
tostringOptionalOnly include records on or before this date. YYYY-MM-DD. Optional.
includeVoidedbooleanOptionalAlso list voided (cancelled) rows, marked voided=true. Only for a question about what was voided: they count for nothing. The answer always says how many it left out (voidedCount).
limitintegerOptionalHow many entries to return, the newest kept. The answer says when older ones were left out (truncated); totals always cover every entry matched.

Example call

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "list_billing_entries",
    "arguments": {}
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the requested records" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
forbidden403The caller is not an owner of the practice.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.

get_billing_entry

Read-onlyAny member

Read a single billing entry by id.

Arguments

ToolTypeRequired?What it means
entryIdstringRequired

Example call

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_billing_entry",
    "arguments": {
      "entryId": "a6e5987e-a6e5-4c3a-9e21-7b84c5d10f33"
    }
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the requested records" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
not_found404The id does not exist in your practice. A malformed id looks identical.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.

get_invoice

Read-onlyAny member

Read an issued invoice and its frozen line items. Use get_patient_invoice_preview for the unissued draft.

Arguments

ToolTypeRequired?What it means
invoiceIdstringRequired

Example call

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_invoice",
    "arguments": {
      "invoiceId": "504ef4ed-504e-4c3a-9e21-7b84c5d10f33"
    }
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the requested records" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
not_found404The id does not exist in your practice. A malformed id looks identical.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.

get_appointment_charge

Read-onlyAny member

Read what charging an appointment would take: whether it can be charged, and the catalog service matching it, with its id and price. Use it before charging an appointment with write_billing_entry.

Arguments

ToolTypeRequired?What it means
appointmentIdstringRequired

Example call

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_appointment_charge",
    "arguments": {
      "appointmentId": "d4398f97-d439-4c3a-9e21-7b84c5d10f33"
    }
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the requested records" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
not_found404The id does not exist in your practice. A malformed id looks identical.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.

write_billing_entry

WritesCan deleteAny member

Create, update or void a billing entry. action=create requires patientId; update and void require entryId. Voiding is a reversal, not a delete — the entry stays on the ledger marked void. A receipt entry records a payment and gets a payment number (HCRM-0001); it is not a document. Receipts and tax invoices are issued with write_invoice.

Arguments

ToolTypeRequired?What it means
actionstring: create | update | voidRequired
entryIdstringOptionalThe entry to update or void.
patientIdstringOptionalThe patient the entry belongs to. Required for action=create.
typestring: charge | credit | receiptOptionalWhat the entry is. Required for action=create, and NOT changeable afterwards. charge = money owed by the patient; credit = a reduction of what they owe; receipt = money received from them.
amountMinorintegerOptionalThe entry's value as a positive whole number. Required for action=create, except a charge with appointmentId, which takes the price of its service (the booking's, or the one named in serviceId). In MINOR currency units (cents): 4500 means 45.00, not 4500.00.
serviceIdstringOptionalThe catalog service this entry is for; the document line then prints the service's own name. A charge with appointmentId takes the booking's service by itself; otherwise use list_services. Link the service rather than writing its name into notes. Not changeable after creation.
appointmentIdstringOptionalOptional appointment this charge or payment is for (type=charge or type=receipt; never a credit). A charge needs a confirmed appointment of the same patient; use list_appointments to find it and its serviceRef. An appointment can have one charge that is not voided: a second is refused with appointment_already_charged, and voiding the first frees it. A payment may name an appointment only after that appointment's charge is saved: before it, the payment is refused with appointment_not_charged. So for charge, payment and receipt, propose the charge alone first, and the payment only once the charge has been saved. Not changeable after creation.
serviceDatestringOptionalThe date the work was done, YYYY-MM-DD — distinct from when the entry was keyed in, so revenue lands in the right period. Date only, no time.
notesstringOptionalExtra words for the document line, printed after the service's name. Leave it out unless the user gave such words; never repeat the service name here.
paymentMethodstring: cash | card | transfer | otherOptionalHow the money arrived, only on type=receipt (a payment): cash, card, transfer (a bank transfer) or other. A charge or a credit with one is refused. Optional: send it only when the caller said how they were paid; never guess it, and never ask for it before recording the money. action=update may correct it on a payment.
reasonstringOptionalWhy the entry is being voided. Only for action=void.

Example call

action=create

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_billing_entry",
    "arguments": {
      "action": "create",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "type": "charge",
      "amountMinor": 4500,
      "serviceId": "5e6f7081-1c2d-4e9f-8a3b-4c5d6e7f8091",
      "serviceDate": "2026-11-02"
    }
  }
}

action=update

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_billing_entry",
    "arguments": {
      "action": "update",
      "entryId": "3c4d5e6f-9a0b-4c7d-8e1f-2a3b4c5d6e7f",
      "amountMinor": 5000
    }
  }
}

action=void

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_billing_entry",
    "arguments": {
      "action": "void",
      "entryId": "3c4d5e6f-9a0b-4c7d-8e1f-2a3b4c5d6e7f",
      "reason": "entered_in_error"
    }
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the written record" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
not_found404The id does not exist in your practice. A malformed id looks identical.
appointment_already_charged409The appointment already carries a charge that is not voided. Void that one before charging again.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.
subscription_read_only403The subscription has lapsed, so the practice is read-only and nothing may be written.

write_invoice

WritesCan deleteAny member

Issue a document for a patient's unbilled entries (action=issue, requires patientId) or void an issued one (action=void, requires invoiceId). It issues a receipt unless kind=invoice, which needs the patient's tax id on their record. Issuing freezes the current line items.

Arguments

ToolTypeRequired?What it means
actionstring: issue | voidRequired
patientIdstringOptionalThe patient whose unbilled charges to bill. Required for action=issue.
kindstring: receipt | invoiceOptionalWhat to issue. receipt (default) for a private patient; invoice for a customer with a tax id. Only for action=issue.
entryIdsarray<string>OptionalOnly bill these charges (ids from list_patient_billing_entries). Name them whenever the request was about a visit, a day or a period: omitting them bills EVERY unbilled charge the patient has, and a receipt for one day's visit came out at nearly five times the amount because a debt from weeks earlier was swept in with it. Omit them only when the caller genuinely means everything outstanding. Issuing a receipt for a payment just recorded means billing the CHARGE the payment settles, never the payment entry itself (type=receipt is a payment and can never go on a document). Only for action=issue.
invoiceIdstringOptionalThe document to void. Required for action=void.
reasonstringOptionalWhy the document is being voided. Only for action=void.

Example call

action=issue

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_invoice",
    "arguments": {
      "action": "issue",
      "patientId": "0f1c7a2e-5b6d-4c3a-9e21-7b84c5d10f33",
      "kind": "receipt",
      "entryIds": [
        "3c4d5e6f-9a0b-4c7d-8e1f-2a3b4c5d6e7f"
      ]
    }
  }
}

action=void

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "write_invoice",
    "arguments": {
      "action": "void",
      "invoiceId": "4d5e6f70-0b1c-4d8e-9f2a-3b4c5d6e7f80",
      "reason": "entered_in_error"
    }
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the written record" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
not_found404The id does not exist in your practice. A malformed id looks identical.
recipient_tax_id_required422kind=invoice for a patient with no tax id on their record. A receipt needs none; a tax invoice does.
receipt_not_paid409A receipt certifies money received, so it cannot be issued for more than the patient has paid.
entries_not_billable409An id in entryIds is not a charge that can be billed: it is voided, already on a document, or not a charge.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.
subscription_read_only403The subscription has lapsed, so the practice is read-only and nothing may be written.

send_invoice

WritesReaches outsideAny member

Sends a REAL EMAIL: an issued receipt or invoice, attached as a PDF, to the patient's email on file and nowhere else. Optional locale en or el. Refused for a voided document, a patient with no email, and after 3 sends of the same document within an hour.

Arguments

ToolTypeRequired?What it means
invoiceIdstringRequiredThe issued document to email (from list_patient_invoices or write_invoice).
localestring: en | elOptionalThe language of the email and the attached PDF: en or el. Leave it out to use the calling user's own language.

Example call

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "send_invoice",
    "arguments": {
      "invoiceId": "4d5e6f70-0b1c-4d8e-9f2a-3b4c5d6e7f80",
      "locale": "el"
    }
  }
}

Result

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "structuredContent": { "...": "the written record" },
    "content": [{ "type": "text", "text": "<the same JSON>" }]
  }
}

Errors

CodeStatusWhen
validation_failed400An argument is missing, of the wrong type, or outside its allowed values.
not_found404The id does not exist in your practice. A malformed id looks identical.
invoice_voided409The document has been voided. A voided document is not sent.
patient_email_missing422There is no address to send it to: the patient has no email on their record.
plan_feature_required403The practice's plan does not include this. MCP needs Complete.
subscription_read_only403The subscription has lapsed, so the practice is read-only and nothing may be written.

Common questions

Patient records are encrypted, both where they are stored and on their way to your screen. Files are private, and a link to one stops working after a short time. Changes to patient records are recorded, with who made them and when. There is no shared practice password: everyone has their own account. On the Complete plan, the practice owner can also require two-step sign-in for the whole team.

Yes. The practice owner can export the patient list, the reports and the activity log to a spreadsheet file (CSV) from the app, and anyone in the practice can download its files at any time. Your practice data belongs to you, and there is no export fee or waiting period. That holds on either plan, during the trial, and after you cancel.

HyperCRM is paid software. Every practice starts with a 30-day free trial of the Complete plan, with no card needed. After that, Essential starts at €19 a month and Complete at €34, VAT included. The price depends on how many practitioners you have. Every price is on the pricing page, so you never need a sales call.