The REST API Dolibarr should ship with
EasyAPI reimplements the 32 core domains of Dolibarr (sales, purchasing, stock, accounting, projects, HR, membership…) on a single contract: the same response envelope, the same error taxonomy, idempotency on every write and an MCP server for AI agents. Your Dolibarr permissions and ACL still rule.
|
32
core domains reimplemented
|
98
EAPI resources auto-discovered
|
MCP
native server for AI agents
|
V15–V24
Dolibarr compatible · PHP 7.4–8.3
|
The problem it solves
The API that ships with Dolibarr handles the basic case. Problems show up when the integration has to survive production: retries after a network drop, permissions that are actually enforced, several companies in the same installation and documents that have to total up correctly. There are five concrete failures, and none of them warns you when it happens.
| What you run into |
What it costs you |
| A POST returns the raw id and the GET returns the object: every domain answers its own way |
A normalization layer in your code, different for every resource, that you have to maintain forever |
| A retry after a timeout creates the document again: the Idempotency-Key header does not exist |
Duplicate invoices and payments that someone has to cancel and reconcile by hand |
| Deleting a payment over the API returns 500 on Dolibarr 20 or later: the signature of Paiement::delete() changed in 20 |
The only documented way to undo a payment does not work exactly when you need it |
| In multi-company setups, a valid DOLAPIKEY from another entity returns 503 “module not enabled” |
The API only talks to entity 1: every company ends up needing its own installation |
| An invoice line with equivalence surcharge or IRPF is saved with the tax at 0 |
The document totals up wrong: you charge too little or withhold too little, and no error is raised |
| The error only says “socid field missing”, without the exact field or a request_id |
Support in the dark: you have to reproduce by hand what already failed in production |
The same envelope, the same codes and the same filters across the 32 domains. You learn it once and it works for all of them: there is no different way to paginate, filter or read the error depending on the resource you touch.
NEW IN 2.28
What the AI agent asks for waits until you confirm it
With human confirmation on, a write that an agent asks for through MCP does not run. It waits in Tools › Pending actions (AI) with its data and a preview, and it only happens when the person who connected the agent presses Confirm.
- You decide what waits. Every write, or only email sending and deletions. The option comes off, so upgrading changes nothing.
- The agent cannot confirm itself. Confirmation does not exist for MCP: it is done in Dolibarr or through the REST API (POST /mcp/pending/{token}/confirm), and only the person who asked for the action can give it.
- Once, and with an expiry. Each action is confirmed once and expires after 10 minutes; the limit goes up to 24 hours.
- Marked emails. An email sent after confirmation carries X-AI-Generated and X-AI-Reviewed.
IN 2.25
Who owes you what and since when, and the price the ERP would charge each customer
Version 2.25 brought business figures you used to build by adding up invoices by hand. The MCP also tells the agent what each operation does before it calls it.
- Debt ageing. GET /invoices/kpis/aging and GET /supplier-invoices/kpis/aging split what is outstanding into not yet due, 1-30, 31-60, 61-90 and over 90 days. You change the buckets with ?buckets=0,15,45, and ?group=thirdparty gives the breakdown per third party. They follow the same ACL as the lists: a sales rep who only sees their own customers only sees those customers' debt.
- Days to collect and pay, and the real balance. /invoices/kpis adds dso and /supplier-invoices/kpis adds dpo, the average days to collect and to pay over the last 90 days. The outstanding and overdue KPIs add up what is really left to pay: they deduct partial payments, credit notes and deposits with the same calculation as the core's getRemainToPay().
- Effective price per customer. GET /products/{id}/price?socid=&qty= returns the price the ERP would put on the line for that customer and that quantity, with VAT, surcharge and withholding tax, the totals and where the price comes from: base, price_level, customer_price or quantity.
- Errors without SQL. The database engine's message stays in the syslog with the request_id, and the response carries a code you can act on: a duplicate is a 409 with reason: duplicate_record, and a lock is a 503 with Retry-After: 2 so you can retry.
- Every operation declares its effect. x-effect says whether it reads (read), writes inside the ERP (internal), reaches outside (external) or cannot be undone (irreversible), and the MCP maps it to readOnlyHint and destructiveHint. Deletes through MCP can be forbidden in the setup (EASYAPI_MCP_ALLOW_DELETE) or user by user, and the stricter setting wins.
- Masked IBAN. Through MCP, the IBAN, the account number and the mandate reference of third-party bank accounts come back with only the last 4 characters. Nothing changes over REST.
A look at the result
The OpenAPI specification generates itself from the resources and is served inside the module, filtered by the permissions of the user requesting it.
The 32 domains browsable, with a server selector and DOLAPIKEY authentication. Every operation comes with its schema and an example ready to run.
Comparison against the native API
| Dimension |
Native Dolibarr API |
EasyAPI |
| Coverage |
More than 50 API classes spread across the core modules, with uneven coverage: in accounting, for example, the only endpoint is the data dump |
32 core domains complete, with their sub-resources: lines, contacts, payments, discounts… |
| How it responds |
Heterogeneous: one POST returns a raw integer, another the object |
Uniform envelope {success, data, meta}: the POST answers 201 with the created record |
| Errors |
Mismatched codes and messages, with no taxonomy and nothing you can quote in a ticket |
Taxonomy of 16 types, the exact field in error.fields, and request_id + doc_url on all of them |
| Idempotency |
Does not exist |
Atomic reservation via Idempotency-Key on every write, including state transitions |
| Bulk operations |
Do not exist |
207 Multi-Status on create, update and delete, up to 100 items per batch and with the result of each one |
| Queries |
Raw SQL filters to write by hand, with no sorting and no field selection |
Filters and sorting in JSON, search, field selection, embedded resources, page and cursor pagination |
| Resilience |
Does not exist |
Request rate limiting, database lock retries, optimistic concurrency and maintenance mode |
| Security |
Native rights, with real failures: the sales reps query returns 401 where a 403 belongs |
The same native rights, with the per-record ACL fixed and uniform: correct 403 and no leaks between third parties or between entities |
| Documentation |
Basic Swagger explorer |
OpenAPI generated from the code, with Scalar, Swagger UI and Redoc embedded and filtered by permissions |
| Extensibility |
You have to touch the core to add endpoints |
Auto-discovery: you create a folder in your own module and your endpoints show up documented |
How it is used
Authentication with your user's DOLAPIKEY in the header. The same key works as Authorization: Bearer if your server swallows custom headers. An invoice with its line in a single call:
INVOICE WITH ITS LINE, IN ONE REQUEST
curl -X POST "https://tu-dolibarr.com/custom/easyapi/api/invoices" \
-H "DOLAPIKEY: tu_clave" \
-H "Idempotency-Key: pedido-4417" \
-H "Content-Type: application/json" \
-d '{"socid": 128, "lines": [{"fk_product": 55, "qty": 2, "subprice": 49.90}]}'
201 · THE INVOICE IS BORN AS A DRAFT WITH ITS PROVISIONAL REF
{
"success": true,
"data": {
"id": 902,
"ref": "(PROV902)",
"socid": 128,
"statut": 0,
"total_ttc": 119.76,
"lines": [ { "id": 3311, "qty": 2, "subprice": 49.90 } ]
}
}
422 · THE ERROR TELLS YOU THE FIELD, NOT JUST THAT SOMETHING FAILED
{
"success": false,
"error": {
"type": "validation_error",
"code": 422,
"message": "Validation failed",
"fields": { "socid": "does not exist" },
"request_id": "req_7f3a91c2",
"doc_url": "https://easysoft.es/docs/api#validation_error"
}
}
The final ref is assigned by validation, which is how Dolibarr works: POST /invoices/902/validate replaces (PROV902) with the series number and recalculates the totals. Send the same request again with the same Idempotency-Key and you will get the invoice from before, not a second one, with the Idempotent-Replayed: true header telling you it was not run again.
What is included
Writes that do not duplicate
Idempotency-Key with atomic reservation and announced replay on every write, including state transitions (validate, pay, close).
Errors that explain themselves
A single taxonomy of 16 types, per-field detail and two pieces of data for the ticket: the request identifier and the link to the error document.
Queries without hand-written SQL
Filters, sorting, search and field selection in JSON; page and cursor pagination to walk large tables without losing rows.
Your permissions, as they are
Dolibarr's native rights plus the fixed per-record ACL: a sales rep does not see what is not theirs, and the 403 shows up where it should.
MCP server for agents
POST /mcp endpoint for Claude, Cursor or any MCP client, authenticated with your key and scoped to the domains you want.
Configurable resilience
Request rate limiting, database lock retries, optimistic concurrency with ETag, maintenance mode and a body size limit. All through constants.
What it is used for
- Syncing an online store or a marketplace: orders, stock and prices against your database, without writing SQL
- A mobile app for sales reps with an HttpOnly cookie session, where the ERP key never reaches the browser
- Dashboards and BI that read the 32 domains with filters and cursor pagination
- Automations and AI agents connected over MCP to your own ERP, with the write rules you decide
- Several companies served from a single API, every user on their own entity
- An external ERP that needs to create invoices and payments knowing that a retry does not duplicate anything
Getting started
1
Unzip the ZIP into custom/easyapi and run composer install --no-dev --optimize-autoloader.
2
Enable EasyAPI in Settings → Modules and open its setup screen: CORS, session, limits and MCP are configured from there.
3
Copy your user's key (User → API tab) and check that it responds: GET /me returns who you are.
4
Open /docs and browse the 32 domains. If your server is nginx, apply the block from the included guide before taking anything for granted.
Requirements
| Dolibarr |
V15 to V24 (tested on 18 and 24; core signatures are checked from 10 to 24) |
| PHP |
7.4 to 8.3, with curl, json and mbstring |
| Database |
MySQL 5.7+ · MariaDB 10.2+ · PostgreSQL 10+ |
| Updates |
365 days of access to updates |
Before you buy
Can I adapt it to my case?
Yes. You get the full source code and you can read, modify and adapt it without asking permission. On top of that the resources are auto-discovered, so the usual thing is that you add your own endpoints from your module instead of touching ours.
How do I try it without risking my data?
Install it on a copy of your Dolibarr and call GET /status: it responds without touching a single record. All reads are harmless. Writes do create real data, so test them on that copy: what Idempotency-Key guarantees is that a retry after a network drop does not duplicate the record, not that the operation is reversible.
What if something does not fit my installation?
Write to us at
info@easysoft.es before buying and we will look at it with you: Dolibarr version, PHP, hosting and what you need to integrate. We would rather tell you it does not fit than sell you something that does not work for you.
Will my integration break when I update?
The contract is a stable v1 API and every response declares it in the X-EasyAPI-Version header. Only compatible changes go into v1: new optional fields, new endpoints, new headers. A breaking change would ship as v2, and your integration would stay on v1 while you migrate whenever it suits you.
What happens when the 365 days run out?
The module you already have keeps working: nothing is disabled or blocked. What ends is access to updates and new downloads, and email support. If you want to pick it up again later, it renews.
Active maintenance
EasyAPI is not a module that gets published and forgotten: the current version is 2.28.0 and every delivery adds tests that hold down what it fixes. 2.28 brings a test for every rule of the human confirmation, checked by breaking the rule on purpose, a browser test of the pending actions page and an HTTP check on Dolibarr 15, 18 and 24. On top of the tests, every release goes through QA against real Dolibarr instances before it is published.
|
31
modules published on Dolistore
|
100 %
full source code
|
8
languages in the interface
|
365
days of updates
|
Does it fit what you need to integrate?
Tell us your case and we will tell you if EasyAPI works for you — and if it does not, we will tell you anyway.
info@easysoft.es www.easysoft.es