Endpoint
https://app.biloh.com.au/api/mcp
MCP Integration
Biloh exposes a Model Context Protocol server. Connect Claude, GPT, Grok, or any MCP client and run your business through chat.
Endpoint
https://app.biloh.com.au/api/mcp
Transport
Streamable HTTP / SSE
Tools
224 operator tools
Paste the configuration below into your MCP client. ReplaceYOUR_PAT_HEREwith a Biloh Personal Access Token issued by your tenant administrator.
Claude Desktop / Claude Code / any JSON-config MCP client
{
"mcpServers": {
"biloh": {
"url": "https://app.biloh.com.au/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_PAT_HERE"
}
}
}
}ChatGPT, Perplexity, Grok (MCP-compatible builds)
Add a custom MCP server in your client's settings with the endpoint URL above and the Bearer token in theAuthorizationheader. The shape matches Claude Desktop's JSON config.
Test from your terminal
curl -X POST https://app.biloh.com.au/api/mcp \
-H "Authorization: Bearer YOUR_PAT_HERE" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'Auto-generated from the live MCP registry. Operator-persona tools shown; architect and platform-internal tools are not listed. 224 tools across 11 categories.
list_clients
Returns clients for the authenticated user's tenant. Supports filtering by status, free-text search via `query` (case-insensitive substring match across legal_name, trading_name, and billing_email), and pagination via limit/offset. Excludes soft-deleted rows. Excludes test rows (is_test=true) by default — pass include_test:true to include them.
get_client
Returns a single client by ID for the authenticated user's tenant. Returns the full client row including legal_name, trading_name, ABN, billing_email, status, and metadata.
get_client_footprint
Returns a one-shot, read-only dependency rollup for a client (or contractor/site): counts + sample ids of every attached site, contract_service_line (active vs inactive), job (by status), proposal (by status), invoice (by status), and payable, plus the parent's is_test/deleted_at and a test/production child split (`test_rollup`). Counts include test and production children (footprint is a cleanup/offboarding aid, not a dashboard); `test_rollup.is_test_false_count` surfaces live production children hanging off an archived parent. `ids` are capped at 10 per type; `count` is the true total.
get_client_portal_link
Returns the client's persistent portal URL (the /portal/client/{token} link) and its underlying portal token. Mirror of get_contractor_portal_link (WP-CLIENT-PORTAL-LINK-RECOVERY, bugs e843138b + 2178dc8f). Idempotent: reuses the existing token if one is set, mints a new one (read-or-UPDATE over the clients.portal_token column) if not. The URL host is the tenant's canonical domain.
resend_client_portal_link
Email a client's persistent portal link (the /portal/client/{token} URL) to their stored billing email. The delivery half of lost-email portal recovery — the MCP-native mirror of the operator UI 'Resend portal link' action (POST /api/clients/[id]/resend-portal-link). Reuses the client's existing portal_token, minting one if null (idempotent, like get_client_portal_link). Open-relay guarded: only ever sends to the client's own stored billing_email, never a caller-supplied recipient (same guard pattern as send_operator_report_email). Returns {client_id, sent, provider_message_id, portal_url}.
list_sites
Returns sites for the authenticated user's tenant. Optionally filtered by client_id. Excludes soft-deleted rows.
get_site
Returns a single site by ID for the authenticated user's tenant. Returns address, geo coords, notes (access / hazards / legal), associated client_id, and metadata.
list_site_contacts
Returns contacts for a site (or every contact across the tenant if site_id is omitted). Canonical table is `site_contacts` — the legacy `client_contacts` table is deprecated. Each contact has name, role, email, phone, and is_primary flag.
create_client
Creates a new client record for the tenant. A client is a business entity that contracts the tenant for services. When billing_email is provided, a default site and billing contact are auto-created so the client's contact details flow onto invoices automatically.
update_client
Updates an existing client's business details, billing info, payment terms, or status. Only provided fields are changed.
create_site
Creates a new site (service location) linked to a client. Sites represent physical locations where services are delivered. Use `access_notes` for contractor access instructions and `legal_notes` for compliance/legal conditions specific to this site.
create_site_contact
Creates a contact person at a site. Contacts receive notifications based on their `contact_role` (owner, primary, billing, backup, emergency, technical). Role defaults auto-set notification flags; explicit flag values override defaults.
update_site_contact
Updates a site contact's details or notification preferences. Only provided fields are changed.
update_site
Updates an existing site's name, address, or notes. Only provided fields change; omitted fields are preserved.
onboard_client_for_service
One-step composite to set up a client's service at a site with correct pricing. Creates a contract_service_line and optionally appends special conditions to site.legal_notes. ENFORCES correct data placement: pricing flows to contract_service_line, site conditions flow to site.legal_notes, the service catalog is never touched.
get_client_statement
Returns the aggregated client statement view for a date range: opening balance, in-period invoices/payments/refunds, closing balance, outstanding amount, and per-PO subtotals. Amounts in integer cents. Pure read — does NOT persist a statement row (use propose_send_statement for that).
get_client_audit_timeline
Returns the chronological financial event timeline for one client over a period. Reads `domain_events` filtered to invoice/payment/refund/statement/payable/proposal/client entities owned by the client. Each row exposes BOTH `occurred_at` (real-world event time) AND `recorded_at` (system row-insertion time) per Principle 3 — these can legitimately differ. `client_po_number` surfaces on every relevant row. Money in integer cents.
get_client_portal_finance_view
Returns the four-panel client portal Finance tab data for the given client: outstanding cents, past invoices (with payable flag), payment history, credit balance + recent entries, and whether the credit panel should display per the per-tenant tunability setting. Read-only — does not persist anything.
list_client_invoices
Lists invoices for a specific client, ordered by issued_at descending. Optional status filter. Default page size 50, max 200. Money fields returned as integer cents. Read-only.
list_client_payments
Lists payments recorded against a specific client's invoices, ordered by payment_date descending. Money fields returned as integer cents. Read-only.
request_statement_for_client
Returns the on-demand statement aggregate for a client over a date range — the same data the client sees in their portal Finance tab when they pick a period. Includes opening balance, in-period invoices/payments/refunds, closing balance, outstanding, and per-PO breakdown. Money fields as integer cents. Read-only — does NOT persist a statement row.
set_client_invoicing_cadence
Sets the invoicing cadence for a specific client. Controls what happens when a job for this client is completed.
get_late_fee_config_for_client
Returns the resolved late-fee config for a client — mode (none|interest|flat_fee), flat fee amount (cents), interest rate (bps), grace period days, recurrence (once|monthly), and the source of the mode (tenant_default vs client_override).
list_jobs_for_client
Returns jobs for a specific client (across all their sites) within an optional date window. Without date_from/date_to it returns the client's jobs ordered by scheduled_date (earliest first, capped at limit); pass date_from/date_to — INCLUDING PAST DATES — to window the result, so this tool DOUBLES AS THE CLIENT JOB-HISTORY READ. A backfilled or completed PAST visit surfaces when you bound the window to its date — the 'upcoming' reading is only the default forward slice, never a hard filter, so do NOT conclude a job is 'not in client history' without querying its past date. Each row carries a denormalised service_name label alongside service_id, so status + service_name make the row self-describing. Each row also carries `contract_service_line_id` so jobs can be attributed to the specific recurring line that spawned them (disambiguates 2+ lines on the same service+site). Honours `tenants.scheduling_settings.client_can_see_contractor_details` — if false (default), the `contractor_id` field is stripped from the response.
list_contractors
Returns contractors for the authenticated user's tenant with pagination. Excludes soft-deleted rows. Excludes test rows (is_test=true) by default — pass include_test:true to include them.
get_contractor
Returns a single contractor by ID for the authenticated user's tenant. Returns business details, ABN, primary contact, sma_status, availability, capabilities, and denormalised compliance snapshot.
get_contractor_portal_link
Returns the contractor's persistent portal URL (the /portal/contractor/{token} link) and its underlying portal token. Mirrors the client portal_token surface. Idempotent: reuses the existing token if one is set, mints a new one (read-or-INSERT over the contractors.portal_token column) if not. The URL host is the tenant's canonical domain.
list_contractor_agreements
Returns agreements (SMAs etc.) for a contractor with their current status (draft / sent / accepted / declined / expired).
list_contractor_quotes
Returns quotes a contractor has submitted for site/service combinations. Quotes are contractor-side reference data used when negotiating contract_service_line rates.
list_contractor_compliance_documents
Returns compliance documents (insurance certificates, ABN verifications, workers comp, etc.) for a contractor with their verification status and expiry dates.
create_contractor
Creates a new contractor (sub-contractor) for the tenant. Contractors are businesses that perform services on behalf of the tenant. Captures business_name, ABN, contact details, capabilities, and initial sma_status.
update_contractor
Updates a contractor's business details, insurance, banking, availability, or capabilities. Most fields are operator-accessible; banking fields (`bank_bsb`, `bank_account_number`, `bank_account_name`) require architect persona.
contractor_visible_notes
Read or write the CONTRACTOR-FACING free-text note on a payable or job. This note is DISTINCT from the operator-only `notes` column — the internal `notes` is never shown to a contractor; only this note crosses to the contractor portal (a deliberate, leak-safe channel for field-worker context). USE WHEN: recording a note a contractor SHOULD see (site access, what to bring, a heads-up); reading what note is set; auditing every contractor-visible note across the tenant (operation 'bulk_read'). DON'T USE WHEN: writing operator-internal commentary — use update_job / update_contractor `notes`. PRECONDITIONS: operation 'get'/'set' require entity_type ('payable'|'job') + entity_id in the caller's tenant; 'set' requires note_text (empty string clears the note). SIDE EFFECTS: 'set' updates contractor_visible_note on the matching row (scoped to the caller's tenant). 'get'/'bulk_read' are read-only.
create_contractor_contact
Creates a contact person for a contractor. Each contact is a person with name, role, phone, email, and notification preferences.
add_contractor_service_capability
Declares that a contractor is configured/qualified to deliver a given service by adding a row to the `contractor_services` capability map. This is the row the `assign_job` compliance gate checks (lib/scheduler/compliance.ts §5) — without it, assignment rejects with `no_capability`.
list_contractor_contacts
Returns contacts for a contractor. Each contact is a person with name, role, phone, email, and notification preferences.
update_contractor_contact
Updates a contractor contact's details or notification preferences. Only provided fields are changed.
create_contractor_agreement
Creates a contractor agreement (SMA — Subcontractor Master Agreement) record in `draft` status. Agreements track the legal relationship between tenant and contractor. A draft must exist before propose_send_contractor_agreement can stage a send.
create_contractor_quote
Creates a contractor quote — a price a contractor has offered for a specific site/service combination. Quotes are reference data used when negotiating contract_service_line rates.
create_contractor_compliance_document
Creates a compliance document record (insurance certificate, ABN verification, workers comp, etc.) for a contractor. Documents track the contractor's regulatory compliance status. Newly created docs start unverified.
verify_contractor_abn
Verifies a contractor's stored ABN against the Australian Business Register (ABR) and sets abn_verified=true when the ABN is ACTIVE. A collected ABN is already a satisfied prerequisite; this is the authoritative verification step.
list_jobs_for_contractor
Returns all jobs assigned to a specific contractor within a date range, optionally filtered by status. Contractor-portal-style view (no client legal scope, no client rate). Sorted by scheduled_date ascending.
get_contractor_schedule
Returns a contractor's own upcoming schedule: jobs by date plus aggregate stats. Excludes cancelled and terminal (completed/approved/missed) jobs. No client legal scope, no client rates.
suggest_contractors_for_job
Returns the contractors that PASS the compliance gate for this job's service (`candidates`), the contractors that FAIL it (`blocked` — each with the failure reason, including contractors with no capability registered for the service), plus the contractor preferred for the underlying CSL (if any). Excludes test rows (is_test=true) from both `candidates` and `blocked` by default — pass include_test:true to include them. MVP returns binary pass/fail with no scoring. v2 adds geographic proximity + workload + margin scoring (deferred).
list_services
Returns the service catalog for the authenticated user's tenant. Services are CLIENT-AGNOSTIC and CONTRACTOR-AGNOSTIC — they describe what work is offered, not who it's for. Excludes soft-deleted rows by default.
list_contract_service_lines
Returns the rate schedule lines (per site/service/contractor) for the tenant. Optionally filtered by site_id, service_id, contractor_id, or client_id (resolved via the client's sites). Each row carries the client_rate_cents, contractor_rate_cents, frequency, active months, and active flag, plus denormalised human-readable labels: service_name, frequency_name, human_description, contractor_name (null when no contractor assigned), site_name, client_name, and client_id (resolved via the line's site). human_description is the active recurrence rule's authoritative cadence label (e.g. 'Every week on Thursday') and should be preferred over frequency_name — the catalog template name, which can lag a day_of_week override (bug 0d32f07b). UUID fields are retained for joins.
get_contract_service_line
Returns full details for a single contract service line, including recurrence data (rrule_string, human_description — the authoritative cadence label, preferred over frequency_name which can lag a day_of_week override per bug 0d32f07b — next N occurrences, plus the raw materialised recurrence — program_definition, rdates, exdates — for explicit_months/program cadences), live job counts by status, and the client portal guardrail counters (client_reschedule_count, client_defer_count — quarterly counters incremented by client-initiated reschedules/defers, checked against max_reschedules_per_quarter).
create_service
Creates a generic, reusable service in the tenant's catalog. Services are CLIENT-AGNOSTIC and CONTRACTOR-AGNOSTIC — they describe what work is offered, not who it's for or who delivers it.
update_service
Updates fields on a generic catalog service. Only provided fields are changed. The same content rules as create_service apply: services are CLIENT-AGNOSTIC and CONTRACTOR-AGNOSTIC.
archive_service
Soft-deletes a service from the catalog (sets `deleted_at`). The service row remains in the database for audit history and FK references from existing contract_service_lines / jobs, but is excluded from active listings.
create_frequency
Creates a new frequency in the tenant's catalog. Frequencies define scheduling patterns (e.g., weekly, fortnightly, 4-weekly) used by contract_service_lines to determine job recurrence.
update_frequency
Updates an existing frequency's fields. Only provided fields are changed.
archive_frequency
Soft-deletes a frequency from the catalog (sets `deleted_at`). The frequency row remains in the database for audit history and FK references from existing CSLs, but is excluded from active queries.
create_contract_service_line
Creates a contract service line — THE place for client-specific pricing. Links a site to a service with a frequency, client_rate_cents, and optional contractor_rate_cents. This is where per-client, per-site pricing lives.
update_contract_service_line
Updates a contract service line's rates, active status, contractor assignment, active months, service (in-place swap), or recurrence inputs (day_of_week, week_of_month, anchor_date). This is where rate changes, seasonal adjustments, service swaps, and schedule edits are made.
create_recurring_service
Set up a recurring service for a client at a site — anything from a simple fixed cadence to a multi-phase PROGRAM with intervals that change over the year. Creates a contract service line + a materialised recurrence rule, spawns the look-ahead jobs, and emits job.spawned / schedule.changed so the rest of the chain (dispatch → work orders → invoicing) flows automatically. Two-phase: confirm=false PREVIEWS the exact dates (no writes); confirm=true creates.
list_jobs
Returns jobs (work orders, scheduled visits) for the tenant. Optionally filtered by status, site_id, contractor_id, or date range. Excludes test rows (is_test=true) by default — pass include_test:true to include them. For large result sets use `summary: true` (lean per-job projection + by_status rollup) or `count_only: true` (counts by status, no rows) to stay under the response size limit — recurring schedules make tenant-wide job lists large fast. Full rows include the reschedule fields (is_rescheduled, rescheduled_from, rescheduled_to, reschedule_reason) — the same ones get_job returns — so you can confirm a single-move-vs-cascade across many jobs in one call instead of N get_job calls (tool upgrade ee800883).
get_job
Returns a single job by ID with full details including status, site, contractor, schedule, notes, completion photos, and completion data.
create_job
Creates a new job (work order) for a site/service on a scheduled date. Jobs represent individual instances of work to be performed. Should reference a contract_service_line where one exists. The job starts in `scheduled` status (or `unscheduled` if no date is provided).
update_job
Updates a job's status, schedule, contractor assignment, or notes. Valid statuses: scheduled, dispatched, completed, approved, invoiced, paid, on_hold, cancelled, missed, partial. General-purpose; specialised scheduler tools (assign_job, dispatch_job, approve_job, reschedule_job, cancel_job) are preferred when they apply.
backfill_completed_job
Records an already-performed job when the contractor's bill arrives after the fact. ONE CALL creates the completed job + contractor payable with the attested GST-INCLUSIVE bill amount (84f7204f). Returns a suggested invoice line for billing the client — suggested_invoice_line.amount_cents is the CSL client_rate_cents (ex-GST); pass amount_basis:'gst_exclusive' to create_invoice/add_invoice_line so 10% GST is added on top (treating it as GST-inclusive under-bills the client).
assign_job
Assigns a contractor to a job. Runs the compliance gate (SMA signed, insurance current, capability matches service, availability). On a hard-gate failure the assignment is blocked and the reason is surfaced. Soft warnings are returned but do not block.
dispatch_job
Transitions an assigned job from `scheduled` to `dispatched`. Sets `work_order_sent_at = NOW()`. Emits `job.dispatched`. Generic dispatch — for the email-bearing work-order send, use the propose_send_work_order / approve_send_work_order pair.
approve_job
Approves a completed (or partial) job — admin sign-off step that downstream invoicing consumes. Sets `status='approved'`, `approved_at=NOW()`, `approved_by=actor`. Emits `job.approved` with the full payload (contractorId, scheduledDate, clientId, siteId, cslId, approvalNotes, client_po_number per the PO cascade).
reschedule_job
Moves a single job to a new date and captures a reason. Validates the new date shape and that the job is not in a terminal status (completed/approved/invoiced/paid/cancelled). Sets `is_rescheduled=true` and increments the per-CSL reschedule counter when triggered from the client side. Emits `job.rescheduled`. When `schedule_change_request_id` is supplied, atomically closes that pending request as 'approved' and links it in the audit trail. Any OTHER pending schedule_change_requests on the job are auto-closed as 'superseded' (architect-ratified 2026-06-06) so the operator queue never accumulates stale rows.
cancel_job
Cancels a job and captures a reason. Terminal status transition — cancelled jobs are not respawned by the engine. Emits `job.cancelled`.
decline_job
Contractor declines an assigned job after acceptance. Captures `decline_reason` and clears `contractor_id` so the job returns to the unassigned queue. Status transitions to `declined`. Emits `job.declined`.
list_unassigned_jobs
Returns unassigned jobs needing dispatch (`status='scheduled'` OR `status='declined'`) with `contractor_id IS NULL` — the admin dispatcher's unassigned queue. Declined jobs re-enter this queue for re-dispatch (decline_job clears contractor_id). `decline_reason` is included so the dispatcher knows why a job is back. Filterable by date range. Sorted by scheduled_date ascending. Excludes test rows (is_test=true) by default — pass include_test:true to include them.
bulk_assign_jobs
Assigns N jobs to one contractor in a single call. Compliance gate runs once per (contractor, service_id) — jobs that fail capability or other gates are returned in the `failed` list with their reason. All passes are committed; nothing rolls back on a partial failure.
bulk_reschedule_jobs
Moves N jobs to a single new date with the same reason. Each job's lifecycle is gated independently — completed/approved/invoiced/paid/cancelled jobs are returned in the `failed` list. Emits one `job.rescheduled` event per successful move.
run_job_spawning
On-demand preview and execution of the recurring-job spawning engine. Two-phase: call with dry_run=true (default) to preview what jobs would be created, then dry_run=false with the returned confirm_token to create them.
list_work_orders
Returns work orders for the tenant. Optionally filtered by status, job_id, contractor_id, scope, or contract_service_line_id. Status values: sent, accepted, declined, rejected_after_acceptance, expired, cancelled, withdrawn, requires_reacceptance. Each row carries its WP-WO-01 scope (single_job / job_set / ongoing_series) and `bound_job_ids` (junction bindings for job-bound scopes).
get_work_order
Returns a single work order by ID with full details including signing token, acceptance record, and signed PDF URL. WP-WO-01 scope model: the response includes `scope` (single_job / job_set / ongoing_series) plus the binding — `bound_jobs` (junction-bound job rows) for job-bound scopes, or `contract_service_line` (the bound series) for ongoing_series.
approve_send_work_order
Approves a staged work-order send and immediately dispatches the email to the contractor with a portal-link CTA. Mints the signing_token, creates the `work_orders` row, sends the branded email, and emits `work_order.sent`. The contractor clicks the link, reviews the work order, and digitally accepts via the portal.
record_work_order_acceptance
Records an out-of-band work-order acceptance (e.g. contractor accepted verbally or via email, and the operator is back-filling the system). Creates an immutable `work_order_acceptances` row and flips `work_orders.status` to `accepted`. Handles all three scopes (WP-WO-01): `single_job` / `job_set` acceptance binds the junction-bound job(s) and transitions them to `dispatched`; `ongoing_series` acceptance binds the contract service line (occurrence jobs are NOT bulk-transitioned — dispatch stays per-occurrence) and stores the forward-view snapshot.
cancel_work_order
Cancels an in-flight work order and invalidates its acceptance link. Soft transition: the row is KEPT (status='cancelled'), the signing_token is expired so the portal accept link stops working, and `work_order.cancelled` is emitted. Once cancelled, the bound job's in-flight guard clears, so `propose_send_work_order` can stage a fresh send — this closes the dead-end where a sent WO blocked re-issuing after a contractor reassignment.
propose_send_work_order
Stages a work-order send. Accepts EXACTLY ONE of `job_id` (single job), `job_ids` (a set of jobs for the same contractor, max 100), or `contract_service_line_id` (an ongoing recurring series) — the work-order scope (single_job / job_set / ongoing_series) is inferred from which identifier you supply. Validates the assigned contractor has a signed Subcontractor Master Agreement; for `job_ids` the validation is ALL-OR-NOTHING (WP-WO-03): every member is checked against the full compliance gate (SMA + insurance + capability + availability), the payment gate, and live work-order conflicts — any single failure rejects the WHOLE batch with a per-job `failures` array and creates ZERO rows. A successful job_set stage returns `earnings_total_cents` (Σ per-job contractor rate, ex-GST — a forecast, not a payment commitment). Creates an `mcp_pending_operations` row that must be approved via `approve_send_work_order` before the work order is actually dispatched. The sent email includes a portal-link CTA (Review Work Order button) — the contractor accepts via the portal.
create_invoice_from_proposal
Composite: creates a DRAFT invoice directly from an ACCEPTED proposal. Resolves the client from the proposal, copies each live proposal line to an invoice line (proposal amounts are EX-GST by schema convention — the gateway adds 10% GST and stores canonical GST-inclusive amounts), inherits the proposal's client PO, and computes due_date from the payment-terms cascade (client terms → tenant default) unless an explicit due_date is supplied. Does NOT send — the draft goes through the normal propose_send_invoice → confirm_pending_operation → approve_send_invoice chain.
list_proposals
Lists proposals for the tenant, with optional filters. Returns proposal header info including status, client, site, and dates. Excludes soft-deleted rows by default.
get_proposal
Gets a single proposal with full detail: header, all lines (with service/frequency names), and acceptance records.
preview_proposal_investment
Preview the HONEST, frequency-aware investment summary for a proposal BEFORE sending — the SAME figures the operator view, the signed-agreement PDF, and the client accept page render (one shared calculator, so the preview matches the document, no drift).
list_proposal_acceptances
Lists proposal acceptance records for audit/history. Returns core fields by default; set `include_snapshot=true` to include the full proposal_snapshot JSONB (large). Audit fields include ip_address, user_agent, content_hash, pdf_url, pdf_generated_at, confirmation_email_sent_at, and retention_class.
create_proposal
Creates a new proposal in `draft` status for a client at a specific site. After creation, add lines with add_proposal_line. Auto-assigns the next proposal_number for the tenant.
update_proposal
Updates the header fields (intro message / notes, valid_until date) on a draft proposal. Only allowed when proposal status is `draft` — same constraint as update_proposal_line. For line item changes use update_proposal_line. For status changes use propose_send_proposal or cancel_pending_operation.
add_proposal_line
Adds a line item to a draft proposal. Each line links a service (and optionally a frequency) with pricing in integer cents. Only allowed when proposal status is `draft`.
update_proposal_line
Updates a proposal line's price, description, quantity, or display order. Only allowed when the parent proposal is in `draft` status.
remove_proposal_line
Soft-deletes a line from a draft proposal (sets `deleted_at` on the line row). Only allowed when proposal status is `draft`.
record_proposal_acceptance
Records that a proposal has been accepted. Creates a `proposal_acceptances` record (immutable legal evidence) and updates the proposal status to `accepted`. For email-reply acceptance, captures the literal acceptance text as legal evidence.
bulk_archive_proposals
Soft-deletes multiple proposals in a single call. Accepted proposals are IMMUTABLE and are skipped (not archived). Cascade-cancels any associated pending/staged operations for archived proposals.
clone_proposal_with_amendments
Supersedes a sent proposal with an amended copy in one atomic call. Archives the source, creates a new draft with the same client/site, copies all lines (applying optional price/quantity/description/frequency amendments), and links source → new via `superseded_by`.
list_invoices
Returns invoices for the tenant. Filter by client_id, status, since (ISO date). Returns totals as integer cents plus lock_version, is_catch_up_invoice, and client_po_number on every row. By default hides `is_test=true` rows; pass `include_test=true` to surface them.
get_invoice
Returns a single invoice by ID with its lines, totals (integer cents), lock_version, status, ATO fields (supplier/buyer), client_po_number, dates, is_catch_up_invoice flag, and live payment position: paid_cents (Σ non-voided payment allocations − attributed refunds), refunded_cents (refunds prorated by allocation share), credit_applied_cents (Σ applied client credits), and outstanding_cents (total − paid − credit, floored at 0) — one call answers "how much is left to collect?", refund-aware (bug 346589f7). Also returns the EFFECTIVE recipient identity — effective_recipient_abn, effective_recipient_name, recipient_abn_source ('header'|'client') — resolved as invoice.buyer_abn then the client's ABN, mirroring the rendered tax invoice + ATO send-gate, so a recipient-ABN compliance read needs no PDF render (tool upgrade 0d134b9a).
get_payment
Returns a payment by ID with its allocations against invoices (m:n via payment_allocations). Amounts in integer cents. `payment_date` is the real-world bank/cash receipt date (per Finance Engine Principle 3). `reference` is the external payment reference stored at record time (propose_record_payment's `external_reference` param — bank transaction ID, EFT reference, cheque number).
list_payments
Returns payments for the tenant, filtered by invoice_id (via allocations), client_id, since (ISO date). Amounts in integer cents. By default hides `is_test=true` rows.
attest_payment
Record how a payment physically landed: the BANKED portion (will appear on this account's bank feed) and the UNBANKED portion (cash retained, or deposited to another account). The invariant is banked_cents + unbanked_cents == the payment's amount. Example: a $1,320 payment settled $420 by bank transfer and $900 cash → attest banked_cents 42000, unbanked_cents 90000, unbanked_method 'cash'. This lets the $420 bank line reconcile to the banked portion instead of nagging as an underpayment. It is a PROVENANCE SIDECAR — it does NOT change the payment amount, its allocation, or any GST, and executes no money movement (Financial Operations Test: PASS). Idempotent per payment (re-attesting overwrites the split).
get_payment_attestation
Return the provenance attestation for a payment — the banked vs unbanked split (banked_cents, unbanked_cents, unbanked_method, note) recorded by attest_payment — or null if the payment has not been attested (provenance unknown / assumed banked).
list_invoice_state_changes
Returns chronological state-transition rows from `audit_log` for invoices in this tenant (entity_type='invoice', filtered to state-transition verbs: created, send/sent, paid, partially_paid, overdue, void/voided, updated). Optionally filter by invoice_id and since (created_at). Read-only temporal query per Finance Engine Principle 4.
mark_invoice_paid
Marks a client invoice as paid WITHOUT recording an underlying payment row. Manual-override / paper-only reconciliation path. The canonical operator workflow for received money is record_payment (composite, propose+approve gated, records the payment + allocation + transitions invoice in one staged operation). Use this tool only when the payment cannot be captured as a payments row.
create_invoice
Creates a draft invoice for a client with one or more line items. Auto-generates an invoice_number (next sequential via finance.invoicing.numbering_prefix setting), resolves the client PO via the cascade (manual override → first non-null on job_ids), and computes header totals (subtotal/gst/total) from the supplied lines. Default GST treatment per line is 'taxable' — Australian GST-inclusive: gst = round(amount_cents / 11). Emits invoice.created with PO + totals in payload (idempotency key invoice.created:<id>).
update_invoice
Updates the header fields of a draft invoice (subject_line, notes, client_po_number, due_date). Issued invoices are immutable — attempting to edit a sent/overdue/paid/voided invoice returns code:'immutable'. Optimistic-concurrency-gated on expected_lock_version: stale values return code:'stale_version' with the current lock_version so the caller can refresh and retry.
void_invoice
Voids an invoice in draft/sent/overdue/disputed status. REJECTS attempts to void a paid invoice — the correction path for paid is the refund flow (FE-05) + a credit note. Requires a non-empty void_reason and an explicit void_date (Principle 3 — no default to today). Increments lock_version. Emits invoice.voided with the reason in payload.
add_invoice_line
Adds a line item to a draft invoice. Recomputes header totals (subtotal/gst_amount/total) atomically from non-deleted lines. Increments lock_version. Issued invoices are immutable — non-draft attempts return code:'immutable'.
update_invoice_line
Updates an existing line on a draft invoice. Recomputes line GST + header totals atomically. Issued invoices are immutable.
remove_invoice_line
Soft-removes a line from a draft invoice (sets deleted_at). Recomputes header totals. Issued invoices are immutable.
validate_invoice
Runs the locale-specific tax-invoice validator against a draft or sent invoice and returns the structured result (ok + code + reason + carry-through fields). v1 ships the AU ruleset (lock-in B5 + WP-FE-06): supplier ABN required, recipient ABN required when GST > 0 AND total ≥ $1,000 (tenant-configurable threshold), GST sum sanity, all lines have explicit gst_treatment, tax-invoice-label correlate. The jurisdiction defaults to tenant setting `finance.tax.locale` ('AU' fallback). Future jurisdictions plug into the dispatch table without call-site changes.
recalculate_invoice_gst
Recomputes per-line and header GST on a draft invoice from current `gst_treatment` flags. Bumps `lock_version`. Emits `invoice.gst_recalculated`. Lines with `gst_treatment='mixed'` PRESERVE their stored `gst_amount` (the operator-set value) — recalc only rewrites `taxable` (1/11 of inclusive amount) and `gst_free` (zero).
approve_send_invoice
Approves a staged invoice send (leg 3 of 3 in the propose → confirm → approve send chain). Runs the locale-specific ATO validator (per WP-FE-06 + lock-in B5) as a HARD GATE: on validation failure the send is refused with a structured code (`missing_supplier_abn` / `missing_recipient_abn_above_threshold` / etc.) and `invoice.ato_validation_failed` is emitted. On validation pass, the invoice transitions to `sent`, the email is dispatched, and `invoice.sent` fires.
propose_resend_invoice
Stages a re-delivery of an already-sent invoice to all finance contacts (or an explicit override). The invoice must be in `sent`, `overdue`, or `partially_paid` status. Returns a pending_operation_id; the email fires on `confirm_pending_operation` (two-leg propose → confirm — no separate approve, the invoice was operator-approved at original send). Does NOT mutate the invoice (no number/total/status/lock_version change), runs no financial postings, emits no invoice.sent — it re-delivers the existing invoice. Recipients fan out to every site contact flagged receives_invoices (de-duped), falling back to the client billing_email; pass recipient_emails to override with a specific set.
propose_record_payment
Stages a client payment recording for operator approval. Validates date discipline, amount, payment method, invoice state, and external-reference idempotency BEFORE staging. If the gate would block, returns the specific code so the operator can fix first.
approve_record_payment
Approves a staged payment recording and atomically inserts the payment row, allocation, transitions the invoice (to partially_paid or paid), and emits payment.recorded + (conditional) invoice.partially_paid or invoice.paid.
void_payment
Soft-deletes a payment and rolls back the linked invoice's status. Required void_reason + void_date for audit-trail discipline. Use for typo recovery (wrong amount, wrong invoice) or bank reversal (deposit clawed back).
get_invoice_full_history
Returns the complete story of a single invoice: header (with PO + lock_version + status + email_send_status) + state transitions from audit_log + line items + every allocated payment + every refund against those payments + email send_history from email_deliveries + linked contractor payables. Money in integer cents.
manual_resync_invoice_to_xero
Manually mirrors a biloh invoice to Xero as a draft. Recovery path for invoices that failed to mirror automatically (status='failed' in xero_mirror_log). Idempotent — by default returns 'already_synced' if a synced row exists.
preview_late_fees_for_invoice
Dry-runs the late-fee application for an invoice and returns what WOULD happen — same response shape as apply_late_fees_for_invoice but with zero side effects. Useful for previewing the daily-sweep behaviour or confirming a fee amount before pulling the trigger.
apply_late_fees_for_invoice
Applies the owed late fee to an overdue invoice per the resolved config (use get_late_fee_config_for_client to inspect). Creates a NEW draft invoice anchored to the original via late_fee_for_invoice_id. Idempotent per (invoice_id, period_number) — a second call returns already_applied_this_period.
list_late_fee_invoices
Returns every late-fee invoice anchored to a given original invoice, ordered by period_number ASC. Empty list = no late fees have been applied yet to that invoice.
create_payment
Records a payment received against an invoice. `amount_cents` is INTEGER CENTS. Payment does not auto-update invoice status — that must be done separately if the invoice is fully paid. `payment_date` is a real-world financial event date and must be explicit (no NOW() default per Finance Engine Principle 3).
propose_send_invoice
Proposes sending an invoice to the client. Creates an `mcp_pending_operations` row — the send is staged (stored status value 'pending') and MUST be confirmed via `confirm_pending_operation`, then approved via `approve_send_invoice`, before the invoice is actually sent. This three-leg pattern prevents accidental sends. Expiry is tenant-tunable via finance.sends.pending_operation_ttl_minutes (default 60, clamp 5-240).
list_compliance_documents_pending_review
Lists all compliance documents with status 'pending_review' for the tenant — the agent review queue. Results are oldest-first so the automation works FIFO.
cancel_pending_operation
Cancels a pending operation in `pending` or `staged` status. After approve_send_operation has fired, the operation is `approved_and_sent` and cannot be cancelled — that's a separate withdrawal/voiding flow.
approve_send_operation
Approves a staged send operation and immediately sends the email to the recipient with a portal-link CTA. Generic dispatcher — branches on `pending_operation.operation_type` to the right artifact-specific send path (proposal / invoice / contractor_agreement / work_order). The recipient lands in the portal, reviews, and signs / accepts / pays via the configured method.
get_pending_operation
Read-only inspection of a staged operation. Returns the operation's current state, the email subject/body that will be sent, the attachment path, the signing_token, and the related entity details. The canonical "preview before approve" tool.
list_pending_operations
List staged or in-flight operations for the current tenant. Default returns the most recent 20 staged operations. Filter by status (`staged`, `approved_and_sent`, `cancelled`) or entity_type.
list_expired_pending_operations
List pending operations that lapsed past their TTL without being confirmed or approved — i.e. staged sends that silently died. Returns swept rows (status='expired') plus pending rows already past expires_at, newest first, with how long ago each expired. Default window: last 7 days.
confirm_pending_operation
Confirms a previously staged operation (leg 2 of 3 in the propose → confirm → approve send chain). Behaviour depends on operation_type:
get_audit_log
Returns recent audit log entries for the tenant. Supports filtering by actor, entity, action, and date range (time-window via `from_date` / `to_date` ISO params). The canonical audit timeline across the whole tenant.
get_audit_log_for_entity
Returns all audit log entries for a specific entity in chronological order. The complete timeline for one entity — use for self-diagnosis of unexpected state. Pass include_domain_events:true to ALSO return the domain_events emitted for the entity (e.g. job.rescheduled, job.spawned, work_order.accepted) as { event_type, occurred_at, payload_summary } — the per-entity event read that confirms a documented event fired for a non-finance entity.
report_bug_to_biloh
Report, file, submit, log, raise, or flag a bug, defect, or issue with the Biloh platform — the CANONICAL WRITE TOOL for filing a bug report. When your intent is 'report a bug' / 'file a bug', call THIS tool: list_my_reported_issues is only the read-back of what you already filed, and list_platform_bugs is the platform-admin triage view; neither files anything. Bugs are stored at the platform level (cross-tenant, visible to platform admins). Downstream automation may pick up the bug and ship a fix; the calling agent is not notified of resolution from within this tool. INCLUDE A `suggested_fix` ONLY IF YOU'RE CONFIDENT — it's a hypothesis for the receiving agent, not a directive.
request_tool_to_biloh
Request, propose, ask for, or submit a NEW tool to the Biloh platform — the canonical write tool for filing a tool request (list_my_reported_issues is the read-back of what you already filed; it does not file anything). Tool requests are stored at the platform level (cross-tenant). Downstream automation may design and ship the new tool; the calling agent is not notified of completion from within this tool. BE PRECISE about `proposed_name`, `tool_class`, and `workflow_context` — the receiving agent uses these to decide whether to build.
upgrade_tool_to_biloh
Suggest, propose, file, submit, or log an upgrade or improvement to an EXISTING Biloh tool that works correctly but could communicate or structure itself better — the canonical write tool for filing a tool-upgrade suggestion. Upgrades are stored at the platform level (cross-tenant). Downstream automation may pick up the upgrade and ship it; the calling agent is not notified of resolution from within this tool.
render_artifact_pdf
Renders a tenant artefact (proposal, signed agreement, work order, ongoing-series work order, invoice, quote, statement, receipt) as a PDF, stores it in Supabase storage scoped to the tenant, and returns a 1-hour signed download URL. Idempotent — re-call to refresh after data changes. Updates the entity row's `pdf_url` / `signed_pdf_url` field if applicable.
lookup_abn
Looks up an Australian business via the Australian Business Register (ABR). Supports two modes: (1) ABN lookup — provide `abn` to get verified details for a known ABN; (2) Name search — provide `name` (and optionally `state`) to find businesses by name, returns up to 5 matches. Works for businesses, body corporates, sole traders, trusts, and partnerships.
record_compliance_review
Records a reviewing agent's verdict on a compliance document. The platform NEVER calls an LLM — whoever drives the MCP client brings their own model. This tool records the extracted fields and verdict.
upload_compliance_document_from_agent
Uploads a compliance document (insurance certificate, etc.) on behalf of a contractor via the agent chat. Accepts the file as base64, validates integrity, stores it in Supabase Storage, creates a pending_review compliance document row, and syncs contractor denormalised fields. Mirrors the portal upload route exactly.
create_compliance_upload_link
Generates a short-lived, single-use upload link for a contractor's compliance document (insurance certificate, photo, scan). Give this link to the user — they tap it on their phone, pick a photo/scan/PDF, and the browser uploads it natively. This avoids the base64-through-chat corruption that affects large files.
list_frequencies
Returns the frequency catalog (scheduling patterns: weekly, fortnightly, 4-weekly, etc.) for the authenticated user's tenant. Frequencies are referenced by contract_service_lines to determine job recurrence.
record_completion
Records contractor completion of a job: sets `status='completed'`, `completion_submitted_at`, `completion_photos[]`, `completion_notes`. Validates min photos + notes per the tenant's contractor-portal completion requirements.
record_field_triage
Records a field-triage event on a dispatched job — the operator/MCP twin of the contractor portal 'Can't complete' flow. Sets the job's hold_reason from reason_code (+ optional note), auto-reschedules the occurrence (to reschedule_to, or the next available day — the day after the current date, never earlier than tomorrow), mints an 'auto_applied' schedule_change_requests row as the operator-facing record, and emits job.rescheduled + job.schedule_change_requested — i.e. exactly the state transition the portal triage produces.
import_bank_csv
Ingest a CommBank NetBank CSV export into the bank-reconciliation feed (bank_transactions). The CSV is header-less with 4 columns: date (DD/MM/YYYY), signed amount (credit +, debit -), description, running balance. Ingest is IDEMPOTENT — re-importing an overlapping window adds zero duplicates, and two genuinely-distinct same-day/same-payer/same-amount credits (the Intum case) both survive.
list_bank_transactions
Returns ingested bank transactions for the tenant, filtered by match_state (unmatched | suggested | matched | ignored), direction (credit | debit), and a value_date window (since/until ISO dates). Amounts are signed integer cents (positive = money in). By default hides is_test=true rows.
ignore_bank_transaction
Mark a bank transaction as a non-sale (bank interest, an internal transfer, a random deposit or refund) so it leaves the reconciliation worklist. A reason is REQUIRED for the audit trail. This is the ingest-time classification of a DEPOSIT as not-a-sale — distinct from clearing a residual variance at close (that lives on the variance surface).
get_reconciliation_suggestions
Ranks each unmatched incoming bank credit against the tenant's outstanding invoices, returning suggested matches with a deterministic confidence score, the reasons (exact_amount, invoice_ref_in_description, alias_match/client_name_match, date_plausible), and a band (auto_suggest >= 0.9, needs_review >= 0.6). Learned client bank-aliases raise confidence for repeat payers. No LLM — fully auditable.
get_sales_gst_report
The accountant's month-end artifact: the CASH-BASIS Sales & GST report for a period. Returns one row per payment RECEIVED (invoice number, issue date, paid date, client, ex-GST cents, GST cents, total cents), the period totals, and the AU financial-year GST-period label (e.g. 'Q4 (Apr–Jun 2026)'). CASH BASIS means a sale and its GST are recognised when the money LANDS (payment_date in the period), NOT when the invoice was issued — so an invoice raised last month but paid this month appears in THIS month's report. GST per receipt = round(amount_cents / 11) at the AU 10% rate. Omitting the period defaults to the CURRENT calendar month.
get_month_end_status
Returns the month-end CLOSE state for a period plus a LIVE summary. `state` is 'open' (no close exists yet), 'closed' (locked — figures frozen), or 'reopened' (was closed, now editable again). `live` carries the current received / ex-GST / GST / receipt_count / outstanding_ar / awaiting_reconciliation, computed fresh from the ledger. `close` (when present) carries the FROZEN snapshot taken at close plus closed_at and any reopen reason. Comparing live vs the frozen snapshot tells you whether the period drifted after it was closed. Omitting the period defaults to the CURRENT calendar month.
run_month_end_close
Closes (locks) a month-end period. Computes the cash-basis figures for the period and SNAPSHOTS them (received / ex-GST / GST / receipt_count / outstanding_ar) into the period_closes record, marking the period 'closed' and stamping who/when. This is the sign-off step: the frozen snapshot is the official figure for the period even if later activity changes the live numbers. IDEMPOTENT — running it again on the same period updates the same single record (re-snapshots), never creating duplicates. The Financial Operations Test: this changes NO invoice/payment amount or GST value and executes NO payment — it records an attestation that the month is signed off. Omitting the period defaults to the CURRENT calendar month.
reopen_period
Reopens a previously-closed month-end period so its figures can be corrected, then re-closed. Sets the period_closes record to 'reopened' and records a REQUIRED reason plus who/when — the reason is the audit trail for why a signed-off month was touched again. After reopening, make the correction (e.g. record a late payment, confirm a missed deposit) and call run_month_end_close again to re-freeze the snapshot. The Financial Operations Test: changes NO invoice/payment amount or GST value and executes NO payment — it flips a sign-off flag with an audited reason.
unmatch_reconciliation
Undo a reconciliation: reverse a deposit that was matched to an invoice. It VOIDS the payment the match created (which unwinds the payment's allocation and recomputes the invoice off 'paid'), then frees the deposit back to 'unmatched' so it can be re-reconciled. A reason is REQUIRED (audit trail); nothing is hard-deleted. The Financial Operations Test: this reverses a previously-recorded payment via the audited void gateway — it changes no GST/amount computation and moves no money.
get_payable
Returns a contractor payable by ID with its linked invoice (id+status) for Layer-2 cash-flow visibility. Amount in integer cents. `payable_paid_date` is the real-world payment date (Finance Engine Principle 3).
list_payables
Returns contractor payables for the tenant, filtered by contractor_id / status / since. Amounts in integer cents. Default hides `is_test=true`.
get_credit
Returns a client credit by ID. `source` distinguishes overpayment | prepayment | adjustment | refund_reversal (lock-in B2 — prepayments arrive before any invoice). Amount in integer cents.
list_credits
Returns client credits for the tenant, filtered by client_id, source, since. Amounts in integer cents. Default hides `is_test=true`.
get_refund
Returns a refund by ID with its linked payment summary (lock-in B8.1 linkage). `refund_date` is the real-world execution date; `refund_reason` is required non-empty. Amount in integer cents. Voided refunds ARE returned, flagged `voided:true` (deleted_at = the void timestamp), mirroring get_payment / get_invoice_full_history; a `Refund not found` error means the id is genuinely unknown, not voided.
list_refunds
Returns refunds for the tenant, filtered by payment_id, since (refund_date). Amounts in integer cents. Default hides `is_test=true`.
get_statement
Returns a statement by ID with its items (invoice + payment line refs). `bundle_invoices` flag indicates whether the eventual email attaches per-invoice PDFs. Amounts in integer cents.
list_statements
Returns statements for the tenant, filtered by client_id and since (period_end). Amounts in integer cents. Default hides `is_test=true`.
get_finance_settings
Returns the tenant's finance configuration from two sources: (1) registry-backed settings (category=finance) with current values, defaults, and tier status; (2) tenant-level finance columns (payment terms, GST rate, currency, overdue interest, payment instructions, bank details) and business profile identity (name, trading name, ABN, address, phone, email). Use update_tenant_finance_profile to write the tenant-level columns.
update_tenant_finance_profile
Updates tenant-level finance columns and/or business profile identity. These fields live directly on the tenants table (not the settings registry). This is the MCP counterpart to the Financial Defaults + Business Profile UI pages.
get_payable_release_status
Returns the Layer 2 cash flow protection gate state for a contractor payable: whether it is releasable and why/why-not, plus the linked client invoice's status, PO, and paid date.
propose_release_payable
Stages a contractor payable release for operator approval. Validates that the Layer 2 gate (linked client invoice must be paid) is passable BEFORE staging — if blocked, returns the specific gate code so the operator knows what to fix first.
approve_release_payable
Approves a staged contractor payable release and atomically transitions the payable to status='released'. Emits payable.released with idempotency key. Re-validates the Layer 2 gate (invoice still paid, payable still releasable) defensively.
void_payable
Soft-deletes a non-released contractor payable (status → 'void', deleted_at set). Required void_reason + void_date for audit-trail discipline. The symmetric partner to void_payment for the contractor money-out side.
propose_record_refund
Stages a client refund recording for operator approval. Refund anchors to a payment (lock-in B8.1) and requires a non-empty refund_reason (B8.2). Validates date discipline, amount, method, payment state, and refundable-balance BEFORE staging. If the gate would block, returns the specific code so the operator can fix first.
approve_record_refund
Approves a staged refund recording and atomically inserts the refund row, recomputes the linked invoice's status (paid → partially_paid or sent/overdue depending on effective allocation), handles linked contractor payables (pending+approved flips back to pending; released emits payable_unwind_required signal), and emits refund.recorded + (conditional) invoice.partially_refunded or invoice.refunded + (conditional) payable_unwind_required.
void_refund
Soft-deletes a refund and rolls the linked invoice's status forward. Required void_reason + void_date for audit-trail discipline. Use for typo recovery (wrong amount, wrong refund) or refund returned to tenant (customer changed mind, refund cheque bounced).
create_credit_note
Creates a DRAFT credit note against an existing sent invoice. Credit notes anchor to invoices (not payments — refunds do that), reduce the invoice's effective settled amount on send, and carry the originating invoice's PO + ATO fields automatically.
update_credit_note_line
Patches a credit-note line. Only valid while the credit note is in draft. Recomputes the line's GST + the header totals automatically.
approve_send_credit_note
Approves a staged credit-note send. Runs the locale-specific ATO validator (per FE-06 reuse — same rules for credit notes as invoices) as a HARD GATE. On refusal returns a structured failure (ok:false + code) without sending. On pass: transitions credit_note to sent, adjusts the originating invoice's effective settled amount, optionally mints a client_credits row for overflow, dispatches the branded email, emits credit_note.sent + credit_note.applied_to_invoice.
propose_resend_credit_note
Stages a re-delivery of an already-sent credit note to all receives_invoices contacts (credit notes are invoice-family) or an explicit override. The credit note must be in `sent` status. The email fires on `confirm_pending_operation` (two-leg propose → confirm). PURE re-delivery: no credit-note mutation, no status change, no re-application to the invoice (the original send already did that). Pass recipient_emails to override the fan-out.
get_credit_note
Returns a credit note by ID with its lines + linked invoice projection (status, total, effective settled).
list_credit_notes
Lists credit notes filtered by client / invoice / status / date range. Defaults to excluding test rows.
void_credit_note
Soft-voids a credit note. If the credit note was previously sent, rolls the originating invoice forward (recomputes effective settled). Emits credit_note.voided + (when sent) invoice.credit_unwound.
create_payable
Manually creates a contractor payable row in 'pending' status. CATCH-UP and OPERATOR-OVERRIDE path — the canonical flow is the job.completed handler which auto-creates payables. The release-side gate (Layer 2 cash flow protection — payable cannot release until linked invoice is paid) still applies.
list_statement_send_history
Lists statement send and void events for the tenant, sourced from audit_log. Filter by client_id and since (ISO date). Returns chronological actor + action + statement_id + metadata.
propose_send_statement
Proposes sending a statement to a client for a period. Creates a draft `statements` row capturing opening/closing balances + in-period totals, then stages an `mcp_pending_operations` row. Awaits `confirm_pending_operation` (stages for operator approval) then `approve_send_statement` (sends the email).
propose_resend_statement
Stages a re-delivery of an already-sent statement to all receives_statements contacts (or an explicit override). The statement must be in `sent` status. The email fires on `confirm_pending_operation` (two-leg propose → confirm). PURE re-delivery: no statement mutation, no status change. Recipients fan out to every receives_statements contact (de-duped), falling back to client billing_email; pass recipient_emails to override.
void_statement
Soft-marks a statement voided. Operator-attested error recovery (sent the wrong period, wrong client, etc.). Required `void_reason` (non-empty) + `void_date` (ISO YYYY-MM-DD; no default, Principle 3). Increments lock_version, sets status=voided + voided_at, emits statement.voided.
get_period_reconciliation_view
Returns the two-sided (AR + AP) basis-aware reconciliation view for a period. AR side: invoices broken by lifecycle state (sent/overdue/paid). AP side: payables broken by lifecycle state (pending/released/paid). NET position = AR total − AP total. Accounting basis (cash/accrual) selects the date anchor: cash = money-movement dates (payment_date / payable_paid_date); accrual = earned/incurred dates (issued_at / created_at). The basis flips BOTH sides identically. Also includes legacy fields (billed_cents, paid_cents, outstanding_cents, refunded_cents), corrections, unhealthy_flags, po_breakdown. Money in integer cents. REFUND BASIS: paid_cents (and ar.paid) are GROSS receipts here — paid_cents_is_refund_net is false — so net cash = paid_cents − refunded_cents and net_cents (AR − AP) is gross-of-refunds; subtract refunded_cents exactly ONCE for the C09 'received − refunded == net cash' figure (refunded_cents is informational disclosure, do not double-subtract).
find_orphaned_or_unusual_events
Returns the operator-alert anomaly stream — six signal types surfaced by the temporal query layer:
get_xero_connection_status
Returns the Xero connector state for the caller's tenant: whether the integration is enabled (tenant setting), whether an active OAuth connection exists, and the most recent error if any.
get_xero_authorize_url
Returns a one-shot Xero OAuth authorize URL for the caller's tenant. The agent surfaces the URL to the operator as a clickable link; only a browser can complete the OAuth handshake.
list_xero_mirror_log
Returns Xero mirror attempts (synced, failed, skipped) for the caller's tenant, ordered newest-first.
get_xero_mirror_for_entity
Returns the chronological Xero mirror history for a single invoice or payable, plus the current synced Xero entity ID if one exists.
manual_resync_payable_to_xero
Manually mirrors a biloh contractor payable to Xero as a Bill draft. Recovery path for payables that failed to mirror automatically.
disconnect_xero
Disconnects the tenant's Xero integration. Idempotent — calling on an already-disconnected tenant returns already_disconnected:true. Best-effort Xero-side revoke; local connection row is always flipped to 'disconnected'.
get_stripe_connection_status
Returns the Stripe Connect Express state for the caller's tenant: whether the integration is enabled (tenant setting), whether an active connection exists, and the capability flags from Stripe (charges_enabled, payouts_enabled, details_submitted).
list_stripe_webhook_events
Lists Stripe webhook events for THIS tenant's CONNECTED (Till-2 / Connect) Stripe account only, sorted newest-first — e.g. payment_intent.succeeded / checkout.session.completed for the tenant's own client-invoice payments. Filter by status, event_type, or date range.
start_stripe_connect_onboarding
Returns a Stripe Connect OAuth URL the operator opens in a browser to connect their existing Stripe account. Uses Standard accounts via OAuth — the tenant connects their own Stripe account (with their own bank details, KYC, and dashboard).
generate_pay_now_link
Creates a Stripe Checkout Session for an unpaid invoice on the tenant's connected Stripe Express account. Returns the Stripe-hosted URL the customer opens to pay by card. Payment is recorded automatically via the Stripe webhook on payment_intent.succeeded.
disconnect_stripe
Disconnects the tenant from Stripe Connect. Marks the local connection as disconnected; does NOT delete the Stripe Express account on Stripe's side (tenant retains ownership and can re-connect at any time).
get_email_delivery_status
Returns the email delivery status for an outbound communication: pending/sent/delivered/bounced/soft_bounced/complained, the chronological Resend webhook event timeline, and (when the entity is a supported FE-09 type) the originating row's email_send_status flags. Pass EITHER message_id (Resend provider id) OR (entity_type, entity_id).
list_recent_bounces
Returns recent bounce and (optionally) complaint events for the tenant — one row per outbound email that failed. Includes recipient, bounce reason from Resend's payload, originating entity (invoice/statement/refund/payment_receipt), and the provider message id. Default time window: last 24 hours.
request_schedule_change
Contractor-side request to reschedule a job. Gated by the `scheduling.contractor_portal.can_change_schedule` registry setting (no | request_only | yes), falling back to the legacy `tenants.scheduling_settings.contractor_can_change_schedule` JSONB only when the registry key is unset. Records a pending request; admin must approve. Never moves the job directly when the setting is `no` or `request_only`.
list_schedule_change_requests
Lists schedule change requests (client portal, contractor portal, field triage, request_schedule_change) with status, requested/effective dates, and linked job context. Default filter is status='pending' — the operator's working set of requests awaiting approve/decline. The returned schedule_change_request_id is the input to approve_schedule_change_request / decline_schedule_change_request.
approve_schedule_change_request
Operator approves a pending schedule change request: atomically moves the job to the requested date (or `override_date`), marks the request 'approved' with actioned_at/actioned_by, links the audit trail to the request id, and emits job.rescheduled + schedule_change_request.actioned. Accepts schedule_change_request_id, or job_id when exactly ONE pending request exists for the job (multiple pending requests fail closed with the candidate list). Approval counts as a client-initiated reschedule: it increments the CSL's client_reschedule_count and is blocked by max_reschedules_per_quarter (architect-ratified 2026-06-06) — use reschedule_job with origin=admin to move the job anyway.
decline_schedule_change_request
Operator declines a pending schedule change request: marks it 'declined' with the reason in actioned_note, sets actioned_at/actioned_by, and emits schedule_change_request.actioned. The job is NOT touched. Accepts schedule_change_request_id, or job_id when exactly ONE pending request exists for the job (multiple pending requests fail closed with the candidate list).
get_dispatcher_view
Composite — returns the canonical ScheduleView for the operator dispatcher: jobs bucketed into unassigned/today/upcoming/completed/cancelled, contractors-on-leave list, and aggregate stats (total / assigned / unassigned / completed). Each job includes lock_version for OCC mutations. Honours the same tenant scope as list_jobs and list_unassigned_jobs. For busy tenants / large date windows use `summary: true` (each bucketed job carries only {id, scheduled_date, status, site_id, site_name, contractor_id} plus stats) or `count_only: true` (per-bucket counts + stats, no rows) to stay under the response size limit.
regenerate_csl_schedule
Permanently changes a contract service line's frequency (cadence) and/or recurrence inputs (day_of_week, week_of_month, anchor_date), then regenerates its future undispatched jobs. Two-phase: call with confirm=false for a preview of what would change, then confirm=true to apply. Alias: change_csl_schedule.
list_my_reported_issues
Read back the bugs, tool requests, and tool upgrades THIS tenant has filed via report_bug_to_biloh / request_tool_to_biloh / upgrade_tool_to_biloh, with their current triage status. Tenant-scoped — returns only your own reports, never the cross-tenant platform queue.
send_operator_report_email
Send a report email from the platform to the CONFIGURED operator address. The recipient is read ONLY from the tenant setting `notifications.operator_report_email` — you cannot pass an arbitrary recipient (open-relay guard). The sender is pinned to the Resend-verified gwcpropertyservices.com.au domain.
list_settings
Lists platform settings registered for this tenant with current values, default values, tier status, and metadata. Filterable by category, scope, and full-text search across keywords + descriptions.
get_setting
Reads one platform setting's current value + metadata for this tenant. Returns the registered definition, the default value, the `previous_value` (if any), `tier_status` (enabled or upgrade_required), and the last-change authorship.
update_setting
Updates one platform setting's value for this tenant. Writes audit_log + emits `settings.updated` event. Reversible via revert_setting (architect persona). Supports optimistic-concurrency via `lock_version`.
list_setting_history
Returns the chronological audit history for one platform setting: every `setting.changed` and `setting.reverted` event with actor, timestamp, before/after values, and reason. Limit defaults to 20, max 100.
configure_setting
Composite (front-of-house). Set a platform setting to a new value. Audit-logged + reversible via revert_setting. Use this as the default for natural-language settings changes; use update_setting directly when you need to pass `lock_version` for optimistic-concurrency control.
get_session_context
⚡ READ ME FIRST ⚡ — Returns the full operational context for this tenant: business identity, your persona + role, key conventions, current state snapshot, and pointers to deeper reference. Call this before any other tool when you first connect. This is mandatory grounding for acting intelligently in this tenant.
search_tools
Find the right MCP tool by intent — returns a ranked, persona-scoped shortlist (name + one-line summary + category) so you do not have to scan the full catalogue or guess a name.
list_marketing_content
Lists the tenant's marketing-site content for one kind (category | industry | case_study | page | help_article | testimonial | site_config). Returns published rows by default; pass include_unpublished:true to see drafts.
upsert_marketing_content
Creates or updates one marketing-site content row (kinds: category | industry | case_study | page | help_article | testimonial | site_config). Insert vs update is resolved by payload.id, else by the kind's slug within the tenant. site_config always updates the tenant's single row; its `content` object is MERGED key-by-key (existing keys survive).
set_marketing_content_published
Publishes or unpublishes one marketing-site content row (kinds with a published flag: category | industry | case_study | page | testimonial).
create_marketing_upload_link
Generates a short-lived, SINGLE-USE browser upload link for a website photo. Give the link to the user — they tap it on their phone, pick the photo, add a caption, and it lands in the website media library. Then use list_marketing_assets + attach_marketing_asset to place it on the site.
list_marketing_assets
Lists the tenant's website photo library (tenant_media_assets), newest first. Each item carries public_url (what attach_marketing_asset places on the site), kind, alt_text, and upload time.
attach_marketing_asset
Places a library photo onto the website by writing its public URL onto a marketing content row.
get_branding_kit
Returns the tenant's branding kit: every asset slot (logo_full, logo_white, logo_icon, favicon, email_header, pdf_header, social_banner, letterhead) with its status, required format/dimensions, and public URL where uploaded — plus brand colors, fonts, and completion percentage. This kit feeds invoices/PDFs, the app icon, the style guide, and the public website.
create_branding_upload_link
Generates a short-lived, SINGLE-USE browser upload link that lands a file directly in a branding-kit SLOT (replacing the current asset at its stable path). Because every surface reads the kit, one upload updates invoices, the app icon, the style guide, and the website together.
get_my_subscription
Returns the current tenant's subscription status, plan, trial countdown, billing details, and the active plan's catalog price — price_monthly_cents, price_annual_cents, currency (AUD), tax_inclusive (true per ADR-0013), read verbatim from the plan catalog so a tenant can verify displayed price == charged price. Tenant-scoped — each tenant sees only their own subscription. It also returns the cancel lifecycle — cancel_at_period_end (true once a cancel-at-period-end is scheduled; the subscription stays active until current_period_end) and canceled_at (the cancellation timestamp, null when not cancelling) — so a tenant agent can read back a pending cancellation via MCP, not only via the Stripe portal.
get_my_entitlements
Returns the calling tenant's EFFECTIVE feature entitlements resolved from its active subscription plan: the per-plan feature flags (custom_branding, api_access, priority_support, white_label, ai_enabled) and numeric limits (max_users, max_clients, max_sites, feature_usage_limit) read VERBATIM from the subscription_plans catalog row for the tenant's current plan_code, plus plan is_active and the tenant's feature-gate tier (tenants.subscription_tier). The read-side complement to get_my_subscription that makes 'displayed == entitled' verifiable from the tenant/MCP surface. Tenant-scoped — each tenant sees only its own entitlements.
set_tenant_plan_price_override
Test-gated, tenant-scoped subscription-plan price override for autonomous, fully-reversible price-change testing inside a dogfood tenant. Sets (or clears with price_cents:null) a GST-inclusive override keyed on (this tenant, plan_code, billing_interval) that changes ONLY this tenant's Till-1 Checkout charge — the shared subscription_plans catalog and all other tenants are untouched.
set_tenant_feature_flag_override
Test-gated, tenant-scoped entitlement feature-flag override for autonomous, fully-reversible entitlement-gating testing inside a dogfood tenant. Sets (value:true/false) or clears (value:null → fall back to the plan default) a per-tenant override of ONE entitlement flag (custom_branding, api_access, priority_support, white_label, ai_enabled) that overlays the shared subscription_plans catalog ONLY for THIS tenant. get_my_entitlements reflects the overlay, so 'displayed == entitled' is verifiable from the MCP surface without platform-admin browser auth and without touching the shared catalog or any other tenant.
create_billing_portal_session
Creates a Stripe Billing Portal session for the current tenant. Returns a URL to redirect the operator to manage their subscription (update payment method, switch plan, cancel). Billing-exempt — works for any subscription status.
get_compliance_document_for_review
Returns a compliance document's metadata, readable content (text and/or images delivered in-band), and a short-lived signed URL (15 min). The reviewing agent can read the document directly from this tool's response — no URL fetch required.
propose_send_credit_note
Stages a credit-note send for operator approval. First leg of the three-leg send chain (propose → confirm → approve). Resolves the recipient email from the linked client's billing_email if not supplied. The ATO validation gate runs in approve_send_credit_note, NOT here.
approve_send_statement
Approves a staged statement send and dispatches the email to the client immediately. The statement transitions from draft → sent, message_id + recipient_email + sent_at are persisted, statement.sent is emitted.
mcp_health
Health check tool — returns auth context, MCP status, server version, tool count, and `tool_names`. Compare `tool_names` to your reachable tools — any name in the server list that you cannot call is a session-staleness or Cowork-side discoverability gap. Restart the Cowork session to refresh.
mcp_session_diagnostic
Read-only session diagnostic — returns server version, auth context, full tool list with categories and personas, and upstream constraint documentation.
Biloh MCP uses Bearer-token authentication. Tokens are tenant-scoped Personal Access Tokens issued by your administrator. Tokens carry an operator persona and respect the same RLS rules as the in-app user interface — a token cannot see or write data outside its tenant.
Self-service PAT issuance is under active development. Until that lands, request a token through your tenant's administrator surface or contact hello@biloh.com.au.
Start a free trial — your MCP server endpoint is ready on day one.