Skip to main content
Glama
billy-mcp

billy-mcp

by Cimalys

billy-mcp

An MCP server for billy.dk — Danish accounting, controllable from Claude (or any MCP-compatible AI client) over the official Billy v2 REST API.

Read your invoices, categorise bank lines, draft new invoices from notes, upload receipt PDFs and attach them to bills, run a P&L on demand, prep MOMS reports, browse the audit log. 65 tools covering the full Billy v2 surface. Write operations are gated behind an explicit confirm: true so the AI cannot post to your books without per-call acknowledgement.

  • Language: TypeScript, Node 20+

  • Auth: personal access token (X-Access-Token header, no OAuth dance)

  • Transport: stdio MCP, install once in your client's config

  • License: MIT — use freely, including commercially

  • Status: v0.3.0, in production use on a real Danish ApS


Why this exists

Dinero (one of the two major Danish accounting platforms) gates developer API access behind a partner approval process that's effectively closed to solo founders and small studios. Billy's API is open: a personal access token, well-documented endpoints, no gatekeeping. This MCP wraps it.

If you run a Danish ApS or sole proprietorship on Billy and you're tired of clicking through the UI for routine bookkeeping, this gives you natural-language control over the entire account, with a built-in safety pattern so the AI can't quietly mess up your books.


Related MCP server: Fortnox MCP Server

Install in 30 seconds (non-technical path)

If you're using Claude Code (or any AI client with shell + filesystem access), copy this prompt into your client and let the agent install everything for you:

Install the billy-mcp from https://github.com/cimalys/billy-mcp into my Claude config so I can manage my billy.dk accounting through chat.

Steps:

  1. Clone https://github.com/cimalys/billy-mcp to ~/billy-mcp

  2. Run cd ~/billy-mcp && npm install && npm run build

  3. Ask me for my Billy API token (I'll get it from billy.dk → Settings → Access tokens → Create new). Save it to ~/billy-mcp/.env as BILLY_API_TOKEN=...

  4. Add the MCP to my user-scope Claude config: claude mcp add --scope user --transport stdio billy --env BILLY_API_TOKEN=<token> -- node ~/billy-mcp/dist/index.js

  5. Confirm with claude mcp list — billy should show as Connected.

When done, run billy_whoami to verify everything works.

The AI handles the cloning, building, token prompting, config edit, and verification. You just paste your token when asked. Total time: under 2 minutes.


Install (technical path)

# 1. Clone
git clone https://github.com/cimalys/billy-mcp.git ~/billy-mcp
cd ~/billy-mcp

# 2. Build
npm install
npm run build

# 3. Get your Billy access token
#    billy.dk → Settings → Access tokens → Create new
#    Tokens do not expire.
echo "BILLY_API_TOKEN=your_token_here" > .env

# 4. Smoke-test the token against the live Billy API
npm run smoke
# Expected: prints your org, three unmatched bank lines, three draft invoices.

# 5. Wire into Claude Code
TOKEN=$(awk -F= '/^BILLY_API_TOKEN=/{sub(/^BILLY_API_TOKEN=/,"",$0); print}' .env | tr -d '\r\n "')
claude mcp add --scope user --transport stdio billy \
  --env "BILLY_API_TOKEN=$TOKEN" \
  -- node "$PWD/dist/index.js"

# 6. Verify
claude mcp list | grep billy   # → "billy: node ... ✔ Connected"

Restart your Claude session. The billy tools become available immediately.


How it's safe — the write-guard

Every mutating tool refuses to execute without confirm: true. Without confirm, the tool returns a dry-run preview of the API call it would have made.

Example — without confirm

{
  "tool": "billy_create_invoice",
  "input": { "invoice": { "contactId": "abc", "entryDate": "2026-06-14", ... } }
}

Returns:

[DRY RUN — write blocked]

Action: create invoice

Payload that would be sent to Billy:
{
  "invoice": { "contactId": "abc", "entryDate": "2026-06-14", ... }
}

To execute, re-issue the same tool call with the additional field: "confirm": true
(This is a hard guardrail in the MCP — no Billy write happens until you explicitly confirm.)

Example — with confirm

{
  "tool": "billy_create_invoice",
  "input": { "invoice": { ... }, "confirm": true }
}

Now the call hits Billy, returns the created invoice, and logs to stderr:

[billy-mcp 2026-06-14T09:25:00Z] WRITE confirmed → create invoice

This means an AI client can never autonomously write to your books in a single turn. It can suggest, preview, and explain — but the actual mutation requires you (or the LLM, with your visible agreement) to issue a second call with confirm.

Read tools are not gated. Listing, getting, reports, audit log, reference data — all fire immediately. The guardrail protects only state changes.


Tool catalog (65 tools)

Organisation

  • billy_whoami — current org details (read-only)

  • billy_update_organization — update fields like VAT no, payment terms, address (write-guarded)

Invoices

  • billy_list_invoices, billy_get_invoice — read

  • billy_create_invoice, billy_update_invoice, billy_delete_invoice — write-guarded

  • billy_approve_invoice — draft → approved (write-guarded)

  • billy_send_invoice — send by email via Billy (write-guarded)

Bills (expenses / supplier invoices)

  • billy_list_bills, billy_get_bill — read

  • billy_create_bill, billy_update_bill, billy_delete_bill, billy_approve_bill — write-guarded

Contacts & contact persons

  • billy_list_contacts, billy_get_contact, billy_list_contact_persons — read

  • billy_create_contact, billy_update_contact, billy_delete_contact, billy_create_contact_person — write-guarded

Products

  • billy_list_products, billy_get_product — read

  • billy_create_product, billy_update_product, billy_delete_product — write-guarded

Chart of accounts

  • billy_list_accounts, billy_get_account, billy_list_account_groups — read

  • billy_create_account, billy_update_account — write-guarded

Bank accounts, payments, lines (reconciliation)

  • billy_get_bank_account, billy_list_bank_payments, billy_list_bank_lines, billy_get_bank_line — read

  • billy_create_bank_account, billy_create_bank_payment, billy_match_bank_line, billy_unmatch_bank_line — write-guarded

Daybook (manual journal entries)

  • billy_list_daybooks, billy_list_daybook_transactions — read

  • billy_create_daybook_transaction — write-guarded

Tax (MOMS / VAT)

  • billy_list_tax_rates, billy_list_tax_rulesets, billy_list_sales_tax_returns, billy_get_sales_tax_return — read

Reports

  • billy_report_profit_loss — P&L for a date range

  • billy_report_balance — balance sheet at a point in time

  • billy_report_sales_tax — MOMS report

  • billy_report_invoiced_sales — invoiced sales by contact/product

Postings & audit

  • billy_list_postings — underlying ledger entries

  • billy_list_action_stream — recent activity log

Webhooks

  • billy_list_webhooks — read

  • billy_create_webhook, billy_delete_webhook — write-guarded

Reference data (all read-only)

  • billy_list_currencies, billy_list_countries, billy_list_payment_terms_modes, billy_list_units

Files / attachments

  • billy_list_files, billy_get_file — read

  • billy_upload_file — upload a local PDF/JPG/PNG/GIF to Billy; returns id (write-guarded; other extensions rejected — Billy v2 limitation)

  • billy_attach_file_to_bill, billy_attach_file_to_invoice — write-guarded


Example prompts after install

"Run billy_whoami and tell me my org details."

"Show me my outstanding invoices and total revenue."

"List unmatched bank lines from the last 30 days and propose a category for each."

"Draft an invoice to Acme ApS for 45,000 DKK for the AI Readiness Audit,
payment terms net 14. Show me the dry run first."

"Generate a P&L for 2026-06-01 to 2026-06-13."

"What was my biggest expense category this month?"

"Set my organisation's payment terms to net 14 days. Preview first."

"Upload /Users/me/Downloads/aws-jun.pdf to Billy, create a bill for AWS
240 EUR for cloud infrastructure, and attach the file. Preview each
step before confirming."

Billy v2 API quirks this MCP works around

Three behaviours that aren't obvious from Billy's docs, patched in this MCP so you don't have to learn them the hard way:

  1. PUT /v2/organization returns 405 — the singular endpoint is GET-only. Org updates have to go to the plural endpoint with the org id: PUT /v2/organizations/<id>. billy_update_organization resolves the id from whoami first, then hits the right endpoint.

  2. GET /v2/bankAccounts returns 405 — Billy v2 doesn't let you list bank accounts. Single lookup by id works (billy_get_bank_account). For the default invoice bank account id, read it from billy_whoami → organization.defaultInvoiceBankAccountId.

  3. GET /v2/bankLines requires accountId — the API rejects unfiltered list calls. The MCP marks accountId as required and points you at whoami for the default.

  4. GET /v2/paymentMethods returns 404 — the resource doesn't actually exist on Billy v2, despite appearing in some references. Tool removed in 0.3.0.

  5. /v2/files is append-only. No DELETE (single or bulk), no PUT — files uploaded via the API cannot be removed or modified via the API. Cleanup must happen through the Billy web UI. No file-delete tool is included in this MCP because Billy doesn't expose one.

  6. /v2/files only accepts pdf, jpg, jpeg, png, gif. Any other extension returns a generic 422. billy_upload_file validates up-front (0.3.2+) so callers get a clean error message instead.


Limitations & non-goals

  • Not a substitute for a Danish revisor. Bogføringsloven 2022 puts the responsibility for accurate bookkeeping on company management. This MCP makes the AI a useful assistant; it does not replace professional accounting signoff.

  • No autonomous loop. This is deliberately a single-tool-call MCP. If you want an agent that polls bank lines nightly and auto-categorises them, that's a separate layer you'd build on top.

  • Personal-token auth only. OAuth flow not implemented (no need for a single-user setup; would be needed for SaaS multi-tenant).

  • File upload requires a local path. billy_upload_file reads from a path on the same machine the MCP runs on. To ingest a receipt that lives only in your email, either save it to disk first or forward it to your Billy bill-capture address (organization.billEmailAddress from whoami) — Billy auto-creates a draft bill.


Contributing

Issues and PRs welcome. Particular interest in:

  • File upload tool (multipart POST /v2/files)

  • Webhook receiver pattern for real-time reconciliation

  • Coverage of the umbrellaUsers and apps endpoints (multi-org)

  • Equivalent MCPs for Dinero once their API opens up


Author

Maintained by Cimalys ApS — Copenhagen.

If you'd like a Billy MCP deployed for your firm, a custom integration with other Danish business systems, or general AI implementation work, get in touch at nlej@cimalys.com.

Available Tools

65 tools
billy_approve_billA

Approve a draft bill. WRITE — requires confirm:true.

Inputs: id (required), confirm.

billy_approve_invoiceA

Approve a draft invoice (draft → approved). WRITE — requires confirm:true. Required before sending or marking paid.

Inputs: id (required), confirm.

billy_attach_file_to_billA

Attach an uploaded file (receipt PDF, etc.) to a bill. WRITE — requires confirm:true.

Inputs: billId (required), fileId (required), confirm.

billy_attach_file_to_invoiceA

Attach an uploaded file to an invoice. WRITE — requires confirm:true.

Inputs: fileId (required), invoiceId (required), confirm.

billy_create_accountB

Create a new account in the chart of accounts. WRITE — requires confirm:true.

Inputs: account (required), confirm.

billy_create_bank_accountA

Create a bank account in Billy. WRITE — requires confirm:true.

Inputs: bankAccount (required), confirm.

billy_create_bank_paymentA

Create a bank payment. WRITE — requires confirm:true. Used to mark invoices/bills as paid. Shape: { entryDate, cashSide: 'debit'|'credit', cashAmount, cashAccountId, contraAccountId, associations: [{ subjectType, subjectId, amount }] }

Inputs: bankPayment (required), confirm.

billy_create_billA

Create a new bill. WRITE — requires confirm:true. Shape: { contactId, entryDate, currencyId, lines: [{ accountId, description, amount, taxRateId? }] }

Inputs: bill (required), confirm.

billy_create_contactA

Create a new contact. WRITE — requires confirm:true. Shape: { name, isCustomer, isSupplier, countryId, street?, city?, zipcode?, registrationNo?, email?, phone? }

Inputs: contact (required), confirm.

billy_create_contact_personA

Create a contact person. WRITE — requires confirm:true. Shape: { contactId, name, email?, phone?, isPrimary? }

Inputs: person (required), confirm.

billy_create_daybook_transactionA

Create a manual journal entry. WRITE — requires confirm:true. Shape: { daybookId, entryDate, description, lines: [{ accountId, side: 'debit'|'credit', amount, taxRateId? }] }

Inputs: transaction (required), confirm.

billy_create_invoiceA

Create a new invoice (default state: draft). WRITE — requires confirm:true. Shape: { contactId, entryDate, currencyId, lines: [{ productId, description, quantity, unitPrice, taxRateId? }], paymentTermsDays?, lineDescription? }

Inputs: invoice (required), confirm.

billy_create_productA

Create a product/service. WRITE — requires confirm:true. Shape: { name, productNo?, description?, prices: [{ unitPrice, currencyId }], salesAccountId?, salesTaxRulesetId? }

Inputs: product (required), confirm.

billy_create_webhookA

Create a webhook. WRITE — requires confirm:true. Shape: { url, event, isActive? }

Inputs: webhook (required), confirm.

billy_delete_billB

Delete a draft bill. WRITE — requires confirm:true.

Inputs: id (required), confirm.

billy_delete_contactA

Delete a contact. WRITE — requires confirm:true. Contacts with associated invoices/bills cannot be deleted.

Inputs: id (required), confirm.

billy_delete_invoiceA

Delete a draft invoice. WRITE — requires confirm:true. Approved/paid invoices cannot be deleted.

Inputs: id (required), confirm.

billy_delete_productA

Delete a product. WRITE — requires confirm:true.

Inputs: id (required), confirm.

billy_delete_webhookA

Remove a webhook. WRITE — requires confirm:true.

Inputs: id (required), confirm.

billy_get_accountA

Fetch one account. READ-ONLY.

Inputs: id (required).

billy_get_bank_accountA

Fetch one bank account by id. READ-ONLY. (Billy v2 does not support listing all bank accounts — look up the default via billy_whoami.)

Inputs: id (required).

billy_get_bank_lineB

Fetch one bank line. READ-ONLY.

Inputs: id (required).

billy_get_billB

Fetch one bill (expense). READ-ONLY.

Inputs: id (required).

billy_get_contactB

Fetch one contact. READ-ONLY.

Inputs: id (required).

billy_get_fileB

Fetch metadata for one file (incl. downloadUrl). READ-ONLY.

Inputs: id (required).

billy_get_invoiceB

Fetch one invoice including line items. READ-ONLY.

Inputs: id (required).

billy_get_productC

Fetch one product. READ-ONLY.

Inputs: id (required).

billy_get_sales_tax_returnB

Fetch one MOMS/VAT return. READ-ONLY.

Inputs: id (required).

billy_list_account_groupsA

List the account-group hierarchy. READ-ONLY.

Inputs: no named parameters.

billy_list_accountsC

List the chart of accounts. READ-ONLY.

Inputs: page, pageSize, isArchived.

billy_list_action_streamC

Recent activity log entries. READ-ONLY.

Inputs: page, pageSize.

billy_list_bank_linesA

List bank statement lines. READ-ONLY. REQUIRES accountId. Default returns UNMATCHED lines (the actionable state).

Inputs: accountId (required), page, pageSize, isMatched, +2 more.

billy_list_bank_paymentsC

List bank payments (records of money in/out tied to invoices/bills). READ-ONLY.

Inputs: page, pageSize, contactId, sinceDate, +1 more.

billy_list_billsC

List bills/expenses. READ-ONLY.

Inputs: page, state, pageSize, contactId, +2 more.

billy_list_contact_personsC

List contact persons. READ-ONLY.

Inputs: contactId.

billy_list_contactsC

List contacts. READ-ONLY.

Inputs: q, page, pageSize, countryId, +2 more.

billy_list_countriesA

List countries. READ-ONLY.

Inputs: no named parameters.

billy_list_currenciesB

List currencies. READ-ONLY.

Inputs: no named parameters.

billy_list_daybooksB

List daybooks. READ-ONLY.

Inputs: no named parameters.

billy_list_daybook_transactionsB

List manual daybook transactions (journal entries). READ-ONLY.

Inputs: page, state, pageSize, daybookId, +2 more.

billy_list_filesB

List files uploaded to Billy. READ-ONLY.

Inputs: page, pageSize.

billy_list_invoicesB

List invoices. READ-ONLY. Filters: state (draft/approved/paid), contactId, sinceDate, untilDate.

Inputs: page, state, pageSize, contactId, +2 more.

billy_list_payment_terms_modesA

List payment-terms modes. READ-ONLY.

Inputs: no named parameters.

billy_list_postingsC

List ledger postings. READ-ONLY.

Inputs: page, pageSize, accountId, sinceDate, +1 more.

billy_list_productsC

List products (your sellable items/services). READ-ONLY.

Inputs: q, page, pageSize.

billy_list_sales_tax_returnsC

List MOMS/VAT returns. READ-ONLY.

Inputs: page, state, pageSize.

billy_list_tax_ratesA

List configured tax rates. READ-ONLY.

Inputs: no named parameters.

billy_list_tax_rulesetsA

List tax rulesets. READ-ONLY.

Inputs: no named parameters.

billy_list_unitsB

List product units. READ-ONLY.

Inputs: no named parameters.

billy_list_webhooksB

List webhooks. READ-ONLY.

Inputs: no named parameters.

billy_match_bank_lineA

Reconcile a bank line with an invoice/bill/bank payment. WRITE — requires confirm:true.

Inputs: subjectId (required), bankLineId (required), subjectType (required), confirm.

billy_report_balanceC

Balance sheet at a point in time. READ-ONLY.

Inputs: date (required).

billy_report_invoiced_salesB

Invoiced sales by contact/product. READ-ONLY.

Inputs: toDate (required), fromDate (required).

billy_report_profit_lossC

P&L report. READ-ONLY.

Inputs: toDate (required), fromDate (required), accountGroupId.

billy_report_sales_taxC

MOMS/VAT report. READ-ONLY.

Inputs: toDate (required), fromDate (required).

billy_send_invoiceA

Send an invoice by email via Billy. WRITE — requires confirm:true. Email payload: { to: ['email'], cc?, bcc?, subject?, message?, attachPdf? }

Inputs: id (required), email (required), confirm.

billy_unmatch_bank_lineA

Remove a bank-line reconciliation. WRITE — requires confirm:true.

Inputs: associationId (required), confirm.

billy_update_accountC

Update an existing account. WRITE — requires confirm:true.

Inputs: id (required), patch (required), confirm.

billy_update_billB

Update an existing bill. WRITE — requires confirm:true.

Inputs: id (required), patch (required), confirm.

billy_update_contactA

Update an existing contact. WRITE — requires confirm:true.

Inputs: id (required), patch (required), confirm.

billy_update_invoiceA

Update an existing invoice. WRITE — requires confirm:true.

Inputs: id (required), patch (required), confirm.

billy_update_organizationA

Update fields on the organization. WRITE — requires confirm:true. Pass any subset of: { name, registrationNo, vatNo, street, zipcode, city, countryId, email, phone, paymentTermsDays, defaultTaxMode, salesTaxPeriod, isVatExempted, ... }

Inputs: patch (required), confirm.

billy_update_productA

Update an existing product. WRITE — requires confirm:true.

Inputs: id (required), patch (required), confirm.

billy_upload_fileA

Upload a file (receipt or invoice PDF, or image) from a local path to Billy. WRITE — requires confirm:true. Billy only accepts PDF, JPG, JPEG, PNG, GIF — convert other formats first. Returns the file metadata including the id, which you then pass to billy_attach_file_to_bill or billy_attach_file_to_invoice. MIME type is auto-detected from the extension.

Inputs: path (required), confirm, filename, contentType.

billy_whoamiA

Get the current Billy organization (sanity check the token, see org settings). READ-ONLY.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.4/5.0

    Scored across 65 tools

    Disambiguation5/5

    Every tool targets a distinct resource and action (e.g., billy_create_invoice vs billy_create_bill). Even similar operations like approve_* are clearly differentiated by resource type. No overlapping or ambiguous tool purposes.

    Naming Consistency5/5

    All tools follow the exact pattern billy_verb_noun (e.g., billy_create_invoice, billy_list_invoices, billy_delete_invoice). Verbs and nouns are consistently used, making the API predictable.

    Tool Count4/5

    65 tools is high but appropriate for a full-featured accounting system covering CRUD, reports, files, bank reconciliation, and webhooks. The number is justified by the domain breadth, though on the upper end of reasonable.

    Completeness4/5

    Core CRUD is present for most entities (bills, invoices, contacts, products, accounts). Coverage includes files, bank reconciliation, daybook transactions, reports. Minor gaps: no update/delete for bank accounts or bank lines beyond match/unmatch.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    • An MCP server that provides read access to your cloud storage providers, bank accounts and more.

      https://api.thoughtbox.ai/mcp
    • MCP server for Codat — companies, connections, invoices, bills and financial statements.

      https://codat.usefulapi.io/mcp
    • e-conomic MCP server for ChatGPT, Claude and any AI agent: create draft invoice, book invoice, add customer, list unpaid invoices and create entry in your own e-conomic account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to e-conomic yourself; Cotlet does not store your password and you need no e-conomic API key. About the official e-conomic API: Yes, e-conomic has a full REST API with customers, draft and booked invoices, invoice PDFs and journal vouchers (checked 14 Oct 2026), but you get it through a developer agreement: you create an app that holds an AppSecretToken, and each accounting user grants it access with an AgreementGrantToken. The developer docs list no official MCP server. Setup and FAQ: https://cotlet.dev/integrations/e-conomic-api-mcp-server

    • Bokio MCP server for ChatGPT, Claude and any AI agent: create invoice, send invoice, upload receipt, create journal entry and list unpaid invoices in your own Bokio account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Bokio yourself; Cotlet does not store your password and you need no Bokio API key. About the official Bokio API: Yes, Bokio has a public REST API for invoices, customers, journal entries, uploads and supplier invoices (checked 14 Oct 2026), but it depends on your price plan and on the kind of integration: a private integration token works for one company only, and a tool that serves several companies must be registered as a public integration with OAuth 2.0. Bokio publishes no MCP server of its own. Setup and FAQ: https://cotlet.dev/integrations/bokio-api-mcp-server

      https://cotlet.dev/mcp/v1/bokio

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      MCP server to interact with the Cuéntica accounting API, allowing users to manage invoices, expenses, income, clients, providers, and bank accounts via natural language.
      59
      13 PyPI
      2
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      An MCP server for integrating with the Fortnox Swedish accounting system. Enables LLMs to manage invoices, customers, suppliers, orders, accounts, vouchers, and provides business intelligence analytics.
      51
      68 npm
      47
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Comprehensive MCP server for QuickBooks Online providing full CRUD operations on 29 entities (customers, invoices, bills, etc.) and 11 financial reports, enabling accounting data management via natural language.
      Apache 2.0