Accounting
Ledger entries, expense and accounting accounts, tax rates and numbering series.
By Juan Cantón Rodríguez · Updated
One consolidated base URL, API-key authentication, cursor pagination and structured errors. Here is what v2 covers, how to authenticate, working code examples, and the real pitfalls when you build or migrate a production integration.
Holded consolidated what used to be several product APIs into a single v2 surface with clear sections. The legacy v1 remains accessible as an archived reference and existing integrations keep working, but all new development targets v2. And, per Holded's own docs, new integrations should use it directly.
v2 uses a per-account API key rather than an OAuth flow for server-to-server integrations. Generate the key in Holded (Settings → Developers), then send it on every request in the key header. The key inherits the permissions of its account, so scope it deliberately.
The API is organised into sections that map to the ERP. Each exposes list, read, create, update and delete operations, plus bulk actions and native attachments where they apply.
Ledger entries, expense and accounting accounts, tax rates and numbering series.
Banking accounts, cash and bank movements, payments, payment methods and remittances.
Invoices, estimates, proformas, credit notes, sales orders, receipts and waybills, with bulk operations.
Products, services, price lists, warehouses, stock levels and in-transit stock, multi-warehouse CRUD.
Contacts and groups, leads, funnels and configurable pipelines shared with your documents.
Projects and time tracking, employees, contracts, events, tasks and the shared inbox.
A minimal read against v2: authenticate with the key header, page with a cursor, and back off on rate limits. The pattern is identical for products, contacts and every other list endpoint.
# List invoices (cursor pagination)
curl -s "https://api.holded.com/api/invoicing/v2/documents/invoice?limit=100" \
-H "accept: application/json" \
-H "key: <YOUR_API_KEY>"
# Follow the cursor returned in the response
curl -s "https://api.holded.com/api/invoicing/v2/documents/invoice?limit=100&cursor=<NEXT_CURSOR>" \
-H "accept: application/json" \
-H "key: <YOUR_API_KEY>"// Cursor loop (pseudo-JS), works the same in any language
let cursor = undefined;
const invoices = [];
do {
const res = await fetch(
`https://api.holded.com/api/invoicing/v2/documents/invoice?limit=100` +
(cursor ? `&cursor=${cursor}` : ""),
{ headers: { accept: "application/json", key: process.env.HOLDED_API_KEY } }
);
if (res.status === 429) { await backoff(); continue; } // respect rate limits
const page = await res.json();
invoices.push(...page.data);
cursor = page.nextCursor; // undefined when there are no more pages
} while (cursor);It is not just the URL. These structural changes break code silently if you do not review each one.
Several product surfaces (Invoice, CRM, Projects, Team, Accounting APIs)
One consolidated base URL with sections (Accounting, Treasury, Sales, Inventory, Contacts, CRM, Projects, Team & HR, Calendar, Inbox)
Change your base URL and route prefixes. Mostly find-and-replace, but review every call.
Offset pagination (page=1, page=2, …)
Cursor pagination (opaque next cursor)
Efficient on large lists, but breaks any code that jumped to page N or computed a total page count.
Ad hoc error messages per endpoint
Structured errors with consistent codes
Your old error handling may not recognise the new shapes. Mandatory review.
Mostly individual operations
Bulk operations for invoices (approve, cancel, delete in batches)
Clear win for large loads, but watch idempotency on partial retries.
Basic inventory
Multi-warehouse with stock levels, in-transit stock and full warehouse CRUD
Cases that needed external logic in v1 are now a single native endpoint.
No standard protocol for AI agents
Official MCP connector (August 2026), with a read-only and an operational profile
Claude or ChatGPT can query Holded without bespoke REST wrappers; invoices and accounting stay read-only.
We maintain an n8n community node for Holded with 100% coverage of API v2: 43 resources · 327 operations. It is published on npm with signed provenance attestation from GitHub Actions, no manual tokens. If you want to automate against Holded without writing a full integration, it is free to start and needs no consulting.
When your case is more demanding than an n8n workflow (high volume, idempotency under concurrency, tax logic, SLAs), we build custom integrations directly against the v2 API. The node is a strong starting point and, sometimes, the whole solution.
An API integration that writes data cannot be flipped over in one shot. What we do on every project:
Holded uses a per-account API key sent as a request header on every call. You generate the key from Holded (Settings → Developers → API key) and pass it in the key header. There is no OAuth handshake for server-to-server integrations, so treat the key like a password: store it in a secret manager, never ship it to the browser, and rotate it if it leaks. Each key inherits the permissions of the account it belongs to.
Three structural changes affect existing code. First, a single consolidated base URL with sections instead of several product-specific APIs. Second, cursor pagination (an opaque next cursor) instead of numbered offset pages, which is far more efficient on large lists but breaks any loop that assumed page numbers. Third, structured error responses with consistent codes instead of ad hoc per-endpoint messages. v2 also adds bulk operations, multi-warehouse inventory with in-transit stock, recurring invoices with a queryable calendar and native attachments.
List endpoints return an opaque next cursor rather than a page number. You request the first page, read the cursor from the response, and pass it back on the next call until no cursor is returned. This is stable under concurrent writes and cheap on large datasets, but it means you cannot jump to page N or compute a total page count up front. If you are porting from v1, rewrite offset loops as cursor loops and add a test that walks a full multi-page result.
Yes. Holded now sends a webhook when a contact, invoice or payment is created, updated or deleted, signed with HMAC so you can check it really comes from Holded. Not everything has an event (there is none for an invoice falling due), so for that we still run a periodic cursor-based query. One thing to get right: deduplicate every delivery, because a webhook can arrive more than once.
MCP is the Model Context Protocol, an open standard from Anthropic that lets AI agents access external systems through typed tools with controlled permissions. Holded released its official MCP connector in August 2026 (mcp.holded.com) with two profiles: a read-only analytics profile and an operational one that can create contacts, quotes or projects, while accounting, invoices and taxes always stay read-only. For querying your data from Claude or ChatGPT it is often enough; custom work makes sense when you need unattended processes, validation against your own rules or controlled writes.
Holded throttles per API key, so design for retries from day one. Respect 429 responses with exponential backoff, batch reads with the largest page size the endpoint allows, and cache reference data (products, contacts, tax rates) that does not change every minute. Because v2 returns structured errors with consistent codes, you can branch on the code rather than parsing free-text messages, which makes retry and idempotency logic much cleaner.
Yes. We build new integrations directly on v2 and migrate existing v1 integrations with a parallel period: your v1 flow keeps producing while the v2 flow writes to staging until both reconcile. Scope and a fixed price come after a free 30-minute diagnostic where we map exactly which endpoints you touch, whether your integration writes (higher risk) or only reads, and where idempotency needs attention.
In 30 minutes we map your endpoints, flag the real risks (pagination, idempotency, rate limits) and tell you whether to build new or migrate.