maesn
For developers

How to integrate with Odoo: Two protocols with a deadline, one table for every contact

Odoo has no REST interface. The two remote procedure protocols that stand in for one now carry a removal date, and hosted instances reach it first. That moves three questions to the front of a build: what your customer hands over, how long it stays valid and why every contact arrives in a single table.

Lennart Svensson, CTO and Co-Founder at Maesn
Lennart Svensson
CTO and Co-Founder · · Updated
Illustration for How to Integrate with Odoo: Two Protocols, One Deadline
The problem

Two remote procedure endpoints, both with a removal date

Odoo answers on remote procedure calls rather than on REST, and until recently the only question was which of the two you picked. Its own reference has since put a removal date on both, and it names two dates rather than one.

The notice sits in a box marked Danger at the top of the RPC reference: “Both the XML-RPC and JSON-RPC APIs at endpoints /xmlrpc, /xmlrpc/2 and /jsonrpc are scheduled for removal in Odoo 22 (fall 2028) and Online 21.1 (winter 2027).” The External JSON-2 endpoint is named as the replacement.

The three interfaces, and how each one identifies the caller
InterfaceHow you authenticateStatus
/xmlrpcDatabase, user id and password on every callScheduled for removal
/jsonrpcDatabase, user id and password on every callScheduled for removal
/json/2A bearer key in the Authorization headerNamed as the replacement

Endpoints, dates and the successor from Odoo’s external RPC reference and JSON-2 reference, both checked 14 August 2026. One exclusion travels with the notice: controllers declared with the jsonrpc route type are not covered by it.

What reaches your code is REST, on the same interface as every other system in the catalogue, described on the Odoo API page. Which of the two dates applies to a given customer follows from their hosting and their version.

Scheduled for removalTwo dates, not one
/xmlrpc/xmlrpc/2/jsonrpc
Named as the replacement
/json/2/<model>/<method>
Winter 2027
Online 21.1
Odoo Online
Autumn 2028
Odoo 22
Self-hosted and Odoo.sh

What reaches your code today is REST. Which of the two dates applies to a customer follows from their hosting, so a mixed portfolio crosses the line twice, roughly a year apart.

The three endpoints, their replacement and the two dates Odoo names for them.
Why it is hard

One major version a year, and each one is its own system

Odoo has shipped one major version a year for eleven years and runs each of them in isolation, with its own codebase and its own database schema. Nobody is upgraded automatically, so a version change is a project somebody schedules.

That is ordinary release management right up to the moment you put it beside the two dates. Which interface a tenant can call is a property of its version, and now so is how long it can keep calling it.

The release list explains the asymmetry. Online 21.1 is a minor version, and minors have arrived every couple of months since January 2026, while major 22 follows the annual rhythm. A hosted tenant is moved by Odoo, on Odoo’s timing; a self-hosted one moves when its owner decides, which makes the later date the softer one only in appearance.

Eleven majors from version 9 in October 2015 to version 19 in September 2025, plus four minors between January and July 2026, counted on Odoo’s release notes page, checked 14 August 2026. Odoo also states that new majors change data models, business logic and occasionally the behaviour of the calls, so an integration validated against one version is not thereby valid on the next.

How it works

What your customer hands over and how long it stays valid

Four values, once: the base URL of the instance without any path, the database name behind it, a login email and either a password or a key. On the RPC endpoints all four travel with every call, which is where integrations tend to go looking for a session.

There is not one. “Both functions are stateless. It means that the database, user ID and user password are to be provided for each call.” Nothing is exchanged, nothing is refreshed, nothing expires mid-run.

The successor works the other way around. A JSON-2 request carries Authorization: bearer and, where one server hosts several databases, an X-Odoo-Database header. Neither the user id nor the password appears.

A JSON-2 read, in the documented shapeHTTP
POST /json/2/res.partner/search_read HTTP/1.1
Host: mycompany.example.com
X-Odoo-Database: mycompany
Authorization: bearer <api-key>
Content-Type: application/json; charset=utf-8
 
{
"domain": [["is_company", "=", true]],
"fields": ["name"]
}

The model and the method sit in the path and the arguments are all named. The key is a placeholder on purpose: identifier formats differ per system.

How long a key is allowed to live

A key is created with a duration, and Odoo caps that choice: “it is not possible to create keys that last for more than three months. This means that long lasting keys must be rotated at least once every three months.” The value is shown once and cannot be read back.

There is a way to do it without a human: res.users.apikeys.generate() and revoke(), restricted by default to users with Settings administration rights and capped at ten keys, above which the call returns 422. Odoo’s own advice is to authenticate with the new key when revoking the old.

One prerequisite is easy to miss on hosted instances: Odoo Online creates users without a local password, so an administrator sets one before RPC access works at all. Keeping that kind of variety off your side is the subject of unified authentication.

Statelessness, the bearer scheme, the three-month ceiling and the programmatic methods from Odoo’s JSON-2 reference, checked 14 August 2026. The Odoo Online password step is in its RPC reference.

Why it is hard

Companies and people share one table called res.partner

Odoo keeps companies and people in a single model. Two fields tell them apart: is_company, a boolean, and parent_id, which points a person at the company they belong to. A list call returns both kinds together, in no useful order.

One failure follows, and it is structural rather than a matter of care. Treat every record as standalone and a company with four contacts arrives as four companies, because each person carries its parent as an attribute instead of a link. The relationship is not lost in transit, it was never read.

One modelres.partner
A company
  • is_companytrue
  • parent_idusually empty
A person
  • is_companyfalse
  • parent_idthe company, or empty
What a list call returns

Both kinds, mixed, with nothing in the response separating them.

Contacts is the route in, not one of several. Customers and suppliers are marked unavailable on Odoo in every column of our coverage.

One model, two kinds of record, and the two fields that are the only thing separating them.

Here that is the only route in. Our coverage marks Customers and Suppliers unavailable in every column, and the filter in the request above is the one Odoo uses in its own example.

What you get

Six objects are readable today, three of them writable

Six objects are enabled for reading and three of those for writing. Nothing is enabled for update or delete, so corrections are posted as new documents, which is how accounting practice expects them anyway.

Enabled today, counted in our own coverage data
ObjectReadWrite
AccountsYesYes
ContactsYesYes
Journal entriesYesYes
InvoicesYesNo
JournalsYesNo
PaymentsYesNo

A further 133 of the 185 object and operation combinations are marked available on request. That is the ERP surface rather than a roadmap: the objects exist, they are not switched on by default, and which of them we enable is a conversation about your use case.

Two limits sit outside that conversation. Odoo exposes only what the connected instance has installed, so a customer without the Accounting app has no invoices to read whatever the table says. And access to the external interface is “only available on Custom Odoo pricing plans”, which belongs in qualification rather than in a sprint.

What runs instead of a subscription

Our webhook documentation lists the systems you can subscribe to through us, and Odoo is not among the nine. What keeps a copy current here is a scheduled read, so freshness is a question of interval and the interval is yours.

Object counts read from our own coverage data on 14 August 2026 and cross-checked against the rendered Odoo integration docs. The plan restriction is Odoo’s wording and appears on both reference pages. On our side every integration is included, at a flat fee per connected customer rather than per call.

Where our part ends

What Maesn takes over, and what stays with you

What we take off the list:

  • The protocol. The call, its shape and the model names stay behind our interface, so no RPC construction reaches your code.
  • The four values, per customer. Base URL, database, login and credential are collected once and attached to every request, hosted or self-hosted.
  • The res.partner split. Companies and people are separated and linked before they reach you, which is the difference between a contact list and a duplicate generator.

Three things stay with you, and two are decisions before they are work:

  • The plan and the version your customer is on. The Custom-plan gate is between them and Odoo, and no integration layer moves it. The same is true of the version they run, which is what decides when the two dates above reach them.
  • How fresh your product needs to be. A scheduled read has an interval, and picking it is a product decision. Paging and filtering read the same here as everywhere else, which is the subject of unified pagination and filtering.
  • Which objects you need switched on. Six are enabled today, and that set is where the conversation starts rather than where it ends.
FAQ

Frequently asked questions

Is XML-RPC deprecated in Odoo?

Yes, and so is JSON-RPC. Odoo's 19.0 reference schedules both endpoints for removal, naming Odoo 22 in autumn 2028 and Online 21.1 in winter 2027, with the External JSON-2 endpoint as the replacement. One exception is easy to misread: Odoo also says that controllers written inside an addon with @route(type='jsonrpc') are not covered by the notice. That is a route decorator in Odoo's own framework, not the public /jsonrpc endpoint an integration calls, so it does not spare the endpoint and it changes nothing for an external integration.

Does Odoo have a REST API?

Not in the sense most integrations expect. Odoo exposes remote procedure calls, historically XML-RPC and JSON-RPC through one endpoint that takes a model name and a method in the request body. Version 19 added a plainer HTTP endpoint at /json/2, closer to REST without being it.

Is Odoo's external interface free to use?

Two answers, because there are two sides. On Odoo's side there is a gate rather than a price: its documentation states that access through the external interface is available only on Custom plans, not on One App Free or Standard. On ours every integration is included, at a flat fee per connected customer.

Do I need to manage sessions when calling Odoo?

No, and Odoo's reference is explicit. The two object service functions are stateless, so the database, user id and password travel with every call rather than being exchanged for something short-lived. The successor drops even that and reads a bearer key from the header.

How long does an Odoo key stay valid?

You set a duration when you create it, and Odoo caps the choice: its documentation states that keys lasting longer than three months cannot be created, so a long-running integration rotates at least quarterly. The value is displayed once and cannot be retrieved later.

Why do Odoo contacts arrive as a mix of companies and people?

Because both live in one model called res.partner. A boolean named is_company says which kind a record is, and parent_id links a person to their company. A list call returns both kinds together, so the separation happens after the read.

Which objects can I read and write in Odoo through Maesn today?

Six are readable: accounts, contacts, invoices, journals, journal entries and payments. Three of those are writable, namely accounts, contacts and journal entries, and nothing is enabled for update or delete. A further 133 of 185 combinations are available on request.

Build once on the Unified API.

Odoo is retiring the two protocols its integrations were built on, and every customer crosses that line on their own schedule. Build against one interface and the distance between one system and the next stops being your architecture.