MCP Integration

Drive Biloh from any AI assistant

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

371 operator tools

Connect from your client

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"}'

Tool catalogue

Auto-generated from the live MCP registry. Operator-persona tools shown; architect and platform-internal tools are not listed. 371 tools across 11 categories.

Clients & Sites (28)
  • 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.

  • set_site_access_notes_by_service

    Sets (or clears) the per-service access-notes section for a site, keyed by service_id.

  • 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

    One-tool client onboarding: creates a client, sites, billing contacts, and recurring service lines in one call. Two-phase: confirm=false PREVIEWS (zero writes) with per-line schedule echo, weekday validation, fact echo, and duplicate-client detection; confirm=true creates everything. Sites MUST have a real address (this composite never creates 'Address pending' sites). Lines reference sites by index (site_ref=0 for the first site). Optional align_with_line_ref phase-locks one line's anchor to another's (e.g. inside cleaning on the same day as outside).

  • 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_money_story

    Client payment-story composite: fuzzy-resolves a client, sweeps bank_transactions credits for payments that look like theirs (search terms derived from legal/trading name, site-contact names, and any payer_aliases you supply), classifies the payment cadence (weekly | fortnightly | monthly | bulk | irregular), and returns open invoices side-by-side. Amounts are integer cents.

  • void_client_credit

    Soft-voids a client credit, reversing ONLY its unconsumed remainder and removing that from the client's credit-on-account balance. A fully-consumed credit is refused (issue a credit note / adjustment instead — applied discounts are never clawed back); a partially-consumed credit voids the remaining balance and reports the consumed vs voided split. Emits client_credit.voided.

  • 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.

Contractors (30)
  • 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, newest first, with their current status (draft / staged_for_send / sent / accepted / declined). Each row carries the WP-SMA-TERMS-TRUTHFUL lifecycle fields: `supersedes_agreement_id` (the re-issue chain — a declined agreement is never overwritten, its amended successor points back at it), `responded_at` + `response_method`, and for declines the structured `declined_reasons` + `declined_note` (the contractor's own words). `terms_schedule_snapshot` carries the commercial terms frozen at send (payment mode/trigger/days, completion evidence, insurance minimum, termination notice) — read it to see exactly what that agreement asserts.

  • list_contractor_quotes

    Returns contractor quotes — prices contractors have offered (or been asked for) on specific site/service work. Each row carries the amount AS WRITTEN plus its GST treatment, the canonical ex-GST figure the engine pays from, lifecycle status, and a `note` stating what happens next: a `submitted` quote is priced into work by accept_contractor_quote with YOUR client price — never ask the operator to restate an amount already on a recorded quote.

  • accept_contractor_quote

    Accepts a submitted contractor quote and prices work from it in one call. The quote's ex-GST amount becomes the CONTRACTOR rate; the CLIENT rate is YOUR decision — client_rate_cents states it outright, margin_percent_bps derives it from the quote for this acceptance, and omitting both falls back to the contractor's standing hidden_margin default. Target either an EXISTING job (job_id — e.g. a rate-null ad-hoc visit dispatch is refusing) or a NEW ad-hoc job (scheduled_date; site/service come from the quote). The margin arithmetic in the response is OPERATOR-SIDE ONLY and never reaches a contractor surface.

  • decline_contractor_quote

    Declines a contractor quote with a recorded reason. The quote keeps its history (never deleted); the contractor sees the outcome in their portal via an in-app notice. No email is sent — passing on a price is the operator's conversation to have in their own words.

  • list_contractor_compliance_documents

    Returns compliance documents (insurance certificates, ABN verifications, workers comp, etc.) for a contractor with their verification status and expiry dates.

  • get_contractor_compliance_summary

    Returns one contractor's compliance state for EVERY credential in the platform registry (insurance, licences, cards, registrations) with the resolved state and expiry date for each. States: none | pending_review | rejected | expired | expiring | current — the same lifecycle the assignment gate reads.

  • 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.

  • configure_contractor_engagement

    Composite (front-of-house). One call to set — and read back — everything that shapes ONE contractor's experience: their rate model, whether they see their earnings and payable status on the portal, whether they can move their own job dates, payment terms, completion requirements, and how Work Orders are sent for a recurring client. Composes EXISTING writes only (update_contractor + configure_setting) so there is one right place for each knob.

  • 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

    Records a contractor's quote — the price THEY get paid for specific work, exactly as they gave it (PDF, email, verbal, SMS). Capture the amount AS WRITTEN plus whether it was GST-inclusive; the canonical ex-GST figure is derived and stored. Recording an amount marks the quote `submitted`; the loop then closes with accept_contractor_quote, which takes YOUR client price and turns the quote into priced work — never ask the operator to restate numbers already recorded here.

  • 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.

  • get_contractor_queue

    Returns the contractor's pending work-order queue: every work order in `sent` or `requires_reacceptance` status assigned to the given contractor, enriched with site name, service name, scope, time-in-pending, and signing token. Excludes work orders for archived (soft-deleted) clients.

  • record_contractor_bill

    Records a contractor's bill (tax invoice) as a payable. The BACKFILL / single-shot door — for the historical paper pile where NO accrual exists yet. For an ongoing multi-job contractor invoice claiming EXISTING payables, use record_bill (WP-CONTRACTOR-BILLS) instead — this tool MINTS a payable and would double-count. Bills land as payable rows (D2, superseded for ongoing work — see record_bill). GUARDED: refuses with code open_accruals_exist when the contractor already holds open job-pipeline accruals. GST is STORED as printed on the paper, never derived (D3). Can record the bill as already-paid (legacy backfill, external payment, bank feed) or as unpaid (approved, awaiting payment).

  • get_contractor_bills

    Returns all bills (payable rows with bill metadata) for a contractor, optionally filtered by period. Each row includes bill_number, bill_date, amount, GST, status, paid_via, and document attachment status.

  • get_contractor_finance_view

    Full financial view for a contractor: summary (owed now, paid this AU FY, last paid date, bill count), 12-month money-out chart data, and the bills ledger with status + evidence. This is the agent's version of the contractor profile financial tab.

  • 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). Every entry carries `display_name` (trading-name-first, tunable via the `directory.contractor_display_name` setting) alongside the raw `business_name`. Excludes test rows (is_test=true) from both `candidates` and `blocked` by default — pass include_test:true to include them. Every entry — candidate AND blocked — also carries `prospective_rate`: what that contractor would be paid on THIS job, resolved from their own rate model against the job's client rate ({ derivable, rate_cents, model, bps, source, margin_disclosed, reason, note }). A blocked contractor's cost is still a fact. `derivable: false` names the missing input rather than guessing. The job's `client_rate_cents` is returned alongside so margin is one subtraction. MVP returns binary pass/fail with no ranking; v2 adds geographic proximity + workload scoring (deferred).

  • preview_send_contractor_agreement

    Previews EXACTLY what a Subcontractor Master Agreement send will contain — the email subject and body as they will be sent, the Schedule 1 clauses the contractor will sign (payment, completion evidence, insurance minimum, termination), and a readiness assessment of the contractor's setup. Read-only: stages nothing, sends nothing.

  • record_contractor_agreement_response

    Records a contractor's real-world response to a sent Subcontractor Master Agreement — accepted or declined — with HOW it arrived (email reply, phone, in person) and, for declines, WHICH clauses they objected to as structured reasons plus their own words. Transitions the agreement (sent/staged_for_send → declined or accepted), syncs the contractor's sma_status ('declined' or 'signed'), expires the signing token on a decline so the refused instrument is no longer signable, emits `contractor_agreement.responded`, and writes the audit row.

Contracts & Services (21)
  • 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. Each row carries `audience` (residential|commercial|both — MATCH THIS to the job's setting when picking a service; never use a residential service for commercial work) and `public_summary` (the customer-facing pitch voice).

  • suggest_service_for_job

    Given a free-text job description (and optionally client_type and a category hint), returns the tenant's catalog services ranked by fit — each with a short legal_scope summary, a deterministic fit score, why it fits, and caveats naming any scope mismatches — plus a list of near-duplicate catalog entries. Lets an agent pick the correct service without reading every legal_scope by hand, and avoids printing the wrong Scope of Services on a client PDF. Ranking is a PURE, DETERMINISTIC token-overlap function — the tool never calls an LLM; the calling agent is the LLM that makes the final pick from the ranked candidates.

  • list_service_credential_requirements

    Reads the credential requirements configured for services — the missing counterpart to set_service_credential_requirement, and the way to answer 'can this contractor take this work?' BEFORE creating a job or recording a verdict.

  • 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. contractor_name is the display name resolved trading-name-first (shared with the operator UI, tunable per tenant via directory.contractor_display_name), not the bare business_name. Full rows also carry the test-data flags is_test + is_test_preserved. For large result sets pass summary:true (the exact lean key set: id, client_id, client_name, site_name, service_name, human_description, frequency_name, client_rate, contractor_id, is_active) or count_only:true (just { count_only, count }, no rows) — mirrors list_jobs.

  • 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). Also returns contractor_name (display name resolved trading-name-first, shared with the operator UI and list_contract_service_lines) and the test-data flags is_test + is_test_preserved.

  • 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.

  • set_service_credential_requirement

    Declares (or retires) a credential that contractors MUST hold — verified and unexpired — before they can be assigned to a service. `required: true` adds the requirement; `required: false` retires it. Idempotent: re-setting the same value is a no-op, and a retired requirement can be revived without losing history.

  • 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.

  • add_adhoc_service

    Adds an ad-hoc service to a client — creates the job, bills it into the client's normal invoicing cadence, and creates the contractor payable if applicable. One composite call replaces the manual sequence of create_job + set_client_invoicing_cadence + create_invoice + create_payable.

  • list_service_requests

    Lists inbound service requests — what CLIENTS have asked for through their portal, before any proposal exists. This is the intake queue: the front door of the pipeline.

  • get_service_request

    Full detail for ONE inbound service request, plus the triage actions currently available on it.

  • triage_service_request

    Marks an inbound service request as UNDER REVIEW — the operator has seen it and is working on it. The client sees the status move in their portal, which is the whole point: it is the difference between 'they got it' and silence.

  • decline_service_request

    Declines an inbound service request with a reason the CLIENT WILL READ in their portal. Write it as you would say it to them — 'We don't service that area' is fine; 'n/a' is not.

  • create_proposal_from_service_request

    Turns an inbound service request into a DRAFT proposal and links the two — the intake-to-quote hop. The request moves to `quoted`; the proposal inherits the client, the site (when the request names one), the client PO, and the request's description as notes.

  • 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.

Jobs (22)
  • 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). Full rows also carry `contractor_rate_resolved` — READ THAT, not the bare `contractor_rate` column, for what the contractor actually gets paid: the payable resolves the contract service line's rate FIRST and only falls back to the job's. `diverges: true` means the two stored columns disagree and the LINE is winning — informational, not a stale rate to repair. `committed: false` means the amount is still a projection. Where NEITHER column carries a rate, the holder's own rate model derives one and `derived: true` says so — `cents: null` then means it genuinely cannot be stated, and `reason` names the missing input. `summary: true` keeps `contractor_rate_resolved` as just `{cents, source}`; `count_only: true` returns no rows at all.

  • 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). The suggested_invoice_line is self-describing: it echoes amount_basis:'gst_exclusive' and the source contract_service_line_id (null when billed from a direct rate), so you can spread it straight into add_invoice_line/create_invoice lines[] without re-deriving the basis from this prose.

  • 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`.

  • unassign_job

    Removes the assigned contractor from a job and returns it to the unassigned scheduled pool (the inverse of assign_job). Emits `job.unassigned`.

  • uncomplete_job

    Reverts a completion — the undo for marking a job done (Housecall-Pro 'Unfinish'). Returns the job to 'dispatched' and clears the completion timestamps. Emits `job.completion_reverted`.

  • hold_job

    Parks a job on hold with a reason (the visit is paused, not cancelled). Emits `job.held`.

  • resume_job

    Resumes a job that is on hold, returning it to the 'scheduled' pool so it can be re-dispatched. Clears the hold reason. Emits `job.resumed`.

  • do_job_myself

    Self-delivery composite (WP-SCHED-GOLD-2): assign a scheduled job to the tenant's OWN internal crew and dispatch it, in ONE call. The operator is doing the work themselves rather than subbing it out. Emits one `job.assigned` + one `job.dispatched`.

  • 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 (defaults anchor to TENANT-TIMEZONE today, never UTC — WP-SCHED-GOLD-0). 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.

  • repair_orphaned_job_assignments

    One-tap remediation sweep for jobs orphaned by the pre-fix CSL-respawn path (bug 0f4bb3e2 / e66914a0). Restores each recurring line's contractor onto its scheduled/declined jobs that lost their assignment, via the single assign write-path (compliance gate + CSL-rate inheritance). Idempotent: re-running once the orphans are repaired is a no-op.

  • 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.

  • run_missed_job_detection

    On-demand preview and execution of the missed-job detection sweep — the pass that transitions past-due, non-terminal jobs to status 'missed' and emits job.missed. Two-phase: call with dry_run=true (default) to see exactly which jobs WOULD be flagged, then dry_run=false with the returned confirm_token to flag them.

Work Orders (7)
  • 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.

  • get_work_order_aging

    Returns pending work orders sorted by age (oldest first), with time-in-pending computed from `sent_at`. Filters by status and/or contractor. Excludes work orders for archived clients. Use to identify stale work orders that need follow-up.

  • 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. The response surfaces `recipients` (the full set the send fanned out to — every receives_work_orders contact, de-duped) and `recipient_count` alongside the back-compat scalar `recipient_email` (= recipients[0]), so you can confirm the fan-out from the tool output instead of an inbox check.

  • 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 AND flows onto its jobs (flowSeriesAcceptanceOntoJobs): every future scheduled job on the CSL is assigned to the accepting contractor and transitioned to `dispatched` (jobs already assigned to a DIFFERENT contractor are left alone), the spawner mints future occurrences pre-assigned + dispatched while the acceptance stays live, and the forward-view snapshot is stored.

  • 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). The response also surfaces `recipients` (the full resolved fan-out set — every contractor contact flagged receives_work_orders, de-duped) and `recipient_count`, alongside the back-compat scalar `recipient_email` (= recipients[0]); pass an explicit recipient_email to collapse the set to one address. Use these to confirm the send will reach every intended contact without an inbox check. 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.

Proposals (17)
  • 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 their resolved cadence), 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), updates the proposal status to `accepted`, and runs the SAME activation cascade as the portal accept path: client lead→active, contract service lines stood up from the proposal's frequency AND cadence lines (line facts carried), recurrence rules materialised, and look-ahead jobs spawned synchronously. For email-reply acceptance, captures the literal acceptance text as legal evidence.

  • decline_proposal

    Records that the client DECLINED a sent proposal (status `sent` → `declined`), closing it out of the live pipeline honestly instead of leaving it dangling as sent forever. The proposal is retained (not archived) as pipeline history; its accept link stops rendering a signable agreement.

  • 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/CADENCE amendments — a mid-flight cadence change is ONE call via line_amendments[].new_cadence), and links source → new via `superseded_by`.

  • get_amendment_proposal_history

    Returns the FULL history of a single amendment proposal: the proposal row itself, the domain events that raised and acted on it, and the audit-log narrative — merged into one chronological timeline.

  • list_amendment_proposals

    Lists amendment proposals for the tenant, with optional filters by status, direction, and CSL.

  • action_amendment_proposal

    Accept or decline an amendment proposal. Composite operator action for the negotiation surface.

Invoices (29)
  • 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. Each row includes `reference` (the external payment reference stored at record time — bank transaction ID / EFT reference / cheque number), matching get_payment's shape so reconciliation reads it directly without an N+1 get_payment lookup.

  • 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 by recording a SYNTHETIC reconciling payment row (source='manual_mark_paid', payment_method='other') plus a payment allocation equal to the invoice's outstanding balance, then transitioning the invoice to paid. 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 for a paper-only / manual reconciliation where the receipt is NOT separately imported from an external ledger.

  • list_blocked_invoices

    Lists invoices the platform REFUSED to raise automatically because the duplicate-invoice guard suspected a duplicate — completed work that is sitting UNBILLED and needs a human decision. Each entry names the client, the period, the amount left uninvoiced, the job ids, and the existing invoice it was mistaken for. Entries are re-checked against current state: once the period has been invoiced (by override or by hand) it stops appearing, so a non-empty list always means real money not yet billed.

  • 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).

  • 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).

  • 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_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).

Operations (9)
  • list_pending_detail_changes

    Lists every unresolved detail change across the tenant's clients and contractors: unreviewed profile changes (awaiting the operator's 'mark reviewed') and PENDING banking changes (held — payouts keep using the old account until approved). Each row carries the entity, the field-level old → new diff, the source surface, and any ABR verdict.

  • 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.

  • 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.

  • update_pending_send

    Personalize the email of a staged send operation BEFORE it goes out. Slot-based editing: `cover_message` (the personal note above the CTA) and `subject`. Re-renders and returns the fresh email_preview — what you see is byte-for-byte what will send. Works on every send-family operation type (proposal, invoice, statement, credit note, work order, quote request, contractor agreement, and resends).

  • confirm_pending_operation

    Confirms a previously staged operation (leg 2 of 3 in the propose → confirm → approve send chain). Behaviour depends on operation_type:

  • approve_send_operation

    Approves a staged send operation and immediately sends the email to every staged recipient with a portal-link CTA (proposals fan out to the recipient_emails resolved and staged at propose time; the response's recipient_emails lists every address reached). 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.

Audit (2)
  • 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.

Platform (4)
  • 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.

Other (202)
  • 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.

  • get_entity_detail_changes

    Returns the field-level before/after change history for a client or contractor — every self-service detail update (proposal acceptance, client portal, contractor portal) with old → new values, the surface it came from, ABR verification verdicts on ABN changes, and pending banking changes awaiting approval. This is the SAME data the operator's 'View changes' popup shows.

  • review_entity_details

    Marks a client's or contractor's self-service detail changes as reviewed — the same action as the operator's 'Mark reviewed' button. Stamps every unreviewed PROFILE change row, clears the review banner, and writes the audit trail (event + audit_log).

  • approve_banking_change

    Approves a contractor's PENDING bank-details change and applies the new account to all future payouts. HUMAN IN THE LOOP IS MANDATORY (architect policy D4): the operator must have verified the change with the contractor out-of-band — normally a phone call — before this runs. Payables keep paying the OLD account until this succeeds.

  • reject_banking_change

    Rejects a contractor's PENDING bank-details change — the contractor's current (old) bank details stay in place for all payouts. Use after the operator decides the change shouldn't apply: couldn't verify with the contractor, the contractor denies making it (possible compromise — also consider rotating their portal link), or it was submitted in error.

  • propose_send_quote_request

    Stages a quote request to one or SEVERAL contractors in one go: creates a `requested` quote row per contractor (visible on their portal quoting pages at once, all sharing one request_group_id) and ONE pending operation covering every email — the operator approves once. Each email's CTA deep-links that contractor straight to their quoting page. Two-step chain: this call sends NOTHING.

  • create_quote_upload_link

    Generates a short-lived, single-use upload link for photos/documents on a quote REQUEST. Give the link to the operator — they tap it on their phone, pick the photos, and every contractor in the request group sees them on their portal quoting page.

  • approve_send_quote_request

    Approves and SENDS a staged quote request — every recipient in the staging, one approval. Each contractor receives a branded email whose CTA deep-links to THEIR portal quoting page, pre-focused on their copy of the request, with the operator's scope of work pre-filling their quote form.

  • record_compliance_review

    Records a verdict on a compliance document. Verdicts: verified (document valid), accepted_with_exception (insurance only — valid but with noted exceptions), rejected, needs_more_info.

  • record_exception_confirmation

    Records that the CONTRACTOR (or their broker/insurer) has confirmed an exception you recorded — the counterparty's answer, as distinct from your own judgement.

  • 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.

  • generate_compliance_report

    Generates a point-in-time compliance report (insurer pack) for the tenant's contractors. Returns the structured payload covering: engagement summary, compliance register, exceptions, gap list, process evidence, and minimums disclosure (including site floors). Each generation writes one compliance_report_snapshots row.

  • list_credential_types

    Lists the platform credential vocabulary — every credential a service can require and a contractor can hold (public liability, workers comp, electrical/gas/plumbing licences, white card, working at heights, ABN registration, …), with its `code`, human `label`, `category` (insurance|licence|card|registration), whether it `expires`, and a `verification_hint` for the reviewer.

  • 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.

  • backfill_line_facts_from_description

    Seeds a contract service line's MISSING facts from the free-text description on its accepted proposal line — the repair for a migrated line whose structured facts were never captured (e.g. a null contractor_method_notes that would dispatch a contractor with no instructions). Reads the accepted proposal line's description and proposes writing it into the null fact fields (client_line_description, contractor_method_notes) for review. `confirm:false` (default) PREVIEWS the proposed mapping and writes NOTHING. `confirm:true` writes ONLY the currently-null facts (a populated fact is never overwritten) and appends a provenance note to the line's internal notes. It NEVER invents content: if the accepted proposal line has no free-text description, it refuses with a named reason instead of guessing.

  • set_line_attribute

    Creates or updates a custom key/value attribute on a contract service line. Attributes are the long-tail complement to first-class line facts — use them for trade-specific metadata (circuit IDs, bin sizes, unit numbers) that vary per line. Each attribute carries audience + render_map for structural visibility control.

  • remove_line_attribute

    Soft-deletes a custom key/value attribute from a contract service line. The attribute is marked with deleted_at but not hard-deleted — it can be re-created with set_line_attribute.

  • 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. DEADLINE MACHINERY (WP-MISSED-RECOVERY — teach the operator, don't let them discover it): the daily sweep flips dispatched jobs to 'missed' after `scheduling.missed.sweep_grace_days` clear days (registry, default 1); the contractor's portal Mark Complete stays available until scheduled_date + `scheduling.completion.late_window_days` (registry, default 7; per-contractor override `scheduling.completion.late_window_days_override`, -1 = inherit). A missed job is therefore NOT terminal: within the window the contractor self-completes from the portal; at any time the operator recovers it here with `backfill:true`.

  • 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

    DEPRECATED — use triage_bank_transaction instead. Mark a bank transaction as a non-sale so it leaves the reconciliation worklist. Maps to triage_bank_transaction with category 'other' and preserves the reason as a note.

  • ignore_pre_cutover_transactions

    Cutover triage: bulk-mark every UNMATCHED bank line dated BEFORE a cutover date as ignored ('pre_biloh_history'), so the reconciliation worklist opens on only the deposits Biloh can actually settle. Use this once when a tenant migrated INTO Biloh mid-stream — their older bank lines reference invoices in their previous system and can never match here, so they would otherwise clog the worklist forever.

  • triage_bank_transaction

    Triage a bank transaction into a tenant-defined category (e.g. 'previous-system-payment', 'marketplace-sale', 'business-expense'). The line leaves the reconciliation worklist and appears in the Triaged tab with its category badge.

  • untriage_bank_transaction

    Reverse a triage decision: restore a bank transaction to the reconciliation worklist. The line goes back to match_state='unmatched' and all triage columns are cleared.

  • bulk_triage_bank_transactions

    Triage multiple bank transactions into the same category in one call. Useful for batch-clearing a set of similar lines (e.g. all old-CRM payments this month).

  • list_bank_triage_categories

    List all bank-feed triage categories for this tenant. Categories define what a non-invoice bank line was (e.g. 'Payments received in previous system', 'Marketplace sale', 'Business expense'). Each carries a platform rollup_class for cross-tenant reporting.

  • list_triaged_transactions

    List bank transactions that have been triaged (categorised as non-invoice). Optionally filter by date range and/or category key.

  • get_bank_triage_report

    Get a summary report of triaged bank transactions for a period, grouped by category or rollup class. Shows count, total credit cents, and total debit cents per group.

  • create_bank_triage_category

    Create a new tenant-defined bank triage category. Categories define what non-invoice bank lines are (e.g. 'ServiceM8 payment', 'Marketplace sale'). Each must be assigned a platform rollup_class for cross-tenant reporting.

  • update_bank_triage_category

    Update a tenant-defined bank triage category. You can rename the label, change direction_scope, or adjust sort_order. The key and rollup_class are IMMUTABLE after creation — if the class is wrong, create a new category and archive this one.

  • archive_bank_triage_category

    Archive (soft-delete) or unarchive a bank triage category. Archived categories are hidden from pickers and block new triages, but all historical triaged rows remain joined and reportable. The 'other' category is protected and cannot be archived.

  • apply_bank_payer_rules

    Apply tenant payer rules to unmatched, untriaged bank transactions. Rules auto-triage lines whose description matches a saved substring, UNLESS the line's amount equals any outstanding invoice total (the One Percent guard).

  • triage_bank_feed

    Composite bank-feed triage tool. Two modes:

  • create_bank_payer_rule

    Create a payer rule that auto-triages bank transactions whose description contains the given substring into the specified category.

  • list_bank_payer_rules

    List all payer rules for the tenant. Payer rules auto-triage bank transactions whose description contains a saved substring.

  • deactivate_bank_payer_rule

    Deactivate (or reactivate) a payer rule. Deactivated rules stop firing on future imports but remain queryable for audit.

  • get_reconciliation_suggestions

    Ranks unmatched bank transactions against outstanding invoices (credits) and payables (debits), returning suggested matches with deterministic confidence scores. Credits: reasons include exact_amount, invoice_ref_in_description, alias_match/client_name_match, date_plausible; bands auto_suggest >= 0.9, needs_review >= 0.6. Debits: reasons include exact_amount, contractor_alias, date_proximity — and (WP-CONTRACTOR-BILLS) every debit suggestion now carries `billReference` (parsed 'Inv # N' from the description), `billCandidates` (open contractor_bills with outstanding_cents; a reference match scores 0.95 `bill_reference_match` — settle via settle_bill_with_bank_debit), payable candidates enriched with `createdAt` + `jobContext` {siteName, serviceName, jobDate} + `linkedInvoice`, and `excludedImpossible` — the count of same-amount payables HARD-EXCLUDED because they were created after the money left (they cannot be what the debit paid). Returns both `suggestions` (credits) and `debit_suggestions` (debits). 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.

  • run_period_close

    Closes (locks) a period on the month → quarter → financial-year close ladder, snapshotting its cash-basis figures (received / ex-GST / GST / receipt_count / outstanding_ar) into period_closes and marking it 'closed'. Give `kind` (month|quarter|fy) and ANY date inside the period as `period_start`; the tool computes the canonical AU span and the kind-aware label for you (kind=quarter + 2026-05-15 → Q4 Apr–Jun 2026, label '2026-Q4'; kind=fy + any date in 1 Jul 2025–30 Jun 2026 → 'FY26'; kind=month + 2026-06-10 → '2026-06'). A month, its covering quarter, and the FY are DISTINCT closes and coexist. IDEMPOTENT per (kind, period) — re-running re-snapshots the same row.

  • get_period_ladder

    Returns the whole month → quarter → financial-year CLOSE LADDER for a financial year in one call — the agent's view of the Periods screen. For the FY containing `fy` (any ISO date inside it; defaults to today's FY): the 12 calendar months (Jul→Jun), the 4 AU-FY quarters (Q1 Jul–Sep … Q4 Apr–Jun), and the FY row, each with its close state (open | closed | reopened | stale) and, when closed, the frozen received + GST snapshot. `stale` means a child period was reopened after this period was closed — re-close it (run_period_close) to refresh the roll-up. READ-ONLY.

  • 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) PLUS the configured email sender (default_from_name, invoices_email, proposals_email, dispatch_email, reply_to_email) and a resolved effective_invoice_from = the {display_name, address} the invoice send path would actually use, including the noreply@<tenant-domain> fallback when invoices_email is unset. 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.

  • settle_payable_externally

    Records that a contractor payable was paid outside the bank feed (personal account, cash, etc.). Requires a note explaining HOW the payment was made. Sets the payable to released with paid_via='external'.

  • reverse_payable_settlement

    Reverses an external or legacy_backfill settlement on a contractor payable. Sets the payable back to approved and clears all settlement fields.

  • attach_bill_document

    Attaches a bill document (photo of invoice, PDF scan) to a payable OR a contractor bill for ATO substantiation. Accepts the file as base64, validates and uploads to storage, sets the document path on the target (payables.bill_document_path or contractor_bills.document_path). Exactly one of payable_id / bill_id. Re-attach replaces the previous document (both old and new paths are audited).

  • create_bill_upload_link

    Generates a short-lived, single-use upload link for a bill document (invoice photo/PDF). Give this link to the operator — they tap it on their phone, pick the photo/scan, and upload it natively. This avoids base64 corruption for large files. Target a contractor_bill (bill_id — preferred for multi-line bills recorded via record_bill) OR a single payable (payable_id — the backfill door). Exactly one.

  • match_bank_debit_to_payable

    Matches a bank debit (money out) to a contractor payable — confirms the payment happened. Sets the payable to released with the bank's value_date as the paid date, marks the debit as matched, and teaches a contractor bank alias for future matching.

  • record_bill

    Records a contractor's multi-line bill (tax invoice) as a first-class contractor_bill with lines linked to open payables. THE front-of-house door for ongoing contractor invoices — one invoice covering N jobs becomes one bill with N lines. GST is stored AS PRINTED on the paper, never derived (D3). Totals must add up (subtotal + gst = total) and every line must equal its payable's accrued amount — a disagreeing claim is a dispute, not a silent overwrite.

  • get_bill

    Returns a contractor bill by ID with its lines, its full settlement timeline (including reversed rows), settled_cents and outstanding_cents. The follow-the-money view for one bill: claim → lines → payables → payments.

  • list_bills

    Lists contractor bills for the tenant, filtered by contractor_id / status / since. Each row carries totals, variance, status and settled_cents (live settlements only) so an AP worklist can render without N+1 get_bill calls. Default hides is_test rows.

  • settle_bill_with_bank_debit

    Settles a contractor bill with one bank debit — the money-out mirror of confirming an invoice payment. Fans the debit out into payable_settlements rows across the bill's payables (oldest accrual first), releases every payable whose settlements now cover it, marks the debit matched (matched_bill_id), learns a contractor bank alias, and sets the bill part_paid or paid. Part payments are first-class: a debit smaller than the outstanding leaves the bill part_paid with the split recorded per payable. The bank's value_date is the paid date (Principle 3).

  • settle_bill_externally

    Settles a contractor bill (fully or partially) with a payment made OUTSIDE the bank feed — personal account, cash, another bank. Requires the real-world paid date and a note explaining HOW. Same fan-out semantics as settle_bill_with_bank_debit, method=external, no bank transaction touched.

  • reverse_bill_settlement

    Reverses a contractor-bill settlement: marks the settlement rows reversed (NEVER deletes them — the ledger is append-only evidence), restores affected payables to approved with paid fields cleared, frees the bank debit back to unmatched, and recomputes the bill status. Scope to one payment with bank_transaction_id, or omit it to reverse every live settlement on the bill.

  • void_bill

    Voids (soft-deletes) an UNSETTLED contractor bill and un-stamps its payables back to unbilled so they can be re-billed correctly. A bill with live settlements is refused — reverse them first (reverse_bill_settlement). The claim-document discipline: a recorded bill is never silently edited; it is voided with a reason and re-recorded.

  • unmatch_bank_debit

    Reverses a bank debit → payable match. Returns the payable to approved status and the bank debit to unmatched, so both can be re-reconciled.

  • list_documents_for_entity

    List all evidence documents (invoices, receipts, statements) linked to a finance entity. Returns document metadata, provenance, and link role.

  • get_document

    Get a source document by ID, including all entity links and a short-lived signed download URL.

  • update_expense_record

    Update an expense record — state GST, attach a receipt, re-categorise, or add notes. Re-evaluates substantiation status automatically.

  • list_expense_records

    List expense records for this tenant. Filter by status, category, date range, or missing receipt.

  • get_purchases_gst_report

    The cash-basis Purchases & GST report — the money-OUT mirror of get_sales_gst_report. Returns one row per contractor bill PAID in the period (bill number, bill date, paid date, contractor, ex-GST cents, GST cents, total cents), period totals, the AU GST period label, and footnotes (unpaid bills excluded from cash basis, out-of-gate + external settlement counts).

  • get_gst_position

    Net GST position for a period: GST collected (1A from sales) minus GST paid (1B from purchases). Returns payable_to_ato (positive net) or refund_due (negative net). Pure composition of the sales and purchases reports — no third money path.

  • get_aged_payables

    Returns aged payables (AP aging) bucketed by days outstanding: current (0-30), 30-60, 60-90, 90+. Shows unpaid contractor bills by age, mirroring the aged-receivables pattern.

  • 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 — twelve 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.

  • generate_pay_all_link

    Returns the durable client-portal "pay everything" link for a client, plus a breakdown of what is outstanding. Opening the link lands the client on their portal Finance page and immediately starts ONE Stripe Checkout paying every outstanding invoice (sent / overdue / partially_paid) at its REMAINING balance. The payment is split across the invoices automatically by the Stripe webhook; any excess lands as credit on account.

  • 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, client name, and linked job context. Default filter is status='pending' — the operator's working set of requests awaiting approve/decline. `submitted_by_type` tells you who asked: 'client' or 'contractor'. The returned schedule_change_request_id is the input to approve_schedule_change_request / decline_schedule_change_request. The same rows render for humans under Pending Operations → Schedule requests.

  • 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 overdue/unassigned/today/upcoming/completed/cancelled, contractors-on-leave list, and aggregate stats (total / assigned / unassigned / overdue / completed). The `overdue` bucket carries every non-terminal job dated before tenant-today plus every `missed` job — the operator's most urgent slice (WP-SCHED-GOLD-0). Date defaults and bucketing anchor to TENANT-TIMEZONE today (never UTC); the response echoes `tenant_today` + `tenant_timezone`. 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.

  • backfill_period

    Backfills a migrated client's ALREADY-PERFORMED occurrences for a past period, derived from the contract service line's OWN recurrence rule — the missing bridge between 'client onboarded mid-month' and 'this month invoiced through biloh'. The spawner materialises forward only; this tool owns the backward direction. Occurrences BEFORE the CSL's anchor derive correctly (the anchor is back-shifted in whole periods, phase preserved — a weekly-Wednesday line anchored 23 Jul still derives 1/8/15/22 Jul). `confirm:false` (default) PREVIEWS: exact dates, per-visit legacy rate, period totals ex/inc GST, dedup against existing jobs, and a plain-language billing expectation from the client's invoicing cadence (a period_final_consolidated client gets ONE consolidated invoice when the month's last job completes — produced by the EXISTING machinery, not re-implemented). `confirm:true` writes. Two modes: 'dispatch' (default) lands the jobs dispatched + assigned in the contractor's portal for real completion; 'complete' completes them immediately through the canonical completion core (real job.completed events → payable + invoice handlers fire identically to a portal completion; completed_at = the visit's own date). The backdated period bills at `backfill_client_rate_ex_gst_cents` (legacy pricing survives a migration price rise); the CSL's forward rates are NEVER touched.

  • settle_legacy_visit

    Settles an already-performed legacy visit end-to-end in TWO calls: creates the completed job(s) on the visit date(s) and drafts the invoice, so 'I did this on the Nth, set it up and invoice them' stops expanding to ~10 plumbing calls. A visit-shaped wrapper over backfill_period (mode complete): give it a `date` (or `dates`), not a window. `confirm:false` (default) PREVIEWS the whole chain — the jobs it would create, the contractor it resolved, the legacy rate, period totals ex/inc GST, and the invoice expectation from the client's cadence — writing NOTHING. `confirm:true` completes each visit through the canonical completion core (real job.completed events → FE-04B payable + FE-02B invoicing-cadence handlers fire exactly as a portal completion would), drafts the invoice(s) per the client's cadence (a period_final_consolidated client gets ONE consolidated draft with a line per visit, service_date = each visit's own date), and returns draft_invoice_ids ready for propose_send_invoice. The backdated visit bills at backfill_client_rate_ex_gst_cents (legacy pricing survives a forward price rise); the CSL's forward rate is never touched. Idempotent: re-running the same visit dates returns already_existed with no duplicate job or invoice.

  • amend_series

    Amends a recurring series' cadence (frequency, day of week, week of month, or anchor date) as ONE operation. Two-phase: confirm defaults to FALSE (a preview that writes nothing); pass confirm=true to apply.

  • assign_series

    Assigns a contractor to a WHOLE recurring series (a contract service line): sets the line's contractor so every future spawn inherits them, re-derives what that contractor is paid, and sweeps the series' existing 'scheduled' occurrences onto them in one call.

  • reassign_series

    COMPOSITE — hand a whole recurring series (a contract service line) to a DIFFERENT contractor in one call. Two-phase: `confirm` defaults to FALSE and returns a preview that writes NOTHING; pass confirm:true to execute.

  • approve_counter_offer

    Operator approves a contractor_to_operator counter-offer, accepting the proposed schedule change.

  • set_csl_constraint

    Create or update a commitment constraint on a contract service line (CSL). Constraints define the negotiation boundaries for schedule changes.

  • remove_csl_constraint

    Remove (soft-delete) a commitment constraint from a contract service line. This is the escape hatch — proves a CSL cannot be bricked by an un-removable lock.

  • list_csl_constraints

    Lists active commitment constraints for a contract service line (CSL).

  • create_run

    Create (or reuse) a RUN for a day — the ordered list of stops a contractor or the operator will work, in sequence. Day-granular: a run groups a day's jobs and orders them 1..n (this is NOT time-slotting — biloh never schedules by time of day). MANUAL ORDERING ONLY — this makes no optimizer/Google call and works with route optimization OFF (optimization is a separate paid bolt-on that lands later). Idempotent: if a run already exists for that (date, contractor) it is reused and any new job_ids are appended. Jobs with a site that has no coordinates are still added to the run and SURFACED under no_location (never dropped). USE WHEN: building the day's run sheet order by hand. Pass contractor_id for a contractor's run, omit it for the operator/self run. Returns the run_id + the ordered view (stops + no_location).

  • get_run

    Read a RUN by id: its ordered stops (stop_order 1..n, each with site name/address/coordinates) plus a separate no_location list of stops whose site has no coordinates yet (surfaced, never dropped). Day-granular sequence, not time slots. Contractor-safe: the payload carries no client identity or rates. USE WHEN: showing or checking a day's run order. Read-only.

  • list_runs

    List RUNS for this tenant (optionally filter by run_date and/or contractor_id), each with its status and stop count. USE WHEN: finding the run for a given day/contractor before reading or reordering it. Pass contractor_id to scope to one contractor's runs; omit run_date to list across days. Read-only.

  • update_run_stop_order

    Set the MANUAL stop order for a run. Pass ordered_job_ids = the run's exact current job set in the new sequence; stop_order is rewritten 1..n in that order. This is a human-set order — no optimizer/Google call (optimization is a separate paid bolt-on). USE WHEN: the operator reorders the run sheet by hand. The list must contain exactly the run's current jobs (no additions/removals/duplicates) — add or remove stops with create_run first. Returns the reordered view.

  • 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 the platform's own identity from the `platform.email.*` settings (Platform Settings → Email), and the sending substrate follows that domain: biloh.com.au sends over Migadu, gwcpropertyservices.com.au over Resend. A domain needing a substrate this environment lacks is refused rather than sent under another 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.

  • get_workflow_guide

    Returns a platform-authored workflow guide: canonical step order (with exact tool names you can call), enum decision tables (which value to pick when), known gotchas, and relevant settings knobs. Tenant-specific notes are merged when configured.

  • get_documentation_directive

    Returns the current knowledge-extraction directive, ICP profiles, and instructions for staging public documentation. USE WHEN: You are told to 'run the documentation extraction' or 'write docs for biloh.com.au/docs' or 'document this on the platform docs'. The directive is the single source of truth for the extraction workflow. PRECONDITIONS: None — read-only. SIDE EFFECTS: None.

  • 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.

  • create_branding_vendor_link

    Mints a branding VENDOR PORTAL link for an external graphic designer: a token URL where they see every kit slot with its spec (format, dimensions, background, size cap), upload BOTH the finished production file AND the editable source master per slot (provider-agnostic — Illustrator, Figma, Affinity, whatever they use), track progress, and submit the kit for review. Multi-use within its lifetime, unlike the single-use create_branding_upload_link (which is for the OPERATOR's own quick phone upload into one slot).

  • list_branding_vendors

    Lists this tenant's branding vendor portal links: designer name/email, status (active/suspended/expired), expiry, last access, and the portal URL for active links. PINs are never returned.

  • revoke_branding_vendor_link

    Immediately revokes a branding vendor's portal access (status → suspended). The link stops working on the next request. Uploads already delivered stay in the kit.

  • commission_branding_kit

    COMPOSITE — the one-call way to commission a branding kit from an external designer. Ensures the tenant's kit slots exist (seeds the 8 canonical slots if missing), mints a vendor portal link, and returns the delivery checklist (every slot with format/dimension specs and current status) plus a ready-to-send email draft for the designer containing the link and the brief.

  • export_branding_kit

    Packages the tenant's ENTIRE branding kit into a zip — every finished production asset, every editable source master (AI/Figma/PSD), and a generated BRAND-GUIDE.md documenting colors, fonts, and the asset inventory — and returns a time-limited (1 hour) download link. The kit is the tenant's property: this is the no-lock-in export and the brand-pack product's deliverable.

  • stage_public_doc

    Stages a public documentation article for the biloh.com.au/docs corpus. The article enters a validation pipeline (docs:check + docs:verify + sanitisation guard) before being committed by a repo-capable session. Never publishes directly. USE WHEN: You have extracted knowledge from a conversation and want to publish it as a public doc. Call get_documentation_directive first for the authoring contract. PRECONDITIONS: Directive loaded, article written per CONTRIBUTING.md. SIDE EFFECTS: Creates a staged_public_docs row with status='staged'.

  • 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.

  • register_comms_account

    Register or update a comms mailbox account for the tenant. Upsert on (tenant_id, address) — first call creates, second returns existing with created:false. NO password field — mailbox credentials are managed via the settings UI only, never through MCP.

  • ingest_comms_messages

    Batch-ingest email messages into the comms layer (≤50 per call). Idempotent on content_hash — duplicate messages are counted but not re-inserted. Body text >64KB is truncated (body_truncated:true), never rejected. Returns {inserted, duplicates} counts.

  • list_comms_threads

    List comms threads for the tenant, optionally filtered by status or account_id. Returns exactly: id, account_id, thread_key, subject, status, last_message_at, created_at.

  • get_comms_thread

    Get a single comms thread with its messages and entity links.

  • link_comms_entity

    Link a comms thread to a business entity (client, contractor, site, invoice, proposal, work_order).

  • file_comms_work_item

    File a comms work item (triage or task) for human decision. The plain block is schema-validated (same shape as the Decision Queue: headline, what_happened, who_it_affects, where, if_we_do_nothing, options with exactly one recommended:true). Rejects with code plain_invalid on malformed blocks.

  • list_comms_work_items

    List comms work items for the tenant, filterable by status, urgency, kind, and Decisions group.

  • decide_comms_work_item

    Approve, decline or REDIRECT a comms work item. The ONLY route to 'approved' status. Stamps decided_by from the authenticated identity and the given decision_channel.

  • record_comms_execution

    Record that an approved comms work item has been executed. Refuses with comms_item_not_approved unless the item is in 'approved' status.

  • create_comms_task

    Create a comms task (kind='task') — a personal or business to-do that needs no email thread.

  • upsert_comms_agent_note

    Create or update an agent note (epistemic memory) scoped to a sender, thread, entity, or globally.

  • list_comms_agent_notes

    List agent notes (epistemic memory), optionally filtered by scope and key. By default only returns non-superseded notes.

  • get_comms_brief

    Get the comms brief for a tenant — digest data including counts of items by urgency, NEEDS-YOU-NOW items, awaiting-decision items, recently executed items, and ingest health.

  • send_comms_digest

    Render and send the comms digest email for a tenant. Recipient MUST match the tenant setting comms.digest_recipients — any other recipient is refused with digest_recipient_not_allowlisted. Empty brief returns {skipped: true} without sending. Sends via existing platform mail machinery.

  • fetch_comms_history

    Fetch older email messages on demand from a connected mailbox. Unlike the background poller (which syncs only the last N days), this tool retrieves messages from ANY date range. Use it when investigating a thread and the relevant history predates the initial sync.

  • get_comms_extraction_queue

    Get messages queued for fact extraction — returns cleaned body text and attachment text ready for an extraction agent.

  • record_comms_facts

    Record facts extracted from an email message. Each fact must include a source_quote that is validated against the actual source text (body or attachment). If ANY quote fails validation, the entire call is rejected atomically — zero facts written.

  • resolve_comms_entity

    Resolve a comms entity — find matching clients, contractors, sites, invoices and bills from sender email, domain, or text references. Returns ranked candidates with score, matched_on, and match_reason. Returns an empty list rather than a low-confidence guess.

  • get_comms_triage_queue

    Get threads ready for triage — those with new messages or new extractions since last triage. Returns thread metadata, from addresses, work item status, and the FULL non-superseded fact set per thread (kind, label, value, source quotes, resolved entities). Facts are the triage pass's entire view of message content: it NEVER reads message bodies, and must not call get_comms_thread (which returns bodies). Cross-check facts against the ledger via read-only tools (list_client_invoices, list_bills, list_payables, list_bank_transactions, get_client_money_story). When finished with a thread, call mark_comms_thread_triaged — always, even when nothing was filed.

  • backfill_comms_attachments

    Backfill attachment metadata + bytes for existing messages on one comms account. Scans messages that have no comms_attachments rows, addresses each by its exact Message-ID header via IMAP SEARCH, fetches bodyStructure + bytes, extracts PDF text, and stores attachment rows + source_documents.

  • mark_comms_thread_triaged

    Mark a comms thread as triaged. Sets last_triaged_at and triage_state='triaged' so the thread leaves the triage queue until a new message or new extraction re-queues it.

  • get_comms_work_item

    Read ONE comms work item with its evidence resolved: cited comms_facts rows (kind, label, typed value, verbatim source_quote, source badge), ledger summaries (invoice number/status/total, bank transaction date/amount, client/contractor/site names), child items spawned from its adjacent opportunities, and its parent (provenance chain).

  • snooze_comms_work_item

    Defer a comms work item: hide it from the Needs-you group until `until` lapses, then it returns unchanged (status untouched — D35: snooze is deferral, not decision). Max 30 days (snooze_too_far beyond that — a longer deferral is a decline or a task).

  • accept_comms_adjacent

    Accept one of a work item's adjacent_opportunities: creates a LINKED CHILD task (kind='task', parent_work_item_id = the parent, dedupe_key adjacent:<parent>:<index>) that carries its own lifecycle — the provenance chain finding → decision → child → execution stays queryable forever (D34). The parent item is not modified. Repeat acceptance answers duplicate_dedupe_key.

  • draft_comms_reply

    Draft a reply INSIDE an existing email thread and put it in the mailbox's real Drafts folder. The reply anchors to a stored message: In-Reply-To and References are derived from that message's own header chain, and the subject gains exactly one 'Re: '. This is the only way to answer an email — a fresh out-of-context message from 'the system' breaks the recipient's conversation view, so cold outbound composition does not exist on this tool.

  • send_comms_reply

    Send a reply that was already drafted by draft_comms_reply, through the mailbox's own SMTP identity and into the existing thread. Files the copy in Sent, so the operator's mail client and the Decisions queue stay two views of one state.

  • list_mailbox_folders

    List the folders that actually exist in a tenant's mailbox. Read-only: this opens a connection, asks, and closes it.

  • create_mailbox_folder

    Create a folder in the tenant's mailbox. ADDITIVE ONLY — a folder that already exists is returned as-is (created:false), never replaced, and nothing is ever removed or renamed (D46).

  • move_comms_messages

    File mail into a folder — on the SERVER, in the operator's real mailbox, and on our row so the two never disagree. A move and nothing else: no copy is left behind, nothing is removed, no read-state or flag changes. Those verbs do not exist in this system (D46).

  • upsert_comms_filing_rule

    Create or update a filing rule — standing permission to move a class of mail into a folder on every future fire, without asking again.

  • list_comms_outbound

    List the replies this system has drafted and sent, with what became of each. Read-only.

  • get_comms_mailbox_census

    Who is actually filling these mailboxes. A sender histogram over the stored messages: one row per account × folder × sender, with message and thread counts, the window they arrived in, up to three sample subjects, and whether an active filing rule already covers that sender. Read-only.

  • list_comms_filing_rules

    The standing permissions: every filing rule this tenant has, what it matches, where it files, whether it is active, and the approval it was born from. Read-only.

  • execute_approved_comms_items

    Run the execution wire: carry out the staged actions of items the operator has ALREADY approved, so an approval at 7am is a coded ledger line by the next fire — never a bypass, never a decision. This tool never approves, decides, or sends anything; it executes only items a human has ALREADY approved, and only when their staged_action names an allowlisted verb with an args object.

  • escalate_comms_work_item

    Hand an approved item you could NOT carry out back to the operator, with the reason. Use this the moment an instruction cannot be honoured — a guard refuses, the world moved, the instruction contradicts live state, or you are not licensed for the verb it needs. The item lands at the TOP of the operator's queue with your reason on the card.

  • sweep_comms_execution

    Release abandoned execution claims and report every decided-but-unfinished item past its TTL, oldest first. An item past its TTL is an ALARM, not a status — twenty-eight items once sat approved for five days and nothing knew that was abnormal.

  • handoff_comms_work_item

    Pass an approved item to the desk that IS licensed to do it, instead of handing it back to the operator. Use this the moment you find the work is real and still wanted but the verb it needs belongs to another lane — raising an invoice is the bookkeeper's, filing mail is the steward's.

  • 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.

Authentication

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.

Reference

Ready to plug Biloh into your assistant?

Start a free trial — your MCP server endpoint is ready on day one.