Holded API Integration
Holded is a cloud ERP for Spanish small and mid-sized businesses, and Visma acquired it in 2021. Invoicing, accounting, inventory, CRM, projects and HR sit in one product, so the accounting data you need is a section of something considerably larger. Connect it through Maesn and you reach it with the same REST API and the same data model as every other supported system.
Holded hands you a key, not a redirect
Three things about Holded land on your engineers before the first record moves: the credential is a static key your customer creates, the permissions sit on that key per endpoint, and the accounting data you want is one part of a product that also runs inventory, CRM and HR. Here is what each one is, and what Maesn takes off the table.
The handover runs on our page or none
Holded authenticates with a static API key that your customer generates in their own account, and Holded shows it exactly once. Maesn takes that key on a page you can style as yours, or you collect it yourself and post it to one endpoint that returns the account key directly. There is no OAuth redirect to build either way.
A refusal that arrives per call
Every Holded key carries scoped permissions, and an endpoint that needs a scope the key lacks answers 403 rather than failing at connect time. So a connection can look healthy while one call is refused. Maesn returns that refusal in the same error shape as every other system rather than a Holded-shaped branch in your code.
The accounting slice, already mapped
Holded publishes 338 endpoints across eleven domains, from sales and inventory through CRM, projects and HR. What Maesn exposes is the accounting-shaped part of it, mapped into the same objects you already read from every other system, so your code never learns where Holded keeps things.
What your code holds is an API key of ours and an account key per customer. The Holded key, the scope failures and the mapping out of an eleven-domain product stay on this side, and the next system answers on the same interface.
A short call is usually enough to tell whether Maesn fits your use case.
HubSpot moves party data into whichever ledger a customer keeps
HubSpot syncs client data out of the CRM and into the accounting system each of their customers runs, which is exactly the object set Holded has switched on: contacts, customers and suppliers. Today they reach DATEV and sevdesk that way.
“We integrate German accounting systems and rely on Maesn as a trusted enabler - ensuring seamless data flow, full compliance, stronger partnerships, and efficient, reliable financial processes that support our growth.”


Three reasons to reach the Holded API through Maesn
Connecting Holded directly is cheap to start and expensive to keep. The key handover is simple. Everything after it is Holded-specific: the scope model, a call budget that belongs to your customer's plan rather than to you and an object layout that looks nothing like the next system you add.
The Holded-specific work stays here
Key collection and storage per tenant, the scope refusal translated into one error contract and the accounting objects mapped out of a product that also runs inventory and HR. None of the three reaches the code you write.
What is switched on, in writing
Read and write are the same list here, so an object that arrives from Holded can be corrected in Holded without a second mapping. Everything beyond that list is available on request rather than absent, so the question is which objects your flow needs and not whether the connection can reach them.
The next system costs nothing
Once Holded runs, every other supported accounting and ERP system answers on the same interface with the same data model. Your second integration is a configuration step rather than a second key model.
The object set is what makes this a narrow integration rather than a small one. Contacts, customers and suppliers are the records two systems argue about most often, because both sides think they own the company they are describing, and keeping customer and supplier data in step is its own problem before it is a Holded one. What Holded adds is a spend limit on how often you are allowed to ask.
One Holded API key, held on our side
Holded is one of the systems with no OAuth redirect at all. It authenticates with a key, so the only question is how that secret gets from your customer to us without passing through your backend. There are two answers, and both end with an account key.
- API key
- One key per customer, generated in their own Holded account
One integration, every customer. Maesn holds the key per tenant, and what your code carries is an account key.
- The key, stored per tenant
- Your customer submits it once and Maesn stores it against their connection. Every later call carries your API key plus their account key, the same two headers you already send for every other system, and the Holded credential never sits in your database.
- The page, or no page at all
- Maesn provides the key entry page and it takes your branding, from the logo to the button and error colours. If you would rather run your own screen, one POST to the accounts endpoint returns the account key in the response and the page is skipped entirely.
The part worth designing around is the scope model rather than the key itself. Holded issues each key with scoped permissions and documents the permission every endpoint requires, so a key can authenticate perfectly and still be refused on a single call with 403. That failure arrives in production, on the one endpoint whose scope was missed, rather than at connect time when someone is watching. Maesn normalises it into the same error contract as every other system, which is the difference between one branch in your error handler and one per system.
- 1
They generate the key in Holded
An administrator opens the developer settings in their Holded account, creates a key with a description and copies it. Holded shows the value once and never again, so your onboarding text has to say that before they close the dialog.
- 2
They hand it over, or you do
They paste it into the Maesn page that carries your branding, and there is a cancel path for the case where they close it halfway. If you collected the key earlier in your own flow, you post it instead and skip the page.
- 3
You work with one account key
Maesn stores the connection and returns an account key. From there the requests look identical to every other system in the catalogue, and nothing in your code is shaped like Holded.
Every system in the catalogue authenticates differently, and Holded is the case where the handshake is trivial and the permission attached to it is not. There is no token to refresh and no environment to select, so the work moves to a single question your onboarding text has to answer: which scopes does this key need before the first call. Unified authentication is where the storage per tenant and the branded entry page live, once for every system rather than once per system. When a scope is missing anyway, the 403 comes back in the shape you already handle, because one error contract covers every system rather than each one having its own.
Holded publishes events, and you still pull
Holded documents a full webhook system of its own, and none of it is switched on through Maesn today. Both halves are true at once, and the second one decides how you build: change detection here is something you run rather than something that arrives.
Eighteen event groups and 58 events, from invoice.create to stock.update, signed with HMAC-SHA256 and retried with backoff. Subscriptions are configured inside a Holded account, and none of them reaches you through Maesn today.
One scheduled read, with the same filter and the same pagination you use for every other system. The code that detects change on Holded is the code you already wrote, pointed at a different account key, so nothing about the loop is Holded-shaped.
The interval is a commercial decision here as much as a technical one, and the budget it spends is not yours. Holded meters API calls against a monthly quota that belongs to your customer's plan. It counts per account rather than per key, so issuing a second key does not buy more room, and the lowest plan in its published table allows 500 calls a month. A polling loop therefore spends from a budget your customer owns and can exhaust, which is a conversation worth having while you size the interval rather than after the first 429.
Two paths exist in the Unified API and only one of them is available on Holded today. What arrives through the event model and what you fetch on a schedule stay separate concerns, and a system with nothing enabled puts all of the weight on the second. That is where a per-system integration is most expensive: you write a change-detection loop for Holded, then another for the next system with its own idea of what a page is, so one way to filter and page removes the part of the work that is not about Holded at all.
What that leaves is a loop whose frequency is a business input rather than a technical one, and it has one property working in its favour. A scheduled read catches up on whatever it missed while a service was down, where an event that was never delivered is simply gone. Sizing it is therefore a question of how often a call is worth spending, and the answer sits in the plan your customers are on rather than in your own code.
What a Holded integration needs first
Most of this is settled before any code runs, and two of the four are the kind that surface after go-live rather than during the build. Four things are worth having decided first.
- API access on the account, plus an administrator
- The key is created inside your customer's account, and generating it needs administrator rights. Holded displays it once and never again, so the person who creates it has to be the person who hands it over. That is one conversation at onboarding rather than a support ticket a week later.
- Which objects your use case actually needs
- Contacts, customers and suppliers are switched on and go in both directions. Invoices, bills, credit notes, payments and expenses are marked on demand, which means a request rather than a switch you flip yourself. If your flow starts with a document, raise it before you scope the work rather than after.
- The scopes the key has to carry
- Holded documents the permission each endpoint needs, and a key issued without one is refused on that endpoint alone. Deciding which scopes the connection asks for is part of the onboarding text your customer reads, not an implementation detail.
- An environment to build against
- Holded offers a free trial you can register today, and Maesn provides a sandbox populated with test data on request. Either gets you building before the customer conversation is finished, and the trial is the faster of the two to start.
We go through the key, the scopes and the test account with you.
One list for read and write, and one shape at the end
What Holded gives back and what it takes are the same list, which is rarer than it sounds and decides how a two-way sync behaves.
Everything switched on goes both ways
Contacts, customers and suppliers are readable, creatable and updatable, the same three objects each time. Two systems here are shaped that way, and no document object is enabled yet.
No booking route runs through here
Journal entries are outside what Maesn exposes for Holded, in every operation, and not an object you can ask to have switched on. Plan for that if your product posts bookings.
No object deletes by default
No object has delete enabled. Accounting practice expects corrections and cancellations as new documents anyway, so plan them that way rather than around a delete you would have to request.
What Holded hands back it also takes: the objects you can read are the objects you can create and update, so a company that arrives from Holded can be corrected in Holded. That is the part that usually breaks when two systems both believe they own the customer record. What is not there is any document. No invoice, bill or credit note is enabled today, so a product that syncs companies finds a full round trip here and a product that posts invoices finds a conversation.
The party objects come back in the shared common data model, so a supplier from Holded has the same shape as a supplier from any other supported system, and the mapping code you write here is the code you reuse for the next one. That matters more than usual on a product this wide: Holded runs sales, inventory, CRM, projects and HR alongside the ledger, and none of that layout reaches you.
Everything beyond that list is available on request rather than unavailable, which means not switched on by default. Holded has belonged to Visma since 2021 and keeps its own API, its own key model and its own quota, which is why it has a page of its own here. Tell us which objects your use case needs, or browse every system in the catalogue to see how the same question is answered elsewhere.
Tell us which objects your use case needs and we will confirm what is possible.











