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:
| Service | What it provides | Status in Odoo 20 | Deadline |
|---|---|---|---|
db | Database management: list, create_database, duplicate_database, drop, dump, restore, rename and more | Removed (also removed in Odoo Online 19.1) | Already gone — migrate now |
common | version(), login() and authenticate() | Deprecated, still working | Removal scheduled in Odoo 22 / Online 21.1 |
object | execute() and execute_kw() calls on any model | Deprecated, still working | Removal scheduled in Odoo 22 / Online 21.1 |
| Your own controllers | Endpoints declared with @route(type='jsonrpc') in custom modules | Not part of this deprecation | None |
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/databasecontrollers —create,duplicate,drop,backup,change_password,restoreandlist— the same HTTP endpoints the database manager page uses. server_versionis replaced byGET /web/version, which also takes over from thecommonservice'sversion()function.list_langandlist_countriesbecome ordinary JSON-2 calls on theres.langandres.countrymodels.migrate_databasesis 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
- Inventory first. Search every codebase, script, and automation workflow for
/xmlrpc,/jsonrpc,execute_kw,execute, andServerProxy, then classify each hit by service. - Fix
dbusage before upgrading. Anything calling the database service breaks on Odoo 20 itself; repoint it at the/web/databasecontrollers or/web/versionfirst. - Create a bot user and an API key for each integration, scoped and short-lived, rather than reusing a human's credentials.
- Port
objectcalls endpoint by endpoint. Mostexecute_kwcalls translate mechanically: the model and method move into the URL, record IDs become theidsfield, and keyword arguments become top-level JSON keys. - Re-test error handling. Your retry and alerting logic was written for RPC faults; it now needs to read HTTP status codes.
- Put Odoo 22 on the calendar. Fall 2028 (winter 2027 on Odoo Online) is when
commonandobjectare scheduled to disappear. JSON-RPC integrations share exactly the same timeline — and your owntype='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
- External JSON-2 API — Odoo 20.0 documentation (includes the “Migrating from XML-RPC / JSON-RPC” section)
- Odoo 20 Release Notes — Odoo
- Which of my XML-RPC / JSON-RPC integrations actually break on Odoo 20? — Odoo forum
