Summary
Importing/operating against a Cloudflare-fronted Odoo behind the json2s (bearer) protocol breaks because odoolib's json2 client introspects each model via GET /doc-bearer/<model>.json, and Cloudflare returns a JS challenge (HTTP 403 "Just a moment…") for that GET. Any call made with positional arguments triggers introspection and fails before reaching Odoo.
Hit during a real migration against a client test env (multi-company, Cloudflare in front, protocol = json2s).
Reproduction
conn = conf_lib.get_connection_from_config(json2s_conf)
conn.get_model('ir.model').search_read([('model', 'like', 'x')], ['model']) # positional args
# -> odoolib json2 does GET /doc-bearer/ir.model.json -> Cloudflare 403 -> call never reaches Odoo
Root cause
odoolib/json2.py _introspect() does GET {base}/doc-bearer/{model}.json to map positional args → param names. The /doc-bearer/ GET path is challenged by Cloudflare, while the actual RPC POST /json/2/<model>/<method> is not (it reaches Odoo fine).
Workaround (confirmed working)
Call with keyword arguments only — that path skips _introspect() and POSTs straight to /json/2/:
conn.get_model('ir.model').search_read(domain=[('model', 'like', 'x')], fields=['model']) # kwargs -> works
The whole migration import then works over json2s (locations, products, supplierinfo via the importer; data reads via kwargs).
Suggestions
- Make introspection optional / skippable (e.g. a connection flag), or
- Fetch the method schema over the same POST channel instead of the
/doc-bearer/ GET, or
- At minimum, document that json2s against Cloudflare-fronted Odoo requires kwargs-only calls, and have the importer prefer kwargs.
Related migration gotchas worth capturing (same env)
- Multi-company import: new
stock.location / product.template / product.supplierinfo must carry company_id to satisfy the cross-company parent constraint; but inventory-mode stock.quant create rejects company_id (only an allow-listed field set is permitted) — company must come from the location + allowed_company_ids context instead.
- Supplier xmlid:
product.supplierinfo.partner_id/id must use the partner's actual export xmlid (__import__.<contact-id>), not __import__.<ref>.
- Per-call context does forward over
jsonrpc; needed for inventory_mode inventory adjustments.
Summary
Importing/operating against a Cloudflare-fronted Odoo behind the
json2s(bearer) protocol breaks because odoolib's json2 client introspects each model viaGET /doc-bearer/<model>.json, and Cloudflare returns a JS challenge (HTTP 403 "Just a moment…") for that GET. Any call made with positional arguments triggers introspection and fails before reaching Odoo.Hit during a real migration against a client test env (multi-company, Cloudflare in front,
protocol = json2s).Reproduction
Root cause
odoolib/json2.py_introspect()doesGET {base}/doc-bearer/{model}.jsonto map positional args → param names. The/doc-bearer/GET path is challenged by Cloudflare, while the actual RPCPOST /json/2/<model>/<method>is not (it reaches Odoo fine).Workaround (confirmed working)
Call with keyword arguments only — that path skips
_introspect()and POSTs straight to/json/2/:The whole migration import then works over json2s (locations, products, supplierinfo via the importer; data reads via kwargs).
Suggestions
/doc-bearer/GET, orRelated migration gotchas worth capturing (same env)
stock.location/product.template/product.supplierinfomust carrycompany_idto satisfy the cross-company parent constraint; but inventory-modestock.quantcreate rejectscompany_id(only an allow-listed field set is permitted) — company must come from the location +allowed_company_idscontext instead.product.supplierinfo.partner_id/idmust use the partner's actual export xmlid (__import__.<contact-id>), not__import__.<ref>.jsonrpc; needed forinventory_modeinventory adjustments.