LedgerBee Developer
  • Getting started
  • Conventions
  • Products
  • Advisor firms
  • Configuration
  • API Reference
SSO
MCP (AI agents)
Configuration

MCP (AI agents)

The LedgerBee MCP server gives AI agents (Claude, ChatGPT, Cursor, and other MCP clients) a curated tool set rather than an auto-generated wrapper around every REST endpoint. Transport is Streamable-HTTP; authorization is OAuth 2.1 with a browser sign-in. Add the server URL to your client and approve the sign-in — no API keys required.

One server per company

A LedgerBee MCP connection is scoped to exactly one company: the OAuth grant behind it binds to a single company, and every tool call runs against that company's books. Two URL forms exist:

URLUse
https://api.ledgerbee.com/api/v1/mcpSingle company — binds to the company you pick at sign-in
https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId}One connection per company — the path pins which company the grant binds to

The /c/{appCompanyId} discriminator exists for users with access to more than one company. MCP clients dedupe connections by URL and server name, so without it only one LedgerBee connection could be registered. With it, each company gets a distinct URL + server name pair (ledgerbee-{company}), and an agent can hold one connection per company side by side.

Marketplace → Connected apps in the LedgerBee app renders the setup below pre-filled for the company you're signed in to — URL, server name, and per-client commands, including a one-click Add to Cursor button and buttons that copy the URL and open Claude's and ChatGPT's settings.

Connect a client

Use the plain server URL — the connection binds to the company you pick at sign-in:

Code
https://api.ledgerbee.com/api/v1/mcp

Claude (Web / Desktop) — both share one connector list:

  1. Open claude.ai/customize/connectors (Settings → Connectors).
  2. Click Add custom connector and paste the server URL.
  3. Complete the LedgerBee sign-in and approve the requested access.

ChatGPT — requires a Plus, Pro, Business, Enterprise, or Edu plan:

  1. Turn on Developer mode under Settings → Security and login.
  2. Open chatgpt.com/plugins, create an app, and paste the server URL. Choose OAuth as the authentication.
  3. Complete the LedgerBee sign-in and approve the requested access.
  4. Select the app in the message composer to use it in a conversation.

On a Business or Enterprise workspace, an admin must allow custom MCP apps in the workspace settings first. OpenAI moves these menus between releases; the current path is in OpenAI's Developer mode help article.

Claude Code — run this in your terminal, then run /mcp inside Claude Code to complete the OAuth sign-in:

TerminalCode
claude mcp add --transport http ledgerbee --scope user https://api.ledgerbee.com/api/v1/mcp

Cursor — Add to Cursor, or paste this into your mcp.json:

Code
{ "mcpServers": { "ledgerbee": { "url": "https://api.ledgerbee.com/api/v1/mcp" } } }

VS Code — run this in your terminal (requires the VS Code CLI):

TerminalCode
code --add-mcp '{"name":"ledgerbee","url":"https://api.ledgerbee.com/api/v1/mcp"}'

Codex:

TerminalCode
codex mcp add ledgerbee --url https://api.ledgerbee.com/api/v1/mcp

Other clients — most accept the universal installer; the JSON snippet above also works in most clients' MCP config. The full client list is at modelcontextprotocol.io/clients.

TerminalCode
npx add-mcp https://api.ledgerbee.com/api/v1/mcp

Register one connection per company with the company-pinned URL. The commands below carry two placeholders — {appCompanyId} and the server name ledgerbee-{company} — replace both, or copy the pre-filled commands from Marketplace → Connected apps. To add the next company, switch company in the app and reopen the card.

Code
https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId}

Claude (Web / Desktop) — both share one connector list:

  1. Open claude.ai/customize/connectors (Settings → Connectors).
  2. Click Add custom connector and paste the company-pinned URL.
  3. Complete the LedgerBee sign-in and approve the requested access.

ChatGPT — requires a Plus, Pro, Business, Enterprise, or Edu plan:

  1. Turn on Developer mode under Settings → Security and login.
  2. Open chatgpt.com/plugins, create an app, and paste the company-pinned URL. Choose OAuth as the authentication.
  3. Complete the LedgerBee sign-in and approve the requested access.
  4. Select the app in the message composer to use it in a conversation.

On a Business or Enterprise workspace, an admin must allow custom MCP apps in the workspace settings first. OpenAI moves these menus between releases; the current path is in OpenAI's Developer mode help article.

Claude Code — run this in your terminal, then run /mcp inside Claude Code to complete the OAuth sign-in:

TerminalCode
claude mcp add --transport http ledgerbee-{company} --scope user https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId}

Cursor — the one-click Add to Cursor button lives in the in-app card (the deep link needs a concrete URL). Or paste this into your mcp.json:

Code
{ "mcpServers": { "ledgerbee-{company}": { "url": "https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId}" } } }

VS Code — run this in your terminal (requires the VS Code CLI):

TerminalCode
code --add-mcp '{"name":"ledgerbee-{company}","url":"https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId}"}'

Codex:

TerminalCode
codex mcp add ledgerbee-{company} --url https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId}

Other clients — most accept the universal installer; the JSON snippet above also works in most clients' MCP config. The full client list is at modelcontextprotocol.io/clients.

TerminalCode
npx add-mcp https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId}

Anonymous developer server

/api/v1/mcp/developer is an anonymous server that exposes a single tool, read_development_docs, over the LedgerBee developer docs. Use it to verify your client setup before connecting the tenant server:

TerminalCode
claude mcp add --transport http ledgerbee-developer --scope user https://api.ledgerbee.com/api/v1/mcp/developer

The developer MCP endpoint in the API Reference renders the same install card with copy-paste setup for popular clients.

Tools

Every tool declares an inputSchema and an outputSchema, and every call returns its result twice: as structuredContent, the typed object the outputSchema describes, and as a text block carrying the same value as JSON. Parse structuredContent — for a tool whose result is wrapped, below, the two blocks carry different shapes. Read both schemas from tools/list, which is the live contract. Which tools appear there depends on the toolsets the connection has enabled; for a read outside them, reached through call_mcp_tool, call list_mcp_tools with response_format: "detailed", whose rows carry the same two schemas.

structuredContent is always an object. lookup_bookkeeping_rule returns a list of hits and get_business_entity_details returns null for an entity it cannot find, so those two values arrive wrapped as { "result": … }, which is what their outputSchema declares. Their text block carries the value unwrapped.

A connection reaches only what the user who authorized it can reach in LedgerBee. Each scope stands for the permission the app requires for the same data: subscriptions-read needs the subscriptions.read permission and the Subscription licence. The consent screen offers only the scopes the user's role covers, and every call checks the role as it is at that moment, so a scope stops working when the role stops covering it and works again when it does. A call the role does not cover is refused with that reason. Only an admin changing the role clears it; request_permissions cannot.

Read tools — available with the mcp:read scope on a tenant server; some also require the listed domain read scope:

ToolsetResourceToolOperationAdditional scopesAlso requires
every connectiondevelopment_docread_development_docsaction——
every connectionmcp_grantrequest_permissionsaction—user token
every connectionmcp_toollist_mcp_toolslist——
every connectionmcp_toolcall_mcp_toolaction——
contextaccounting_periodget_accounting_periodgetjournal-entries-read—
contextbookkeeping_rulelookup_bookkeeping_ruleactioncompany-read—
contextdimensionlist_dimensionslistdimensions-read—
contextjournal_categorylist_journal_categorieslistjournal-categories-read—
contextledger_accountlist_ledger_accountslistaccounts-readaccounting.read permission
contextledger_accountget_ledger_accountgetaccounts-read, journal-entries-read—
contextvat_codelist_vat_codeslistvat-codes-read—
ledgeraccounting_periodcheck_period_close_readinessactionaccounts-read, journal-entries-readaccounting.read permission
ledgerjournal_entrylist_journal_entrieslist—user token, accounting.read permission
ledgerjournal_entryget_journal_entrygetjournal-entries-read—
ledgerjournal_entrysearch_journal_entriesactionjournal-entries-read—
ledgertax_account_statementget_tax_account_statementgetjournal-entries-read—
ledgervat_readiness_findinglist_vat_readiness_findingslistjournal-entries-read, vat-codes-readaccounting.read permission
ledgervat_reportcheck_vat_readinessactionjournal-entries-read, vat-codes-readaccounting.read permission
ledgervat_reportget_vat_breakdownactionjournal-entries-read, vat-codes-read—
ledger_analysisbank_accountfind_duplicate_bank_postingsactionjournal-entries-readaccounting.read permission
ledger_analysisjournal_entryfind_counterparty_mispostingsactioncustomers-read, journal-entries-read, suppliers-read—
ledger_analysisjournal_entryfind_related_entriesactionjournal-entries-read—
counterpartiescounterpartylist_counterpartieslistcustomers-read or suppliers-readrecords.read permission
counterpartiescounterpartyget_counterpartygetcustomers-read or suppliers-read—
counterpartiescounterpartyfind_duplicate_counterpartiesactioncustomers-read or suppliers-readrecords.read permission
counterpartiescounterparty_grouplist_counterparty_groupslistcustomers-read or suppliers-read—
counterpartiescvr_registrationlookup_cvractioncompany-read—
documentsdocumentlist_documentslistdocuments-read—
documentsdocumentget_documentgetdocuments-read—
documentsdocument_fileget_document_filegetdocuments-read—
bookkeepingdraft_journal_entryget_draft_journal_entrygetjournal-entries-read—
bookkeepingopen_entrylist_open_entrieslistjournal-entries-read—
bookkeepingopen_entryget_open_entrygetjournal-entries-read—
stagingstaged_writelist_staged_writeslist—user token
stagingstaged_writeget_staged_writeget—user token
stagingstaged_writecreate_decision_linkaction—user token
apipublic_apiapi_detailsaction——
apipublic_apiapi_readaction——
apipublic_apiapi_searchaction——
reportscash_flow_forecastget_cash_flow_forecastget—Cashflow licence, user token, cashflow.read permission
reportsfinancial_reportget_financial_reportgetaccounts-read—
reportsinvoicelist_invoiceslistcustomers-read—
reportsinvoiceget_invoice_payment_statusactionjournal-entries-read, customers-read or suppliers-read—
reportsinvoiceget_invoice_remindersactioncustomers-readrecords.read permission
reportsinvoiceget_overdue_invoicesactionjournal-entries-read, customers-read or suppliers-read—
my_worknotificationlist_notificationslist—user token
my_worktasklist_taskslist—user token
bankingbank_accountlist_bank_accountslist—accounting.read permission
bankingbank_paymentlist_bank_paymentslist—BillPayment licence, payments.read permission
bankingbank_reconciliationlist_bank_reconciliationslist—accounting.read permission
bankingbank_transactionlist_bank_transactionslistjournal-entries-read—
bankingbank_transactionget_bank_transactionget—accounting.read permission
bankingcard_account_statementget_card_account_statementget—accounting.read permission
bank_matchingbank_reconciliationget_dismissed_suggestionsaction—accounting.read permission
bank_matchingbank_transactionfind_inbox_document_candidatesaction—accounting.read permission
bank_matchingbank_transactionfind_invoice_anchor_candidatesaction—accounting.read permission
bank_matchingbank_transactionfind_journal_entry_candidatesaction—accounting.read permission
bank_matchingbusiness_entityget_business_entityget—accounting.read permission
bank_matchingbusiness_entityresolve_business_entityaction—accounting.read permission
subscriptionssubscriptionlist_subscriptionslistsubscriptions-readSubscription licence, subscriptions.read permission
subscriptionssubscriptionget_subscriptiongetsubscriptions-readSubscription licence
subscriptionssubscriptionget_saas_metricsactionsubscriptions-readSubscription licence
subscriptionssubscriptionlist_mrr_by_customeractionsubscriptions-readSubscription licence
productsproductlist_productslistproducts-readrecords.read permission
payrollpayroll_runlist_payroll_runslist—PayRoll licence, user token, payroll.read permission
uipageinspect_pageaction—user token
uiroutelist_routeslist——
uiroutefind_viewaction——
dashboardsdashboardlist_dashboardslist—user token, dashboard.read permission
dashboardsdashboardget_dashboardget—user token, dashboard.read permission
budgetsbudgetget_budgetgetbudgets-readBudgets licence
approvalsworkflow_approvallist_workflow_approvalslist—user token
approvalsworkflow_approvalget_workflow_approvalget—user token

Write tools — available with the mcp:write scope, or the listed domain write scope. Writes always require an OAuth grant:

ToolsetResourceToolOperationAdditional scopesAlso requires
every connectionmcp_toolcall_mcp_tool_batchactionmcp:read or mcp:write—
every connectiontool_gapreport_tool_gapaction—user token
counterpartiescounterpartycreate_counterpartycreatecustomers-write or suppliers-writeuser token
counterpartiescounterpartyupdate_counterpartyupdatecustomers-write or suppliers-writeuser token
counterpartiescounterpartychange_supplier_bank_accountactionsuppliers-writeuser token, records.write permission
bookkeepingcredit_notelink_credit_noteactionjournal-entries-writeuser token
bookkeepingdocumentbin_inbox_documentactionjournal-entries-writeuser token, inbox.write permission
bookkeepingdocumentbook_inbox_documentactionjournal-entries-writeuser token, inbox.write permission, with settlement 'pay': suppliers-write scope, payments.write permission, BillPayment licence
bookkeepingdocumentupload_documentactionjournal-entries-write—
bookkeepingdraft_journal_entrycreate_draft_journal_entrycreatejournal-entries-writeuser token
bookkeepingdraft_journal_entryupdate_draft_journal_entryupdatejournal-entries-writeuser token
bookkeepingdraft_journal_entrydelete_draft_journal_entrydeletejournal-entries-writeuser token
bookkeepingdraft_journal_entrybook_journal_entryactionjournal-entries-writeuser token
bookkeepingjournal_entryreverse_journal_entryactionjournal-entries-writeuser token
bookkeepingledger_accountcreate_ledger_accountcreateaccounts-writeuser token
bookkeepingledger_accountupdate_ledger_accountupdateaccounts-writeuser token
bookkeepingopen_entrysettle_open_entriesactionjournal-entries-writeuser token
bookkeepingsettlementunsettle_entriesactionjournal-entries-writeuser token
bookkeepingvat_periodsettle_vat_periodactionjournal-entries-writeuser token
stagingstaged_writeapprove_staged_writeaction—user token
stagingstaged_writecancel_staged_writeaction—user token
stagingstaged_writepresent_for_approvalaction—user token
stagingstaged_writerevise_staged_writeaction—user token
stagingstaged_writestart_chainaction—user token
stagingstaged_writesubmit_chainaction—user token
apipublic_apiapi_writeaction—user token
my_worknotificationdismiss_notificationsaction—user token
my_worknotificationmark_notifications_readaction—user token
bankingbank_paymentpay_supplier_billactionsuppliers-writeBillPayment licence, user token, payments.write permission
bank_matchingbank_transactiondismiss_bank_transactionactionbank-reconciliations-writeuser token
bank_matchingbank_transactionlink_bank_transactionactionbank-reconciliations-writeuser token
bank_matchingbank_transactionunlink_bank_transactionactionbank-reconciliations-writeuser token
uipagefire_actionaction—user token
uipagehighlight_rowsaction—user token
uipagespotlightaction—user token
uiroutenavigateaction—user token
dashboardsdashboardcreate_dashboardcreate—user token, dashboard.write permission
dashboardsdashboardedit_dashboard_global_configaction—user token, dashboard.write permission
dashboardsdashboardedit_dashboard_layoutaction—user token, dashboard.write permission
dashboardsdashboardedit_dashboard_metadataaction—user token, dashboard.write permission
dashboardsdashboardsave_dashboardaction—user token, dashboard.write permission
dashboardsdashboard_widgetcreate_dashboard_widgetcreate—user token, dashboard.write permission
dashboardsdashboard_widgetupdate_dashboard_widgetupdate—user token, dashboard.write permission
dashboardsdashboard_widgetdelete_dashboard_widgetdelete—user token, dashboard.write permission
budgetsbudgetset_budget_figuresactionbudgets-writeBudgets licence, user token
approvalsworkflow_approvaldecide_workflow_approvalsaction—user token, core.write permission
approvalsworkflow_approvalreassign_workflow_approvalaction—user token, core.write permission

Every tool names the toolset it belongs to, the resource it acts on and its operation. A toolset is the job a tool serves; the tools marked every connection belong to none and are always advertised. list, get, create, update and delete tools are named <operation>_<resource>; an action is a lifecycle step or lookup with no CRUD shape, such as booking a draft. A renamed tool stops answering to its old name in the same release.

This table is the full catalog. Call tools/list on the server for the tools your connection advertises, with each tool’s parameters and description.

A connection advertises the tools of its enabled toolsets plus the tools that belong to none. The toolsets are picked on the consent screen, which offers the ones the requested permissions can serve and ticks the recommended set — or, when a client re-authorizes a connection you already have, that connection's own set. An admin changes it afterwards under Settings → Integrations → OAuth clients → Connected apps, where each connection has a toolset checkbox per toolset; the change reaches the client the next time it reconnects. A toolset decides what the connection is shown, not what it may do: a read outside the enabled set stays reachable through call_mcp_tool, and what bounds a write is its scopes and the company's write policy. The page-control and dashboard-editing tools are off unless a connection enables ui or dashboards; their reads stay reachable through call_mcp_tool, and their writes need the toolset ticked on a hosted client. An agent asks for a toolset with request_permissions, the same call it uses for a missing scope.

MCP scopes are OAuth-only and cannot be assigned to API keys. Write tools always require an OAuth grant with the matching scope.

Discovery

Two tools search the catalog and reach a tool by name, so an agent never has to guess whether a capability exists. Both are advertised to every connection.

  • list_mcp_tools searches the full catalog by keyword, toolset or kind. Each row carries the tool's name, toolset, resource, operation and description, and invokeVia: direct when the tool is in this connection's tools/list, call_mcp_tool when it is a read outside it, or enable_toolset when it is a write outside it, in which case enableToolset names the toolset to ask for. blockedReason carries the message tools/list puts on a tool the caller cannot invoke yet, or null when no gate is visible at list time. Called with no arguments it returns the toolset index; a query that matches nothing returns the index and the closest names rather than an empty list. response_format: "detailed" adds each tool's input schema and annotations.
  • call_mcp_tool runs one read tool by name, with the target's own arguments under arguments, and returns that tool's own result. The target is checked exactly as a direct call: its scopes, licence, rate limit and AI budget apply, and a refusal carries the same message. A write tool is refused with its toolset named. Neither discovery tool can be run through it.

A wrapped call is recorded in the MCP audit log as call_mcp_tool:<tool>, with the target's route in targetOperation when the target is a facade tool. The usage statistics report the target under its own name and count the wrapped calls separately.

Resources

The server publishes your company's reference data as MCP resources, so a client can attach it to a conversation instead of spending a tool call on it every session. resources/list returns the ones this connection can read:

URIWhat it holds
ledgerbee://company/{companyId}/chart-of-accountsEvery ledger account you post to — number, name, type, parent
ledgerbee://company/{companyId}/vat-codesYour VAT codes with the ids a draft entry carries
ledgerbee://company/{companyId}/journal-categoriesYour own classification of an entry, with its ids

Each returns application/json, the same payload the matching tool returns — reading both gains nothing.

A read runs as the tool call it is: the same scopes, the same licence check, the same rate limit, and a row in the MCP audit log under resource:<name>. A resource therefore reaches nothing a tool would not, and a connection missing the scope or the toolset behind one is not shown that resource.

The company is in the URI because one person can connect several companies, and a client that keys a cache or a saved attachment by URI would otherwise hold two companies' charts under one name. A URI naming a company other than the one that consented to the connection is not found; it never resolves to yours.

Results carry a one-minute cache hint on protocol revision 2026-07-28, so an account or a VAT code you add is visible to the agent the next time it reads.

There are no resource templates. Every resource is fixed, and the only variable a template would have taken is the company, which your bearer token already decides.

Prompts

prompts/list returns three workflows a client turns into something you pick — a slash command in Claude Code and VS Code, an entry in the conversation in Claude.ai:

PromptArgumentsWhat it asks for
month-end-closeperiodA verdict on whether a month can be closed, and what is in the way. Reads only
book-documentdocumentA draft journal entry for one document in your inbox, grounded in your own chart
reconcile-bank-periodfrom, to, bankAccountA bank account's transactions over a range matched against what is posted, and a list of what is left

A prompt is text. It reads none of your data and calls no tool — it states the job and the order to do it in, and the agent then works through your tools under every gate that already applies, including the write policy. A prompt naming tools this connection is not served is left out of the list.

A host that collects no arguments still gets a usable brief: the step that needed the value says so, and the agent asks you.

Direct mode

The curated tools above cover the common accounting tasks. For everything else, the four tools on the public_api resource call the public REST API directly, as the connection's own access token. Any route the grant can already call over HTTP is reachable through them, and no other route is: the facade grants nothing on its own.

  • Search finds routes by keyword. Each result carries the method, the path template, the required scopes, whether this connection can call it (callable), and curatedTool when a dedicated tool already covers the route.
  • Details describes one route: path and query parameters, the request body schema, the success response shape, required scopes, and deprecation.
  • Read performs a GET and returns the status and JSON body. A body longer than maxBytes (default 64 KB) is cut, and the result names the route's paging parameters.
  • Write performs a POST, PATCH, PUT or DELETE. It takes a one-line summary of the change, shown on the approval card and kept in the audit log.

The agent searches first and uses the curated tool whenever one is named: the curated tools validate input, stage the change for review in LedgerBee, and can be reversed. The facade does none of that.

Enabling writes

Direct writes are off by default. Turn them on under Home → Hivey → Permissions, where the Direct API writes row takes one of three values.

ValueEffect on the write tool
Deny (default)The call is refused. The tool is advertised with an "unavailable" marker that names this setting.
ApprovalThe request is stored and appears in the approval queue. It runs when you approve it, as the same grant that staged it. The tool returns executed: false and a chainId.
AutoThe request runs immediately and the tool returns executed: true with the route's status and body. No approval card is shown.

The three read-side tools have no setting: a read the grant can perform over HTTP is not more dangerous when performed through MCP.

What Auto holds back

Auto commits in the call: the server asks the user nothing first. A curated tool's write that looks risky is staged instead, exactly as under Approval, and waits in the approval queue; the result carries autoCommitHolds naming each reason, and re-reading the proposal adds the same reasons as sentences in autoCommitHeldBecause. A write is held when:

RuleHeld when
Amount ceilingAn amount is at or above the company's Amount ceiling, set under Automatic booking in the Hivey settings. An amount in another currency than the company's is held whenever a ceiling is set, since it cannot be compared with it. No ceiling, no rule.
First postingA line posts to an account the company has never posted to.
First booking against a counterpartyThe entry names a supplier or customer the company has never booked an entry against.
DeleteThe write deletes a draft entry or bins an inbox document.
Large batchThe call is one item of a call_mcp_tool_batch with more than 10 items.

A write with a blocker, and one that redirects money (a payment from an inbox booking, a supplier's bank account), waits for a person as it always has. The Write tool's direct requests are not covered by these rules: under Auto they run as sent.

Tools that drive your browser tab

Some tools act on a live LedgerBee tab and need one paired to the session; each says so in its description. When none is paired and the client declares URL elicitation, the call returns an input_required result asking the user to open a page rather than failing — the approval queue, the connection's permission editor, or the app itself, whichever the call needs them to reach. Retry once they have. A second retry that is still blocked returns the refusal instead of the link again.

Scopes

Search, details and read require mcp:read; write requires mcp:write and a user-consented grant. Every route also enforces its own scopes, exactly as it does for an HTTP client: a read of /v1/customers needs customers-read, and a write of POST /v1/customers needs customers-write. A call the connection cannot make with its scopes, as its user's role narrows them, is refused before any request is made, with the missing scope named. Search reports the same check as callable, so the agent can see which routes it can reach.

Idempotency

Every write call carries an idempotency key. When the agent passes idempotencyKey, that key is sent; otherwise the server derives one from the grant, the method, the route template and the request body. The result echoes the key as idempotencyKey and sets replayed: true when the API served the stored outcome of an earlier identical request instead of writing again. A retry after a timeout therefore creates no duplicate.

Excluded routes

Routes that manage authentication, single sign-on, API keys and the MCP endpoint itself (/v1/auth, /v1/oauth, /v1/api-key, /v1/scim, /v1/portal-sso, /v1/mcp) are never reachable through the facade, whatever scopes the grant holds, and neither is any write to /v1/webhook-endpoints: a webhook endpoint created under prompt injection would be an exfiltration channel. A request to one of them is refused with a message saying so.

Errors

A route that answers 400 or above does not fail the tool call. The result carries error: true, the status, and the route's own error body, so the agent can read a validation message and correct the request. The facade's own refusals raise a tool error: an unknown path, an excluded route, a missing scope, or direct writes set to Deny.

Audit

Every facade call is recorded in the MCP audit log with the tool name, the grant, the outcome, and targetOperation, the route it hit, as "POST /v1/customers". The loopback request carries the same request id as the MCP call, so the API request log and the MCP audit row share one id.

Endpoints

EndpointAuthPurpose
POST /api/v1/mcpOAuth 2.1 bearer, Dynamic Client Registration grant (mcp:read scope)Tenant-scoped server
POST /api/v1/mcp/c/{appCompanyId}sameTenant-scoped server, company pinned in the URL
POST /api/v1/mcp/developerAnonymous (any bearer)Developer/docs server

Authorization (OAuth 2.1)

The tenant-scoped servers follow the MCP authorization spec. A compliant client handles the whole flow on its own: it discovers the authorization server via .well-known, registers itself, and opens the browser sign-in. The metadata endpoints:

  • /.well-known/oauth-protected-resource (and a per-company variant)
  • /.well-known/oauth-authorization-server
  • /.well-known/openid-configuration
  • /.well-known/jwks.json

Clients that self-register use Dynamic Client Registration. An MCP client is a DCR-registered connected app — the same record backs both terms:

Code
POST https://api.ledgerbee.com/api/v1/oauth/register

A partner app that LedgerBee registers for you (rather than self-registering via DCR) runs the same authorization flow. See Connected Apps.

See Authentication for the full OAuth flow.

Last modified on November 6, 2026
SCIM provisioning
On this page
  • One server per company
  • Connect a client
  • Anonymous developer server
  • Tools
  • Discovery
  • Resources
  • Prompts
  • Direct mode
    • Enabling writes
    • What Auto holds back
    • Tools that drive your browser tab
    • Scopes
    • Idempotency
    • Excluded routes
    • Errors
    • Audit
  • Endpoints
  • Authorization (OAuth 2.1)
JSON
JSON