Cross-version compatibility (Odoo 10-19)¶
The MCP server resolves model, field, and capability names according to the
detected version, edition, and deployment. The authoritative, verifiable source
is the repository's delta map
(server/src/odoo_mcp/compat/deltas.py);
this page summarizes it.
New in v1.5.0
- The permission policy follows the renames. A rule on either name of
a renamed or merged pair of this map applies to both, on every version
(
account.invoiceandaccount.move,stock.quant.packageandstock.package,base.automationandbase.action.rule, and so on), and a refusal on either name wins. See Precedence. - The sandbox matrix (Odoo 10.0 to 19.0) also checks the permission policy against each live series, and skips, with its reason, a check whose model or field that series lacks.
odoo_add_automationon Odoo 10 fills the trigger field ofbase.action.rule, which that version nameskind(nottrigger).
Detection¶
- Version: major and minor of
common.version()(server_version_info, or parsed fromserver_version); the major is clamped to 10-19. The minor is 0 on a stable series and N on Odoo Online'ssaas~<major>.Nlines, which sit between two stable series: 18.0 < saas~18.1 < … < saas~18.4 < 19.0. WithODOO_TRANSPORT_PREFset toautoorjson2, the version is read without signing in and without a deprecated endpoint, so it leaves no line in the Odoo log: first the web client's own route (POST /web/webclient/version_info, 10.0 and later), thenGET /json/version(19.0+), and only last the classiccommon.version.jsonrpcandxmlrpckeepcommon.versionfirst. The transport keeps the unclamped series (e.g. 20.0, saas~21.1) for its own choice. - Edition:
+esuffix in the version string; if not, the moduleweb_enterpriseis probed (ir.module.module); if not,unknown. - Deployment: host
*.odoo.com=>saas, otherwiseonprem.odoo_online_profilealso says how sure that detection is (deployment_confidence):versionwhen the version string is asaas~line, which only Odoo Online runs (it then reportssaaseven on a custom domain);hostwhen only the host name decides, because Odoo.sh also uses*.odoo.com, and an Online database on a stable series behind its own domain looksonprem.
Model renames¶
| Modern name | Historical name | Boundary |
|---|---|---|
account.move |
account.invoice |
see the warning about v10 to v12 |
account.move.line |
account.invoice.line |
see the warning about v10 to v12 |
stock.package |
stock.quant.package |
new in v19 |
For stock.package you can pass either name; the resolver returns the valid
one in the target instance.
Accounting: account.move and account.invoice are NOT a simple rename
In Odoo 13 the invoice model account.invoice was merged into the
accounting-entries model account.move (and account.invoice.line into
account.move.line). But in Odoo 10 to 12 both coexist as distinct models
with distinct semantics: account.move are the journal entries and
account.invoice are the invoices.
For that reason, globally substituting one name for the other is dangerous,
especially in writes and deletes (odoo_create/odoo_write/odoo_unlink):
an operation meant for invoices could land on journal entries (or vice versa)
in v10 to v12.
Practical rule in v10 to v12 instances: name explicitly the model you want
(account.invoice for invoices, account.move for entries) and verify with
odoo_fields_get before writing. In v13 and later only account.move exists,
so the ambiguity disappears.
The resolver already applies this rule: it maps account.invoice →
account.move only on v13+ and never rewrites account.move backwards.
Field changes¶
| Model | Field | Change | Boundary |
|---|---|---|---|
account.move.line |
analytic_account_id → analytic_distribution |
rename | v16 |
product.template |
uom_po_id |
removed (use uom_id) |
saas~18.1 (Online) and 19.0 |
res.partner |
company_type |
removed (use is_company) |
saas~19.1 (Online); still in 19.0 |
Both boundaries were checked against Odoo's public source on every stable and saas branch from 16.0 to saas-19.4.
Removed fields are dropped from the query with a dropped_fields warning,
instead of failing the whole call.
Capabilities¶
| Capability | Availability |
|---|---|
api_key_auth |
Odoo >= 14 (earlier ones require a password) |
update_field_translations |
Odoo >= 16 (earlier ones use writing with language context) |
API keys are accepted on /jsonrpc and /xmlrpc/2 alike (both call Odoo's
dispatch_rpc, 14 to 19), and they are the only credential JSON-2 accepts. The
auto transport keeps its XML-RPC fallback for when a proxy blocks or alters
/jsonrpc.
Methods¶
| Method | Change | Use instead |
|---|---|---|
name_get |
deprecated in v17, removed in v18 | read display_name (10 to 19) |
read_group (classic) |
deprecated in 19.0; absent from saas~19.1 to saas~19.4; back in 20.0 with the _read_group signature |
formatted_read_group (saas~18.4+) |
formatted_read_group |
new in saas~18.4 (web module) |
|
check_access_rights |
deprecated in 19.0, removed from saas~19.1 | has_access (18.0+) |
The method entries are data too (METHOD_DELTAS). odoo_read_group calls
formatted_read_group from saas~18.4 on and keeps the classic arguments and
output; see the Tools reference.
RPC endpoints¶
New in v1.4.0: JSON-2 transport
Odoo 19 deprecates /xmlrpc and /jsonrpc (every call logs a warning on
the Odoo server), and Odoo 22 and Odoo Online saas~21.1 remove them.
Their replacement, JSON-2 (/json/2/<model>/<method>), exists from
saas~18.4 and 19.0 and accepts an API key only. Since v1.4.0 the plugin
uses it automatically.
| Endpoint | Series | Credential | Notes |
|---|---|---|---|
/xmlrpc/2 |
10.0 to 21.0 | password, or API key 14+ | deprecated in 19.0 (moved to the auto-installed rpc module); every call logs a warning in the Odoo log; removed in Odoo 22 and Odoo Online saas~21.1 |
/jsonrpc |
12.0 to 21.0 | password, or API key 14+ | same as /xmlrpc/2 (both go through dispatch_rpc, 14 to 19) |
JSON-2 POST /json/2/<model>/<method> |
saas~18.4, 19.0+ | API key only (Authorization: bearer) |
named arguments only; X-Odoo-Database header; no deprecation warning |
The auto transport¶
With ODOO_TRANSPORT_PREF=auto (the Transport preference option, the
default), the server decides from the unclamped series, the credential kind
and whether ODOO_URL carries user:pass@ (basic auth for a gateway, which
needs the same Authorization header as the JSON-2 key):
| Case | Order | Notes |
|---|---|---|
| Before saas~18.4 (Odoo 10 to 18, saas~18.1 to 18.3) | JSON-RPC → XML-RPC | as before |
saas~18.4 / 19.0+, with an API key and no user:pass@ |
JSON-2 → JSON-RPC → XML-RPC | JSON-2 is pinned after the first sign-in; JSON-RPC and XML-RPC only if a proxy blocks JSON-2 |
same range, with a password or user:pass@ |
JSON-RPC → XML-RPC | from 19.0 odoo_version returns a transport_notice with the fix and the server logs it once |
| saas~21.1 / Odoo 22+, with an API key | JSON-2 only | no other endpoint is left |
saas~21.1 / Odoo 22+, with a password or user:pass@ |
ConfigError |
set an API key and remove user:pass@ |
json2 forces JSON-2; jsonrpc and xmlrpc force that endpoint. None of them
switch transport by themselves. A call moves to another transport only when
that cannot run it twice: version, sign-in and reads always; writes only if the
request never reached Odoo. A rate limit (HTTP 429) never switches transport:
reads wait for Retry-After (at most 30 s, 3 tries) and writes are reported.
On JSON-2, odoo_execute takes the record ids in ids and the other arguments
by name in kwargs. JSON-2 sends @api.readonly methods to a read-only cursor,
which is a replica when the Odoo host sets db_replica_host: a read right after
a write may lag there.
Odoo Online tools¶
The six Odoo Online tools work on any Odoo 10-19 and degrade per version (details in the Tools reference):
| Tool | Version notes |
|---|---|
odoo_online_profile |
API keys from 14, expiry from 18; on 19.0 only an administrator can read the module list (for other users it comes back null and unavailable says why) |
odoo_api_catalog |
/doc-bearer needs 19.0+ and an administrator's API key; a regular user gets the built-in table |
odoo_access_check |
record level from 18 (has_access); check_access_rights on 10-17; export group from 16 |
odoo_record_documents |
stored invoice PDF from 17; these tools do not render reports (odoo_report is deprecated and never worked over RPC) |
odoo_import_preview |
16-19, over JSON-RPC or JSON-2 |
odoo_import |
12-19, over JSON-RPC or JSON-2; atomic on every version |
Editions¶
The Enterprise-exclusive models (heuristic list: documents.document,
sign.request, account.consolidation.period, quality.check,
helpdesk.ticket, planning.slot, appraisal.appraisal) raise a
CompatError when the instance is detected as Community. Runtime module
probing is the authoritative source.
"Best-effort" boundaries¶
Boundaries not yet confirmed against Odoo's source or a real instance are
marked best-effort. Since the map is flat data covered by table-driven tests,
correcting a boundary is a one-line edit plus a test row (see
Contributing). The sandbox matrix workflow also checks the
res.partner entries against a real Odoo Community 10.0-19.0 every week, and
since v1.5.0 the permission policy too: the policy block of odoo_version
against --check-policy, the standard refusals that need Odoo's own metadata,
the link field of each installed delegating model, odoo_add_field and
odoo_add_automation under builder (base.action.rule on 10.0) and
--verify-audit on the audit trail of the run; since v1.5.1, also the refusal
of a default_<field> holding a list in a context passed positionally. Since
v1.6.0 the server signs in with the sandbox's API key on every series from 14.0
(over JSON-2 on 19.0) and with the password on 10.0 to 13.0, and the workflow
checks that neither reaches the audit trail and that a second sandbox up
keeps the key.
In practice¶
Write against the modern name, except accounting on v10 to v12:
account.invoice/account.move is a merge, not a rename. On v10 to v12 name
explicitly the model you want (account.invoice for invoices, account.move
for entries) and verify with odoo_fields_get before writing. The resolver
(resolve_model) maps account.invoice → account.move only on v13+ and
never rewrites account.move backwards. If a query returns dropped_fields,
it means that field does not exist in the target version: check the equivalent
modern name in the table above. Since v1.5.0, a permission-policy rule on
account.move also restricts account.invoice on v10 to v12, and the other
way round. On v10 to v12, pass the context by name (in kwargs): those versions
take an object passed positionally as the context, since v1.5.1 the policy
checks it as one, and since v1.6.0 it refuses its keys that are neither fields
of the model nor allowed context keys (see
known limit 10).
Skill reference: /odoo-tools:odoo-crossversion.