Holded MCP server for AI agents

Live

BEARER TOKEN

ACCOUNTING

Accounting & Finance

Holded gives your agent business management in one account: create and send invoices and estimates, record payments, manage contacts and CRM leads, track products and warehouse stock, and log time on projects.

  • Per-user credentials: each call uses the actual user's access token, never a shared bot.
  • Encrypted per-tenant vault: AES-256, resolved at request time, never in LLM context.
  • Scoped before every call: pre-call scope check, 90-day SIEM-exportable audit chain.
Holded
agent · Acme Q3
Run
Which sales invoices from last month are still unpaid?
S
holded_invoices_list
127ms
Back office agent
9 invoices from September are unpaid, totaling €18,430. Distribuciones Norte owes the most at €5,200.
Sources: Holded sales invoices, 9 unpaid
holded
9 invoices
18:29
Message Claude...

Tools your back office agent reaches for on Holded, scoped per user.

CALL ANY TOOL
109 tools for accounting in Holded: create, send, and convert invoices, estimates, and orders, record payments and ledger entries, manage contacts and funnel leads, track product stock, and log project time.
holded_accounting_account_create
Create accounting account
Create an account in the chart of accounts, using a group prefix and optionally a specific number. Returns the id of the affected record. Use to add ledger accounts; prefix is the 1-4 digit group code, not the full account number.
Parameters
Name
Type
Required
Description
name
string
Required
Display name for the accounting account. Example: Acme Corp.
prefix
integer
Required
Chart-of-accounts group code (1-4 digits). The full account number is auto-assigned within this group when `number` is absent. Must be 1-4 digits. Example: 4300.
color
string
Optional
Hex color code for UI display. Example: blue.
description
string
Optional
Optional description of the account purpose. Example: Consulting services for Q1.
number
integer
Optional
Exact account number to assign (e.g. 70000013). Must start with `prefix`. When provided the number is used as-is and must be unique; otherwise the next available number in the group is assigned.
holded_accounting_accounts_list
List accounting accounts
holded_bank_account_get
Get bank account
holded_contact_update
Update contact
holded_contacts_list
List contacts
holded_contacts_search
Search contacts
holded_credit_note_get
Get credit note
holded_document_convert
Convert document
holded_estimate_create
Create estimate
holded_estimate_send
Send estimate
holded_funnel_get
Get funnel
holded_funnels_list
List funnels
holded_invoice_cancel
Cancel invoice
holded_invoice_payment_create
Create invoice payment
holded_lead_stage_update
Update lead stage
holded_ledger_entry_get
Get ledger entry
holded_payment_create
Create payment
holded_payments_list
List payments
holded_product_stock_get
Get product stock
holded_product_update
Update product
holded_project_create
Create project
holded_project_summary_get
Get project summary
holded_project_time_entries_list
List project time entries
holded_purchase_payment_create
Create purchase payment
holded_purchase_update
Update purchase
holded_remittances_list
List remittances
holded_sales_order_get
Get sales order
holded_service_create
Create service
holded_tags_list
List tags
holded_task_get
Get task

For more tools, view docs.

Build your Agent
Same auth pattern across LangChain, OpenAI, Anthropic, and Google ADK.
Python · LlamaIndex
import { ScalekitClient } from "@scalekit-sdk/node";
import { createReactAgent } from "@langchain/langgraph/prebuilt";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);

// Holded tools scoped to this user
const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["holded"], toolNames: [
    "holded_invoices_list",
    "holded_invoice_send",
    "holded_contacts_list"] },
  pageSize: 100,
});

const agent = createReactAgent({ llm, tools });
await agent.invoke({ messages: [{ role: "user", content: "Find last month's unpaid invoices and email each one to its customer again" }] });
import OpenAI from "openai";
import { ScalekitClient } from "@scalekit-sdk/node";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);
const openai = new OpenAI();

const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["holded"] }, pageSize: 100,
});

const res = await openai.chat.completions.create({
  model: "gpt-5",
  messages: [{ role: "user", content: "Find last month's unpaid invoices and email each one to its customer again" }],
  tools,
});

// Execute the tool call with the user's vaulted Holded access token
await sk.tools.executeTool(res.choices[0].message.tool_calls[0], "user_123");
import Anthropic from "@anthropic-ai/sdk";
import { ScalekitClient } from "@scalekit-sdk/node";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);
const anthropic = new Anthropic();

const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["holded"] }, pageSize: 100,
});

const msg = await anthropic.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Find last month's unpaid invoices and email each one to its customer again" }],
  tools,
});

// Tool call runs with the user's vaulted Holded access token
await sk.tools.executeTool(msg.content, "user_123");
import { Agent } from "@google/adk/agents";
import { ScalekitClient } from "@scalekit-sdk/node";

const sk = new ScalekitClient(env.SCALEKIT_ENV_URL, env.SCALEKIT_CLIENT_ID, env.SCALEKIT_CLIENT_SECRET);

const { tools } = await sk.tools.listScopedTools("user_123", {
  filter: { connectionNames: ["holded"] }, pageSize: 100,
});

const agent = new Agent({
  name: "back_office_agent",
  model: "gemini-2.5-pro",
  instruction: "Holded tools scoped to this user",
  tools,
});

await agent.run("Find last month's unpaid invoices and email each one to its customer again");
Try these prompts
Copy any prompt into your agent. Each maps directly to a Holded tool. Click to copy, paste into your agent, done.
Invoicing
Copy the prompt
Copied
Create an invoice for Distribuciones Norte for 40 units of SKU-118.
Copy the prompt
Copied
Email invoice F260112 to the customer.
Copy the prompt
Copied
Convert the accepted estimate for Bodega Sur into an invoice.
CRM and contacts
Copy the prompt
Copied
Add a lead for Café Lumen to the Wholesale funnel.
Copy the prompt
Copied
Move the Café Lumen lead to the Proposal stage.
Copy the prompt
Copied
Find contacts whose name starts with Mar.
Stock and projects
Copy the prompt
Copied
What is the stock of SKU-118 in each warehouse?
Copy the prompt
Copied
Lower SKU-118 stock in the Madrid warehouse by 12.
Copy the prompt
Copied
Log 4 hours on the Website Relaunch project for today.
SEE HOW AUTH WORKS
Each user connects their own Holded access token once, and Scalekit sends it with every call. Access tokens stay vaulted, every call is scope checked, and every action is logged.
1
Authorize
Your user connects
Holded
once. We tie it to their identity and the meetings they approved — no shared bot account, no org-wide access
Who:
user ‘A’
when:
Once per user
access:
Limited to user
2
Store
Their
Holded
token lives in a vault scoped to them. User A's meetings are never reachable by an agent acting for user B, even on the same connection
vault:
encrypted
scope:
per-user
tokens:
auto-refreshed
3
Resolve
When your agent calls a
Holded
tool, we fetch the right token server-side. It never touches your agent, never appears in the LLM context, never shows up in your logs
speed:
~40ms
check:
before every call
seen by:
nobody
4
Audit
Every
Holded
tool call is logged — who triggered it, which meeting was fetched, what came back. 90 days of history, tied to the user who authorized it
history:
90 days
export:
SIEM-ready
logged:
every call
Test other agents
See the same per-user auth pattern across other accounting and CRM connectors.
GTM and RevOps Teams
Revenue forecast commentary
Pulls open pipeline from Salesforce and HubSpot, calculates coverage against quota, flags at-risk stages, posts commentary to Slack, and logs every snapshot to Google Sheets.
GTM and RevOps Teams
CRM AI agent
Reads the Granola transcript after every call, extracts next steps and updates the HubSpot record, drafts the follow-up in Gmail, and confirms in Slack, all on the rep's own delegated OAuth.
Test other agents
See the same per-user auth pattern across other accounting and CRM connectors.
GTM
Revenue forecast agent
Score pipeline coverage against quota across Salesforce and HubSpot, post forecast commentary to Slack, log snapshots to Sheets.
GTM
CRM AI agent
Turn each Granola call transcript into a HubSpot record update, a drafted Gmail follow-up, and a Slack recap.
Why Scalekit
Secure your agent's access. Connectors ship in minutes
01.
Shared tokens break per-user analytics
A shared Holded access token looks fine in a demo. In production every invoice and ledger entry looks like one service account, and you cannot tell which user triggered it. Scalekit resolves the credential of the actual user who triggered the agent, never a shared bot.
// shared key
audit → bot_service_account

// scalekit
audit → user_abc ✓
02.
Authentication is not authorization
03.
Multi-tenancy is architectural
04.
Holded today. Ten connectors tomorrow.
“Our agents act across Salesforce, Gong, Google Drive, and more, on behalf of every customer. Scalekit behind the scenes meant we can keep adding tools without ever rebuilding how credentials or tool calling work.”
Venu Madhav Kattagoni
Head of Engineering / Von
FAQs
Frequently Asked Questions
Does the agent access Holded as the user or through a shared access token?
As the user. Each user connects their own Holded access token once, and Scalekit sends it with every call. Audit logs attribute every invoice, payment, and ledger entry to that user, not a shared service account.
Where is the Holded access token stored?
In Scalekit's managed AES-256 token vault, namespaced per tenant. To rotate it, the user reconnects with a new access token. Revocation is a single dashboard action. Credentials never appear in prompts, logs, or LLM context.
Can I limit what the agent does in Holded?
Yes. Filter by tool name in listScopedTools to expose only what you want, for example the list and get tools without holded_invoice_cancel, which voids a sales invoice, or holded_ledger_entry_create, which posts manual journal entries. Scalekit also enforces scope checks before every API call.
What happens when a user revokes Holded access?
The connection is invalidated on the next tool call. Subsequent requests for that user fail closed with a clear error. Other users in the tenant remain unaffected. The event is logged for audit.
Does the agent reach Holded projects and team data too?
Yes. Besides invoicing and accounting, the tools list projects, tasks, and time entries, create tasks and log time on projects, and read employees and time-off requests such as holidays and sick leave.
Start in your coding agent
Up and running in one command
Install the Scalekit skill in your editor of choice. Connector, auth, tools, prompt, all wired up
Claude Code REPL
/plugin marketplace add scalekit-inc/claude-code-authstack
/plugin install agentkit@scalekit-auth-stack
Cursor Code REPL
# ~/.cursor/mcp.json
{
""mcpServers"": {
""holded"": {
""url"": ""https://mcp.scalekit.com/holded"",
""headers"": { ""Authorization"": ""Bearer $SCALEKIT_TOKEN"" }
}
}
}
Codex Code REPL
# ~/.codex/config.toml
[mcp_servers.holded]
url = ""https://mcp.scalekit.com/holded""
auth_env = ""SCALEKIT_TOKEN""
Copilot Code REPL
# .vscode/mcp.json
{
""servers"": {
""holded"": {
""url"": ""https://mcp.scalekit.com/holded"",
""type"": ""http""
}
}
}