Skip to content
billoquy

For agents · Model Context Protocol

Connect an assistant to billoquy

mcp.billoquy.com is an MCP server. Point an MCP-capable assistant at it and, on a signed-in person's behalf, it can discover the checks, start one, read the finding with cited sources, preview a prepared fix, and hand the person off to approve and pay. It cannot approve, file, send, or pay — those are the person's actions.

Connect

The server is a streamable-HTTP MCP endpoint. Configure your client with the endpoint and the person's agent key as a bearer token:

Server namebilloquy (v1.0.0)
Endpointhttps://mcp.billoquy.com/mcp
Auth headerAuthorization: Bearer <agent key>

A typical client config looks like this:

{
  "mcpServers": {
    "billoquy": {
      "url": "https://mcp.billoquy.com/mcp",
      "headers": { "Authorization": "Bearer <agent key>" }
    }
  }
}

list_checks and the two resources need no auth, so a client can discover the surface before a key is attached. Every tool that touches an account needs the key. Clients that cannot send headers may instead pass the key as an accessToken argument on each account-scoped tool.

Auth: the agent key

An agent key is a billoquy session bound to a person's account with the purposeagent. It acts on that person's behalf: it is owner-scoped to their account and nothing else. It is the same token model the web app uses, with one added purpose — there is no parallel auth scheme.

  • Minted by the person, human-only. The account owner creates and names a key in the AI assistant keys panel on their account page. Minting is a human action — an agent cannot mint, list or revoke its own credentials. The key is shownonce and never readable again.
  • Revoked by the person. The same panel lists every key the account holds by label and revokes any of them, including one whose value the owner no longer has. Developers have the same three routes directly: see the developer API.
  • Fails cleanly when absent. An account-scoped tool called without a key returns an agent-legible error pointing to where the person creates one — never a silent or misleading result.

The approval guardrail

An agent can prepare — only the human approves

An agent connected to billoquy can discover checks, start them, read findings, preview a prepared fix, and hand the person a link to approve or pay. It can neverapprove, file, send, deliver, or pay. This is not a convention — it holds at three layers:

1. The edge (the real gate). Approving or declining a prepared fix requires ahuman token. An agent key — owner-scoped though it is — gets a 403 at the approval endpoint, no matter what any client does.
2. The MCP surface. There is simply no approve, decline, file, send, or pay tool. The client the tools are built on has no such method, so it cannot reach that endpoint.
3. Preview, then hand off. get_prepared_fix returns the drafted artifact and an approval link for the person to open in their own signed-in session — nothing more.

Tools

Seven tools. Only list_checks is unauthenticated.

ToolAuthWhat it does
list_checksnoneDiscover the checks and the approval policy.
start_checkagent keyStart a free check for the owner.
get_checkagent keyPoll/read a check and its finding.
get_prepared_fixagent keyPreview a prepared fix + get the approval link.
start_fix_checkoutagent keyGet a Stripe Checkout link to hand the person.
get_billing_portalagent keyGet the self-serve billing portal link.
list_accountagent keyList the account's checks, fixes, and receipts.

list_checks

No input, no auth. Returns { checks, approvalPolicy }. Each entry inchecks carries id, name, tagline, available, finds, record, recordUrl, worth, input[], requiresDocument, documentHint, preparesFix and, when not available yet, an availabilityNote. preparesFix is what says whetherstart_fix_checkout can get anywhere for that check at all.

→ list_checks {}

← {
  "checks": [
    {
      "id": "energy",
      "name": "Energy Check",
      "available": true,
      "finds": "the wrong rate plan",
      "record": "public utility rate database",
      "recordUrl": "https://apps.openei.org/USURDB/",
      "worth": "$200–600/yr",
      "input": [
        {
          "name": "zip",
          "required": true,
          "description": "5-digit US ZIP code"
        }
      ],
      "requiresDocument": true,
      "documentHint": "a photo or PDF of a recent utility bill",
      "preparesFix": false
    }
    // ...one per check
  ],
  "approvalPolicy": "billoquy prepares; the person approves. ..."
}

start_check

Inputs: product (an id from list_checks), intake (the structured fields for that check — see per-check intake), and for document-centric checks either documentUrl (a public https link to a PDF or photo) or uploadKey(already staged with billoquy). Optional accessToken.

  • Where a check can't yet answer for the person's input — an area whose public data isn't loaded — the diagnostic resolves to status: "unsupported" onget_check, carrying the reason — nothing is charged.
  • A document check with neither documentUrl nor uploadKey returns an error telling you to pass one (or have the person upload it).
  • On success: { status, diagnosticId, next }. Poll get_check with the id.
→ start_check {
  "product": "energy",
  "intake": { "zip": "78704" },
  "documentUrl": "https://example.com/my-utility-bill.pdf"
}

← { "status": "received", "diagnosticId": "diag_…", "next": "Poll get_check …" }

get_check

Input: diagnosticId (+ optional accessToken). The response depends on status:

  • complete: { status, product, headline, detail, dollars, confidence, worthFixing, citations }, where dollars is { amount, basis: "annual" | "one-time", currency: "USD" }or null, and each citation is { label, source, url }. WhenworthFixing is false and the finding still carries dollars, afixNotOffered object says which refusal applies — informational_check(this check sells no fix at any figure) or below_fix_floor (it sells one, but not for a finding this small).
  • unsupported: { status, product, message }.
  • failed: { status, product, reason }.
  • otherwise (still running): { status, product, note } — poll again shortly.
→ get_check { "diagnosticId": "diag_…" }

← {
  "status": "complete",
  "product": "energy",
  "headline": "You're on the wrong rate plan",
  "detail": "Switching to Plan B saves about …",
  "dollars": { "amount": 340, "basis": "annual", "currency": "USD" },
  "confidence": "high",
  "worthFixing": false,
  "fixNotOffered": {
    "reason": "informational_check",
    "note": "The Energy Check reports what it found and stops there — …"
  },
  "citations": [
    { "label": "Utility tariff", "source": "OpenEI USURDB", "url": "https://apps.openei.org/USURDB/" }
  ]
}

get_prepared_fix

Input: diagnosticId (+ optional accessToken). If a fix has been drafted (the person paid), returns{ prepared: true, title, fixStatus, artifactPreview, approval } whereapproval is { required: true, by: "human", url, note } — the link the person opens to approve. If not, returns{ prepared: false, message, diagnosticPage }. This tool never approves.

start_fix_checkout

Input: diagnosticId (+ optional accessToken). Returns{ checkoutUrl, note } — a Stripe Checkout link to hand the person. The agent cannot pay; the person completes payment themselves, after which billoquy drafts the fix and it waits for their approval. Refused where there is no fix on offer: a check whosepreparesFix is false sells none at any figure, and a check that does still declines below the fix floor. Read worthFixing on get_checkfirst and relay the fixNotOffered reason rather than sending someone to a link that will not open.

get_billing_portal

Input: optional accessToken. Returns { portalUrl, note } — the self-serve Stripe billing portal link for the person. No money moves through the agent.

list_account

Input: optional accessToken. Owner-scoped summary:{ user, diagnostics, preparedFixes, receipts } — diagnostics with status and finding headline, prepared fixes with their approval status (approvedVia), and receipts (whatWeDid, whatItMeans, whatHappensNext).

Per-check intake

The intake object for start_check, per product. Calllist_checks for the authoritative, machine-readable version.

productintake fieldsdocumentavailable
energyzip (5-digit US ZIP code)Yes — a utility billyes
fees(none)Yes — a bank or card statementyes

Resources

Two read-only resources describe the surface without any tool call:

  • billoquy://checks — the checks as JSON (application/json): what each finds, its public record, inputs, and availability. The same data list_checksreturns.
  • billoquy://approval-policy — the "we prepare, you approve" guarantee in prose (text/plain): what an agent can and cannot do.

A worked example

Running a Fee Check for the connected person and handing them the payment and approval links:

  1. Connect with the person's agent key as a bearer token.
  2. list_checks → confirm fees is available, needs a document, and has preparesFix: true.
  3. start_check with { product: "fees", intake: {}, documentUrl } → a diagnosticId.
  4. get_check on that id until status: "complete" → the finding, with dollars and citations.
  5. If worthFixing: start_fix_checkout → hand the person checkoutUrl to pay. If not, relay fixNotOffered.note — the finding is theirs to act on, free.
  6. After they pay: get_prepared_fix → hand them approval.url. They approve on the web; the agent never does.

Honest limits

  • Availability. A check's coverage can be built out area by area, so a started check may come back from get_check as unsupported with amessage saying so. Relay it — do not lead the person into a dead end.
  • Polling. get_check is a single fetch; there is no server-side long-poll. Poll it until the status is terminal.
  • Documents. documentUrl must be a publicly fetchablehttps link (it is fetched under strict SSRF guards). Documents behind a login must be uploaded by the person; the tool's error copy says so.
  • Prefer raw HTTP? The MCP is a pure client of the same edge API — see thedeveloper API reference.