XML-RPC in Odoo 20: What Is Deprecated, What Broke, and the Move to JSON-2

Somewhere in your stack there is probably a script, a middleware service, or an automation workflow that still talks to Odoo over XML-RPC. If you have read the upgrade chatter, you may also have seen the claim that Odoo 20 “removed” XML-RPC entirely. That claim is wrong, and panicking over it would waste your migration budget. The accurate picture, taken from Odoo's official 20.0 developer documentation, is more nuanced: the XML-RPC and JSON-RPC external APIs were deprecated in Odoo 19, exactly one of their three services was removed in Odoo 20, and the remaining two have a published removal date — Odoo 22 (fall 2028) for on-premise and Odoo.sh, and Online 21.1 (winter 2027) for Odoo Online. The replacement, the JSON-2 API, has been available since Odoo 19. This article separates what actually broke, what is merely on borrowed time, and how to move over at a sane pace.

Deprecated is not the same as removed

The legacy external RPC API is exposed at /xmlrpc, /xmlrpc/2 and /jsonrpc, and both protocols expose the same three services: common, db and object. Odoo 20 treats each service differently, and the differences are the whole story:

ServiceWhat it providesStatus in Odoo 20Deadline
dbDatabase management: list, create_database, duplicate_database, drop, dump, restore, rename and moreRemoved (also removed in Odoo Online 19.1)Already gone — migrate now
commonversion(), login() and authenticate()Deprecated, still workingRemoval scheduled in Odoo 22 / Online 21.1
objectexecute() and execute_kw() calls on any modelDeprecated, still workingRemoval scheduled in Odoo 22 / Online 21.1
Your own controllersEndpoints declared with @route(type='jsonrpc') in custom modulesNot part of this deprecationNone

The last row matters more than it might seem. Many custom modules expose their own JSON-RPC-style endpoints for point-of-sale hardware, e-commerce bridges, or internal tools. Those controllers (known as type='json' until Odoo 18) are your own HTTP endpoints, not the external /jsonrpc service, and the migration note in the official documentation explicitly excludes them. They keep working untouched.

What actually broke in Odoo 20: the db service

The one real casualty of the upgrade is the db service and its 13 database-management functions. Most business integrations never touched it, but infrastructure tooling often did: hosting scripts that provision databases with create_database, backup jobs that call dump and restore, and multi-database dashboards polling list(). Those calls fail on Odoo 20, and they are the first thing to inventory before upgrading.

Odoo documents replacements for nearly all of them:

  • Database operations move to the /web/database controllers — create, duplicate, drop, backup, change_password, restore and list — the same HTTP endpoints the database manager page uses.
  • server_version is replaced by GET /web/version, which also takes over from the common service's version() function.
  • list_lang and list_countries become ordinary JSON-2 calls on the res.lang and res.country models.
  • migrate_databases is the exception: the documentation notes it has no programmable replacement at the moment.

Meet JSON-2, the replacement API

JSON-2 arrived in Odoo 19, so it is not an untested novelty you are being pushed onto. A call is a plain HTTPS POST to /json/2/<model>/<method>, authenticated with an API key in the Authorization: bearer header — no database name, user ID, and password bundled into every request the way the stateless execute_kw demands. The database only needs to be named explicitly, via the X-Odoo-Database header, when one domain hosts several databases. The body is a flat JSON object holding the record ids, an optional context, and the method's arguments — all named, since JSON-2 has no positional arguments at all:

POST /json/2/res.partner/search_readAuthorization: bearer <api_key>Content-Type: application/json{  "domain": [["is_company", "=", true]],  "fields": ["name", "email", "phone"],  "limit": 10}

Error handling changes for the better, too. A successful call returns HTTP 200 with the method's return value serialized as JSON. A failed call returns a real 4xx or 5xx status with a structured error object (name, message, arguments, context, debug) instead of the RPC style of an HTTP 200 wrapping a fault. Two operational details deserve attention: every JSON-2 call runs in its own SQL transaction, so multi-step operations cannot be chained inside one transaction — prefer single methods that do the whole job, such as search_read or an action_ method — and the models, fields, and methods available on a given database are documented on that database's own /doc page. Note also that access to the external API is only available on Custom pricing plans, not on One App Free or Standard.

Two familiar common service calls get quiet replacements along the way. version() is superseded by GET /web/version, which returns the server's version information as JSON. The login() and authenticate() functions existed to return a user ID for subsequent object calls; JSON-2 needs neither the ID nor the password, because the API key identifies the user on every request. If a migrated integration genuinely needs its own user ID, the documentation points to a JSON-2 call to res.users/context_get with no record IDs — the current user is derived from the key. The stateless, credentials-on-every-call model that made XML-RPC clients so verbose is exactly what JSON-2 was designed to retire.

API keys replace the login handshake

Because JSON-2 drops the login/authenticate round trip, the API key becomes the credential to manage. Keys are generated per user from the account security settings, with a description and a duration; they are strong 160-bit random values, shown only once at creation, and they cannot be made to last longer than three months — key rotation is built into the design rather than left as a policy hope. Keys can also be managed programmatically through res.users.apikeys.generate() and res.users.apikeys.revoke(), with scopes to restrict where a key may be used. By default only users with the Settings administration right can do this (a system parameter, base.enable_programmatic_api_keys, widens it), and a default limit of ten programmatically generated keys per user applies — exceeding it fails with HTTP 422. Odoo's documentation recommends dedicated bot users for automated integrations: grant the bot the minimum permissions it needs, set its password empty so the account itself cannot be logged into, and you get a clean audit trail for free, because every call is validated against that user's access rights, record rules, and field access. Rotation follows a safe order: generate the new key, deploy it, verify every service is using it, and only then revoke the old one — a revocation attempt carrying an invalid key fails with HTTP 403, so mistakes surface immediately rather than silently.

A practical migration plan

  1. Inventory first. Search every codebase, script, and automation workflow for /xmlrpc, /jsonrpc, execute_kw, execute, and ServerProxy, then classify each hit by service.
  2. Fix db usage before upgrading. Anything calling the database service breaks on Odoo 20 itself; repoint it at the /web/database controllers or /web/version first.
  3. Create a bot user and an API key for each integration, scoped and short-lived, rather than reusing a human's credentials.
  4. Port object calls endpoint by endpoint. Most execute_kw calls translate mechanically: the model and method move into the URL, record IDs become the ids field, and keyword arguments become top-level JSON keys.
  5. Re-test error handling. Your retry and alerting logic was written for RPC faults; it now needs to read HTTP status codes.
  6. Put Odoo 22 on the calendar. Fall 2028 (winter 2027 on Odoo Online) is when common and object are scheduled to disappear. JSON-RPC integrations share exactly the same timeline — and your own type='jsonrpc' controllers, to say it once more, are not on it at all.

The bottom line: upgrading to Odoo 20 does not break a typical execute_kw integration, but it starts a countdown that Odoo has now published in its own documentation. Treat the move to JSON-2 as scheduled maintenance over the next release cycle, not as an emergency — and the one after next will hold no surprises.

Further reading

Leave a Comment